Web Performance
Two distinct problems: load fast (get to useful content quickly) and stay responsive and smooth at the active display refresh rate. Measure first—optimize the dominant cost, not what's easy.
Core Web Vitals (the targets)
- LCP (Largest Contentful Paint) ≤ 2.5s — largest element visible. Fix: optimize the hero image/font, preconnect, reduce render-blocking CSS/JS, server response time.
- INP (Interaction to Next Paint) ≤ 200ms — input delay + handler work + presentation delay. Fix the measured phase rather than assuming every INP problem is a long task.
- CLS (Cumulative Layout Shift) ≤ 0.1 — visual stability. Fix: set
width/height/aspect-ratio on media, reserve space for dynamic content, preload fonts.
Evaluate these thresholds at the 75th percentile of field visits, segmented by mobile and desktop. A fast Lighthouse run does not prove healthy field Core Web Vitals.
The frame budget
At 60Hz, one frame is ~16.7ms; at 120Hz it is ~8.3ms. The actual budget follows the display and must leave browser/OS headroom for JavaScript, style, layout, paint, and composite work.
JS → Style → Layout → Paint → Composite
(transform/opacity may avoid earlier stages; verify)
Load fast
- Ship less JS: code-split by route, lazy-load below-the-fold and heavy libs, tree-shake, audit the bundle.
- Don't block the critical path:
defer/module scripts, inline critical CSS, lazy non-critical CSS.
- Prioritize the measured LCP resource (
fetchpriority="high" where useful). Preload only a few critical, late-discovered resources; unnecessary preloads compete with the real critical path.
- Images: right-sized, modern formats (AVIF/WebP),
srcset/<picture>, loading="lazy" off-screen.
- Fonts: subset and self-host when justified; choose
font-display: swap, fallback, or optional by product needs, and align fallback metrics to limit layout shift. Preload only a truly critical font.
- Stream/SSR where possible so first paint has content.
Stay smooth at the active refresh rate
- Prefer
transform/opacity for hot motion, then verify the trace; compositor promotion is not guaranteed. Avoid width/top/left in frame-by-frame loops.
- Drive visual updates with
requestAnimationFrame, never setInterval/setTimeout. Use the rAF timestamp / a fixed timestep; don't assume 60Hz.
- Avoid layout thrash: batch DOM reads, then writes. Reading
offsetWidth/getBoundingClientRect after a write forces synchronous layout.
- If profiling shows GC pressure in a hot loop, reduce transient allocations; object pools, typed arrays, and manual reuse are targeted fixes, not universal defaults.
Keep interactions responsive
- Break long tasks (>50ms) into chunks; yield between them:
- Feature-detect
scheduler.yield() / scheduler.postTask() and retain a timer-based fallback where support is missing.
- Use
requestIdleCallback only for deferrable work; required work needs a supported fallback or a timeout because idle callbacks may be delayed indefinitely.
- Move heavy compute to a Web Worker (parsing, image/audio, physics, search indexing). Use
OffscreenCanvas to render in a worker.
- Consider WebAssembly for measured CPU kernels (see
wasm-rust). Preserve an existing GPU renderer when investigating its cost; evaluate a renderer change only when the bottleneck and support requirements justify it.
- Debounce/throttle scroll/resize/pointer handlers; use passive event listeners.
Game-loop pattern
let last = performance.now();
const maxDt = 0.05;
function loop(now) {
const dt = Math.min((now - last) / 1000, maxDt);
last = now;
update(dt); // use a bounded fixed-step accumulator for physics
render(); // mutate transforms / draw to canvas
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
document.addEventListener('visibilitychange', () => {
if (!document.hidden) last = performance.now(); // rAF pauses in hidden tabs
});
Measure (don't guess)
- PageSpeed Insights combines CrUX field data (when available) with a Lighthouse lab run; read them separately.
- Performance panel: record an interaction, find long tasks, forced reflows, dropped frames; check the FPS/GPU track.
- web-vitals library for real-user (field) INP/LCP/CLS.
- DevTools Performance Monitor for live CPU/DOM/listener counts.
- For GPU paths, measure representative integrated/discrete GPUs, DPR, VRAM/texture budgets, power modes, upload/readback cost, and context/device loss—not just desktop FPS.
Triage checklist
Reference
- web.dev: Core Web Vitals, "Optimize long tasks", "Rendering performance", RAIL.
GoogleChrome/modern-web-guidance-src (performance guides); Addy Osmani's web performance writing.
- MDN:
requestAnimationFrame, OffscreenCanvas, Performance API.
- Chrome for Developers:
scheduler.postTask/scheduler.yield, Web Workers.
1---2name: web-performance3description: Diagnoses browser loading, Core Web Vitals, main-thread work, and layout/paint bottlenecks. Use for a slow page or measured browser-performance issue; isolated React renders and GPU passes have specialist workflows.4license: MIT5---67# Web Performance89Two distinct problems: **load fast** (get to useful content quickly) and **stay responsive and smooth** at the active display refresh rate. Measure first—optimize the dominant cost, not what's easy.1011## Core Web Vitals (the targets)1213- **LCP** (Largest Contentful Paint) ≤ 2.5s — largest element visible. Fix: optimize the hero image/font, preconnect, reduce render-blocking CSS/JS, server response time.14- **INP** (Interaction to Next Paint) ≤ 200ms — input delay + handler work + presentation delay. Fix the measured phase rather than assuming every INP problem is a long task.15- **CLS** (Cumulative Layout Shift) ≤ 0.1 — visual stability. Fix: set `width`/`height`/`aspect-ratio` on media, reserve space for dynamic content, preload fonts.1617Evaluate these thresholds at the **75th percentile of field visits**, segmented by mobile and desktop. A fast Lighthouse run does not prove healthy field Core Web Vitals.1819## The frame budget2021At 60Hz, one frame is **~16.7ms**; at 120Hz it is **~8.3ms**. The actual budget follows the display and must leave browser/OS headroom for JavaScript, style, layout, paint, and composite work.2223```24JS → Style → Layout → Paint → Composite25 (transform/opacity may avoid earlier stages; verify)26```2728## Load fast2930- Ship less JS: code-split by route, lazy-load below-the-fold and heavy libs, tree-shake, audit the bundle.31- Don't block the critical path: `defer`/`module` scripts, inline critical CSS, lazy non-critical CSS.32- Prioritize the measured LCP resource (`fetchpriority="high"` where useful). Preload only a few critical, late-discovered resources; unnecessary preloads compete with the real critical path.33- Images: right-sized, modern formats (AVIF/WebP), `srcset`/`<picture>`, `loading="lazy"` off-screen.34- Fonts: subset and self-host when justified; choose `font-display: swap`, `fallback`, or `optional` by product needs, and align fallback metrics to limit layout shift. Preload only a truly critical font.35- Stream/SSR where possible so first paint has content.3637## Stay smooth at the active refresh rate3839- Prefer **`transform`/`opacity`** for hot motion, then verify the trace; compositor promotion is not guaranteed. Avoid width/top/left in frame-by-frame loops.40- Drive visual updates with **`requestAnimationFrame`**, never `setInterval`/`setTimeout`. Use the rAF timestamp / a fixed timestep; don't assume 60Hz.41- **Avoid layout thrash**: batch DOM reads, then writes. Reading `offsetWidth`/`getBoundingClientRect` after a write forces synchronous layout.42- If profiling shows GC pressure in a hot loop, reduce transient allocations; object pools, typed arrays, and manual reuse are targeted fixes, not universal defaults.4344## Keep interactions responsive4546- Break long tasks (>50ms) into chunks; **yield** between them:47 - Feature-detect `scheduler.yield()` / `scheduler.postTask()` and retain a timer-based fallback where support is missing.48 - Use `requestIdleCallback` only for deferrable work; required work needs a supported fallback or a timeout because idle callbacks may be delayed indefinitely.49- Move heavy compute to a **Web Worker** (parsing, image/audio, physics, search indexing). Use `OffscreenCanvas` to render in a worker.50- Consider **WebAssembly** for measured CPU kernels (see `wasm-rust`). Preserve an existing GPU renderer when investigating its cost; evaluate a renderer change only when the bottleneck and support requirements justify it.51- Debounce/throttle scroll/resize/pointer handlers; use passive event listeners.5253## Game-loop pattern5455```js56let last = performance.now();57const maxDt = 0.05;58function loop(now) {59 const dt = Math.min((now - last) / 1000, maxDt);60 last = now;61 update(dt); // use a bounded fixed-step accumulator for physics62 render(); // mutate transforms / draw to canvas63 requestAnimationFrame(loop);64}65requestAnimationFrame(loop);6667document.addEventListener('visibilitychange', () => {68 if (!document.hidden) last = performance.now(); // rAF pauses in hidden tabs69});70```7172## Measure (don't guess)7374- **PageSpeed Insights** combines CrUX field data (when available) with a Lighthouse lab run; read them separately.75- **Performance panel**: record an interaction, find long tasks, forced reflows, dropped frames; check the FPS/GPU track.76- **web-vitals** library for real-user (field) INP/LCP/CLS.77- DevTools Performance Monitor for live CPU/DOM/listener counts.78- For GPU paths, measure representative integrated/discrete GPUs, DPR, VRAM/texture budgets, power modes, upload/readback cost, and context/device loss—not just desktop FPS.7980## Triage checklist8182- [ ] Measured; dominant cost identified (load vs runtime).83- [ ] The LCP resource is discovered early and appropriately prioritized; preload only when measurement shows critical late discovery.84- [ ] Hot animations prefer transform/opacity and are verified in a trace; updates use rAF.85- [ ] No layout thrash (reads batched before writes).86- [ ] Long tasks split/yielded; heavy compute in a worker/WASM.87- [ ] Media sized to prevent CLS.8889## Reference9091- web.dev: Core Web Vitals, "Optimize long tasks", "Rendering performance", RAIL.92- `GoogleChrome/modern-web-guidance-src` (performance guides); Addy Osmani's web performance writing.93- MDN: `requestAnimationFrame`, `OffscreenCanvas`, Performance API.94- Chrome for Developers: `scheduler.postTask`/`scheduler.yield`, Web Workers.