Actionable techniques to make your web app faster — Core Web Vitals, lazy loading, bundle optimization, caching, and rendering strategies.
Google uses Core Web Vitals as ranking signals. The three key metrics:
| Metric | Measures | Good Target |
|---|---|---|
| LCP (Largest Contentful Paint) | Loading performance | < 2.5s |
| INP (Interaction to Next Paint) | Responsiveness | < 200ms |
| CLS (Cumulative Layout Shift) | Visual stability | < 0.1 |
Measure with web-vitals library:
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
# Analyze your bundle
npx vite-bundle-visualizer # Vite
npx next/bundle-analyzer # Next.js
Tree-shaking: Only import what you use:
// Bad — imports entire library
import _ from 'lodash';
const result = _.groupBy(data, 'category');
// Good — only the function you need
import groupBy from 'lodash/groupBy';
const result = groupBy(data, 'category');
Code splitting: Load code only when needed:
// React lazy loading
const HeavyChart = lazy(() => import('./HeavyChart'));
<Suspense fallback={<Spinner />}>
<HeavyChart />
</Suspense>
Images are often 60–80% of a page's weight.
<!-- Use modern formats -->
<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" />
</picture>
<!-- Always specify width and height to prevent CLS -->
<!-- Use loading=lazy for below-the-fold images -->
<img src="photo.webp" loading="lazy" width="800" height="600" alt="..." />
// Intersection Observer for deferred loading
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
# Cache-Control headers
# Static assets (hashed filenames) — cache forever
Cache-Control: public, max-age=31536000, immutable
# HTML pages — revalidate on every request
Cache-Control: no-cache
# API responses — short cache
Cache-Control: public, max-age=60, stale-while-revalidate=300
| Pattern | When to use | LCP | Personalization |
|---|---|---|---|
| SSR (Server-Side Rendering) | Dynamic, SEO-critical | ✅ | ✅ |
| SSG (Static Site Generation) | Rarely changing content | ✅✅ | ❌ |
| CSR (Client-Side Rendering) | Dashboards, auth-only pages | ❌ | ✅ |
| ISR (Incremental Static Regen) | High traffic + fresh data | ✅✅ | Partial |
The fastest code is code that never runs — aggressive caching, precomputed HTML, and minimal client-side JS remain the most impactful optimizations.