Web Performance Optimization
Improve Core Web Vitals, reduce bundle sizes, and optimize frontend performance with lazy loading, code splitting, and modern build tools.
Overview
Web performance directly impacts user engagement, conversion rates, and search rankings. Google’s Core Web Vitals — Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS) — provide measurable targets. This resource covers practical techniques: lazy loading, code splitting, image optimization, critical CSS, and modern build tooling to hit sub-3-second page loads.
When to Use
Use this resource when:
- Core Web Vitals scores are failing (LCP > 2.5s, CLS > 0.1)
- Mobile users on 3G networks abandon pages before they load
- Bundle sizes exceed 200KB and impact time-to-interactive
- Third-party scripts (analytics, ads) block the main thread
Solution
Critical CSS Inline + Async Load (HTML)
<head>
<!-- Inline critical CSS (~14KB max) -->
<style>
/* Above-fold styles: header, hero, layout skeleton */
body{margin:0;font-family:system-ui}
.hero{background:#3b82f6;min-height:60vh}
</style>
<!-- Preload key resources -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/hero-image.webp" as="image" fetchpriority="high">
<!-- Async load non-critical CSS -->
<link rel="preload" href="/styles.css" as="style" onload="this.rel='stylesheet'">
</head>
Lazy Loading Images with Native API
<!-- Native lazy loading — no JavaScript required -->
<img src="hero.webp" alt="Hero" fetchpriority="high" width="800" height="400">
<img src="below-fold-1.webp" alt="Product" loading="lazy" width="400" height="300">
<img src="below-fold-2.webp" alt="Team" loading="lazy" width="400" height="300">
Code Splitting with Live Imports (React)
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
const AnalyticsDashboard = lazy(() => import('./AnalyticsDashboard'));
function Dashboard() {
return (
<div>
<CriticalStats /> {/* Always loaded */}
<Suspense fallback={<Spinner />}>
<HeavyChart /> {/* Loaded on demand */}
</Suspense>
<Suspense fallback={<Spinner />}>
<AnalyticsDashboard /> {/* Separate chunk */}
</Suspense>
</div>
);
}
Explanation
Core Web Vitals targets:
| Metric | Good | Poor | Measures |
|---|---|---|---|
| LCP | < 2.5s | > 4s | Largest visible element load time |
| INP | < 200ms | > 500ms | Interaction responsiveness |
| CLS | < 0.1 | > 0.25 | Visual stability (layout shifts) |
| TTFB | < 600ms | > 1.8s | Time to first byte |
Performance budget example:
- JavaScript: 150KB (gzipped)
- Images: 250KB total
- CSS: 50KB (including critical inline)
- Fonts: 40KB (subsetted)
- Third-party: 100KB max
Variants
| Technique | Impact | Effort |
|---|---|---|
| Image optimization (WebP/AVIF) | -50% image bytes | Low |
| Font subsetting | -80% font bytes | Low |
| Code splitting | -60% initial JS | Medium |
| Edge caching | -90% TTFB | Low |
| Service Worker | Instant repeat visits | Medium |
| HTTP/3 + QUIC | Faster on lossy networks | Low (CDN) |
What Works
- Measure real users, not lab tests: Field data from Chrome UX Report reflects actual conditions
- Optimize the critical path: Anything blocking
<head>should be under 50KB total. See server-side rendering. - Self-host fonts and analytics: Third-party connections add DNS + TLS + TCP overhead
- Use
content-visibility: auto: Browsers skip rendering off-screen content - Defer non-critical JavaScript:
deferortype="module"for scripts that aren’t needed immediately
Common Mistakes
- Oversized hero images: A 4MB PNG hero destroys LCP; use responsive images with
srcset - Render-blocking third parties: Google Fonts loaded synchronously delays first paint
- No resource hints:
preload,prefetch, andpreconnectare free performance wins - Hydrating everything: Islands architecture (Astro, Fresh) ships zero JS for static content
- Ignoring mobile: 70% of users are on mobile; test on real devices, not just DevTools
Troubleshooting
- Largest Contentful Paint is high: optimize images, preload critical resources, and reduce server response time.
- JavaScript bundle size grows: analyze the bundle, split code by route, and tree-shake unused dependencies. Lazy-load non-critical components.
- Cache hit rate is low: review cache keys, TTLs, and invalidation patterns.
- Database CPU spikes: find the top queries by execution time and frequency. Add indexes, rewrite queries, or cache results.
- Throughput drops under load: profile for contention, garbage collection, and blocked threads. Scale horizontally only after optimizing the hot path.
Further Reading
- Official documentation: check the current reference for the framework or tool used.
- Related guides: explore the web-performance and performance guides for deeper coverage.
- Complementary patterns: review design patterns applicable to your technology stack.
- Public postmortems: study real incidents from teams that faced similar production issues.
Production Notes
- Deploy gradually using canary or blue-green to catch regressions early.
- Configure alerts for error rate, p99 latency, and failure rate before enabling in production.
- Document the rollback in the runbook; test the procedure in staging at least once per quarter.
- Review structured logs with correlation IDs to trace requests end-to-end during incidents.
Key Takeaways
- Apply web performance optimization when you need a practical solution for your use case.
- Monitor performance after implementation; measure latency, errors, and resource usage before and after.
- Check the Troubleshooting section for common failures; most have documented root causes with fixes.
- Keep dependencies updated and run tests in CI to prevent production regressions.
See Also
- Performance Optimization Guide — detailed strategies for backend and frontend
- SPA Code Splitting — lazy loading in single-page apps
- Caching Strategies — reducing backend load
- Security Headers — headers that also affect performance
- Load Testing — testing performance under load
Last updated: 2026-07-09
Common Production Pitfalls
- Copying the example without adapting it to real data volumes and failure modes.
- Skipping load and error-injection tests before the first production deployment.
- Hard-coding values that should be configurable per environment.
- Forgetting to add logging and monitoring at each step.
- Deploying without a rollback plan or a tested backup strategy.
- Assuming the minimal example will scale without adding caching or batching.
- Not documenting the version and configuration used in production.
- Letting the recipe sit unchanged when dependencies or scale evolve.
Frequently Asked Questions
What's the single biggest performance win?
Image optimization. Images are typically 60-80% of page weight. Use modern formats, responsive sizing, and lazy loading.
Should I use a CDN?
Yes. A CDN reduces TTFB by serving from edge locations close to users. Essential for global audiences.
How do I balance performance with developer experience?
Use frameworks that optimize by default (Astro, SvelteKit, Next.js with App Router). Don't fight the tooling.
Related Resources
Web Performance Optimization Guide
A thorough guide to optimizing web application performance for better Core Web Vitals and user experience.
RecipeSPA Performance: Code Splitting and Lazy Loading
Improve single-page application load times by splitting bundles at route and component level, implementing lazy loading with React.lazy and live imports
DocCapacity Planning Template
A reusable template for planning system capacity, estimating growth, and preventing performance bottlenecks before they happen.
GuideSystem Design Interview Guide: Key Concepts
A practical guide to system design interviews: scalability, databases, caching, load balancing, microservices, and how to structure your answer.
GuideSQL Performance Tuning — Indexes, Queries, and Explain Plans
A practical guide to optimizing SQL queries: indexing strategies, query rewriting, EXPLAIN plan analysis, and common anti-patterns to avoid.
GuideBundle Size Optimization: A Practical Frontend Guide
A hands-on guide to measuring and reducing JavaScript bundle size with tree shaking, code splitting, dependency swaps, compression, and CI budgets.