Core Web Vitals
Core Web Vitals (CWV) are Google's three user-experience metrics, measured from real Chrome users and used as a ranking signal. They're a tiebreaker, not a dominant factor - but they're also a proxy for something that does dominate: whether users stay or bounce.
The three metrics#
| Metric | Measures | Good | Poor |
|---|---|---|---|
| LCP - Largest Contentful Paint | Time until the biggest above-fold element renders | ≤ 2.5s | > 4.0s |
| INP - Interaction to Next Paint | Worst-case responsiveness to clicks/taps/keys | ≤ 200ms | > 500ms |
| CLS - Cumulative Layout Shift | How much the layout jumps around | ≤ 0.1 | > 0.25 |
Thresholds are evaluated at the 75th percentile of real users - your fast dev machine on fiber is not the measurement.
Field data vs. lab data#
- Field data (CrUX - Chrome real-user telemetry) is what ranking uses. See it in PageSpeed Insights and Search Console's Core Web Vitals report. It lags ~28 days.
- Lab data (Lighthouse, local DevTools) is reproducible diagnostics. Use it to find causes, never to declare victory.
npx lighthouse https://example.com --only-categories=performance --viewFixing LCP#
LCP is almost always the hero image or headline block. The usual chain of causes, in fix order:
- Slow server response (TTFB). Cache HTML at the edge; in Next.js prefer static rendering/ISR for content pages over per-request rendering.
- The LCP image isn't prioritized. It must be discoverable in the initial HTML and fetched early:
import Image from "next/image";
<Image
src="/hero.webp"
alt="…"
width={1200}
height={630}
priority // disables lazy-loading, adds preload + fetchpriority=high
/>- Render-blocking CSS/fonts. Inline critical CSS (frameworks do this), use
font-display: swap(next/fonthandles it, self-hosts, and removes the third-party request). - Too much image. Serve responsive sizes and modern formats - again,
next/imagedefaults.
Fixing INP#
INP captures the worst interaction latency - usually long JavaScript tasks blocking the main thread:
- Ship less JS. Audit bundles (
@next/bundle-analyzer), use dynamicimport()for below-fold and on-interaction components, prefer Server Components for non-interactive UI. - Break up long tasks. Yield to the main thread in heavy handlers; move pure computation to a Web Worker.
- Tame third-party scripts - tag managers, chat widgets and analytics are routinely the worst offenders. Load them with
next/scriptstrategy="lazyOnload"or remove them. - Render less per interaction. Virtualize long lists; memoize expensive React subtrees so a keystroke doesn't re-render the world.
Fixing CLS#
Layout shift is reserved-space failure:
- Images/video/embeds: always declare dimensions (
width/heightor CSSaspect-ratio). - Ads and dynamic slots: reserve a fixed container before the content loads.
- Fonts:
font-display: swap+ metric-compatible fallbacks (next/fontautomates the fallback metrics) to stop reflow when webfonts land. - Never insert content above existing content after load - banners and notices animate in from a reserved slot or overlay instead.
A realistic workflow#
1. Search Console → CWV report → which URL groups fail, on which metric
2. PageSpeed Insights on failing URL → field numbers + lab diagnosis
3. Fix per the playbooks above → verify in lab immediately
4. Wait for field data to roll → confirm in GSC (~1 month)Performance work is permanent: add a Lighthouse CI budget to your pipeline so regressions fail the build instead of failing the quarter.
Next: Structured Data - telling machines exactly what your content is.
