Web Performance Optimization Cheat Sheet
Covers measuring Core Web Vitals, lazy loading and code splitting, caching headers and resource hints, and key optimization techniques.
Measuring Core Web Vitals
Tracking loading, responsiveness, and stability metrics.
// Using the web-vitals libraryimport { onLCP, onINP, onCLS } from 'web-vitals';onLCP(console.log); // Largest Contentful Paint - loadingonINP(console.log); // Interaction to Next Paint - responsivenessonCLS(console.log); // Cumulative Layout Shift - visual stability// Programmatic timing via PerformanceObservernew PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log(entry.name, entry.startTime); }}).observe({ type: 'largest-contentful-paint', buffered: true });
Lazy Loading & Code Splitting
Deferring work until it's actually needed.
// Native lazy-loaded image// <img src="hero.jpg" loading="lazy" alt="Hero" width="800" height="400">// Dynamic import for route-level code splittingconst Chart = React.lazy(() => import('./Chart'));function Dashboard() { return ( <React.Suspense fallback={<Spinner />}> <Chart /> </React.Suspense> );}
Caching & Resource Hints
HTTP caching and hints that speed up resource discovery.
<!-- Preload the LCP image so the browser fetches it immediately --><link rel="preload" as="image" href="/hero.webp"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link rel="dns-prefetch" href="https://api.example.com"><!-- Cache-Control for static, fingerprinted assets --><!-- Cache-Control: public, max-age=31536000, immutable --><!-- HTML - must revalidate since it references fingerprinted assets --><!-- Cache-Control: no-cache -->
Optimization Techniques
High-impact levers for reducing load time.
- Code splitting- ship only the JS needed for the current route or view
- Tree shaking- remove unused exports from the final bundle
- Image formats (WebP/AVIF)- smaller file sizes than JPEG/PNG at similar visual quality
- Critical CSS- inline the above-the-fold CSS and defer the rest
- CDN- serve static assets from edge locations closer to the user
- Compression (Brotli/gzip)- reduces transferred bytes for text-based assets
- Explicit width/height- reserves layout space up front to prevent CLS from late-loading media
fetchpriority & Resource Priority Hints
Explicitly telling the browser which resources matter most for LCP.
<!-- Boost the LCP image above the browser's default heuristic priority --><img src="/hero.webp" fetchpriority="high" alt="Hero" width="1200" height="600" /><!-- Deprioritize offscreen/below-the-fold images competing for bandwidth --><img src="/related-1.webp" loading="lazy" fetchpriority="low" alt="Related" /><!-- Works on preload and fetch() too --><link rel="preload" as="image" href="/hero.webp" fetchpriority="high" /><script> fetch('/api/critical-data', { priority: 'high' });</script>
Optimizing INP with scheduler.yield
Breaking up long tasks so input handling isn't blocked, improving Interaction to Next Paint.
// Long synchronous work blocks the main thread and delays the next paint// after a click/keypress - split it into yieldable chunks.async function processLargeList(items) { for (let i = 0; i < items.length; i++) { doExpensiveWork(items[i]); if (i % 50 === 0) { // Yield back to the browser so pending input/paint can run if ('scheduler' in window && 'yield' in scheduler) { await scheduler.yield(); } else { await new Promise((resolve) => setTimeout(resolve, 0)); } } }}// Detect long tasks in production via PerformanceObservernew PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.duration > 50) console.warn('Long task:', entry.duration); }}).observe({ type: 'longtask', buffered: true });
Stale-While-Revalidate Service Worker Strategy
Serving cached responses instantly while refreshing them in the background.
self.addEventListener('fetch', (event) => { if (event.request.destination !== 'script' && event.request.destination !== 'style') return; event.respondWith( caches.open('assets-v1').then(async (cache) => { const cached = await cache.match(event.request); const networkFetch = fetch(event.request).then((response) => { cache.put(event.request, response.clone()); return response; }); // Return cached immediately if present; otherwise wait on the network return cached || networkFetch; }) );});
Advanced Performance Metrics
Metrics beyond the three Core Web Vitals that diagnose specific bottlenecks.
- TBT (Total Blocking Time)- lab proxy for INP; sums the blocking portion of every long task between FCP and TTI
- TTI (Time to Interactive)- when the page is visually rendered AND reliably responds to input within 50ms
- Speed Index- how quickly visible content is painted during load, expressed as an area-under-curve score
- INP breakdown- input delay (main thread busy) + processing time (event handler work) + presentation delay (next frame paint)
- TTFB (Time to First Byte)- server/network latency before any HTML arrives; a floor under every other metric
- Long Animation Frames (LoAF)- newer API that attributes janky frames to the specific script/task that caused them, successor to Long Tasks for INP debugging
Connection Setup: preconnect & Multiplexing
Cutting handshake latency for critical third-party origins under HTTP/2+.
<!-- preconnect does DNS + TCP + TLS ahead of time - use sparingly (3-4 max), each open connection has real cost --><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /><link rel="preconnect" href="https://api.example.com" /><!-- Under HTTP/2 or HTTP/3, avoid legacy domain sharding (splitting assets across subdomains) - it defeats connection multiplexing and forces extra handshakes instead of reusing one connection --><!-- Prefer same-origin or a single CDN origin so requests share one multiplexed connection rather than each needing preconnect -->
Optimize for Largest Contentful Paint first: find your LCP element in Chrome DevTools' Performance panel (usually a hero image or heading) and preload just that asset - it often yields a bigger score improvement than any amount of JS micro-optimization.