Web Vitals Optimization
Optimize Core Web Vitals: fix LCP with preloading and image optimization, eliminate CLS with size reservations, improve INP with task splitting.
Core Web Vitals (CWV) are Google's user-experience metrics that directly affect search rankings. LCP (Largest Contentful Paint) measures loading speed, CLS (Cumulative Layout Shift) measures visual stability, and INP (Interaction to Next Paint) measures responsiveness. All three are measured in the field (real users) via Chrome UX Report.
Measure before fixing - use PageSpeed Insights or CrUX data, not just Lighthouse. Fix LCP first (biggest ranking impact), then INP, then CLS. Every optimization should be verified in field data, not just lab.
Workflow
- Diagnose with real field dataLab tools (Lighthouse) simulate one device; field data (CrUX) reflects your actual users across all devices and connections.
- Fix LCP (Largest Contentful Paint)LCP > 2.5s is "Poor" - typically caused by slow server response, render-blocking resources, or unoptimized images.
- Fix CLS (Cumulative Layout Shift)CLS > 0.1 is "Poor" - shifts happen when images/embeds load without size reservations, or ads inject without reserved space.
- Fix INP (Interaction to Next Paint)INP > 200ms is "Poor" - caused by long JavaScript tasks that block the main thread from responding to user input.
- Monitor in CI and productionRegressions revert your work - automated performance budgets catch them before shipping.
Output Format
Field data from CrUX/PSI as baseline, `fetchpriority="high"` + preload for LCP hero image, `width`/`height` on all images for CLS, `scheduler.yield()` for INP, Lighthouse CI assertions in GitHub Actions.
Examples
LCP image tag: `<img src="/hero.webp" srcset="/hero-480.webp 480w, /hero-960.webp 960w, /hero-1440.webp 1440w" sizes="(max-width: 480px) 480px, (max-width: 960px) 960px, 1440px" width="1440" height="720" fetchpriority="high" loading="eager" decoding="async" alt="Hero image">`Send Web Vitals to analytics: `import { onLCP, onINP, onCLS } from "web-vitals/attribution"; function sendToAnalytics({ name, value, id, attribution }) { fetch("/api/vitals", { method: "POST", body: JSON.stringify({ name, value: Math.round(name === "CLS" ? value * 1000 : value), id, attribution }), keepalive: true }) } onLCP(sendToAnalytics); onINP(sendToAnalytics); onCLS(sendToAnalytics)`React concurrent: `import { startTransition, useTransition } from "react"; const [isPending, startTransition] = useTransition(); function handleInput(e: ChangeEvent<HTMLInputElement>) { setInputValue(e.target.value); // urgent update startTransition(() => { setFilteredItems(heavyFilter(items, e.target.value)) // non-urgent }) }`Edge Cases & Gotchas
- LCP text vs image: if the LCP element is text (h1, paragraph), the fix is TTFB + render-blocking JS/CSS, not image optimization. Check what element is flagged in the LCP breakdown in PageSpeed Insights
- CLS from ads: ad networks inject content dynamically. Reserve space with a fixed-height container matching the ad spec. Use `min-height` (not `height`) so the container can grow without shifting content above it
- INP on mobile: mobile devices have slower CPUs - an interaction taking 100ms on desktop may take 400ms on a mid-range Android. Test INP on real devices or Moto G Power emulation in DevTools
When to Break These Rules
- For internal tools, admin dashboards, or apps where users are on fast corporate networks/devices, CWV optimization has low ROI. Prioritize CWV for public marketing pages, e-commerce, and content sites where search ranking and bounce rate matter.
Reference Files
Web Vitals: https://web.dev/vitals/LCP optimization: https://web.dev/articles/optimize-lcpINP optimization: https://web.dev/articles/optimize-inpCLS optimization: https://web.dev/articles/optimize-clsweb-vitals library: https://github.com/GoogleChrome/web-vitals