Web Performance Optimization
Optimize web application performance using Core Web Vitals as a framework. Covers LCP, FID, CLS optimization, bundle analysis, lazy loading, image optimization, and the performance budget that keeps your app fast as it grows.
Performance is a feature. A 100ms increase in page load time reduces conversion rates by 1%. A page that takes 3 seconds to load loses 53% of mobile visitors. Performance optimization is not a nice-to-have — it directly affects revenue, user satisfaction, and search ranking.
Core Web Vitals
Google’s Core Web Vitals are the standard for measuring user-perceived performance:
| Metric | Good | Needs Work | Poor | Measures |
|---|---|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | 2.5-4s | > 4s | Loading speed |
| FID (First Input Delay) | < 100ms | 100-300ms | > 300ms | Interactivity |
| CLS (Cumulative Layout Shift) | < 0.1 | 0.1-0.25 | > 0.25 | Visual stability |
| INP (Interaction to Next Paint) | < 200ms | 200-500ms | > 500ms | Responsiveness |
LCP Optimization
Critical Rendering Path
<!-- Preload the LCP image -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<!-- Inline critical CSS -->
<style>
/* Only styles needed for above-the-fold content */
.hero { min-height: 60vh; background: var(--bg); }
.hero img { width: 100%; height: auto; }
</style>
<!-- Defer non-critical CSS -->
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
Image Optimization
<!-- Modern format with fallbacks -->
<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" alt="Hero" width="1200" height="600"
loading="eager" fetchpriority="high" decoding="async">
</picture>
<!-- Responsive images -->
<img srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 1200px"
src="hero-800.webp" alt="Hero" width="1200" height="600">
Font Optimization
/* Use font-display: swap to prevent invisible text */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
font-weight: 400;
}
<!-- Preload critical fonts -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
CLS Optimization
Reserve Space for Dynamic Content
/* Always set dimensions on images */
img { width: 100%; height: auto; aspect-ratio: 16 / 9; }
/* Reserve space for ads */
.ad-slot { min-height: 250px; }
/* Contain layout shifts from web fonts */
.text-content { font-size-adjust: 0.5; }
Avoid Layout-Shifting Patterns
// BAD: Injecting content above existing content
document.querySelector('.header').insertAdjacentHTML('afterbegin', banner);
// GOOD: Reserve space, then populate
// CSS: .banner-slot { min-height: 60px; }
document.querySelector('.banner-slot').innerHTML = banner;
Bundle Optimization
Code Splitting
// Route-based splitting (React)
const OrderPage = React.lazy(() => import('./pages/OrderPage'));
const AdminPage = React.lazy(() => import('./pages/AdminPage'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/orders" element={<OrderPage />} />
<Route path="/admin" element={<AdminPage />} />
</Routes>
</Suspense>
);
}
Tree Shaking
// BAD: Import entire library
import _ from 'lodash';
_.debounce(fn, 300);
// GOOD: Import only what you need
import debounce from 'lodash/debounce';
debounce(fn, 300);
Bundle Analysis
# Webpack
npx webpack-bundle-analyzer stats.json
# Vite
npx vite-bundle-visualizer
# Next.js
ANALYZE=true next build
Performance Budget
performance_budget:
javascript:
total: 200KB gzipped
per_route: 100KB gzipped
css:
total: 50KB gzipped
images:
per_image: 200KB
hero: 100KB
fonts:
total: 100KB
metrics:
lcp: 2.5s
fid: 100ms
cls: 0.1
tti: 3.5s
Enforce in CI:
# bundlesize in package.json
"bundlesize": [
{ "path": "dist/js/*.js", "maxSize": "200 kB" },
{ "path": "dist/css/*.css", "maxSize": "50 kB" }
]
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| No performance budget | Bundle grows unchecked | Set and enforce budgets in CI |
| Loading everything upfront | Slow initial load | Code splitting + lazy loading |
| Unoptimized images | LCP > 4 seconds | WebP/AVIF, responsive images, CDN |
| Render-blocking resources | White screen for seconds | Async CSS, deferred JS |
| No performance monitoring | Regression goes unnoticed | RUM + synthetic monitoring |
Performance optimization is a continuous practice, not a one-time project. Without budgets and monitoring, every feature addition degrades performance until users notice.