seo101

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#

MetricMeasuresGoodPoor
LCP - Largest Contentful PaintTime until the biggest above-fold element renders≤ 2.5s> 4.0s
INP - Interaction to Next PaintWorst-case responsiveness to clicks/taps/keys≤ 200ms> 500ms
CLS - Cumulative Layout ShiftHow 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.
Quick lab check
npx lighthouse https://example.com --only-categories=performance --view

Fixing LCP#

LCP is almost always the hero image or headline block. The usual chain of causes, in fix order:

  1. Slow server response (TTFB). Cache HTML at the edge; in Next.js prefer static rendering/ISR for content pages over per-request rendering.
  2. The LCP image isn't prioritized. It must be discoverable in the initial HTML and fetched early:
Hero image with priority
import Image from "next/image";
 
<Image
  src="/hero.webp"
  alt="…"
  width={1200}
  height={630}
  priority // disables lazy-loading, adds preload + fetchpriority=high
/>
  1. Render-blocking CSS/fonts. Inline critical CSS (frameworks do this), use font-display: swap (next/font handles it, self-hosts, and removes the third-party request).
  2. Too much image. Serve responsive sizes and modern formats - again, next/image defaults.

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 dynamic import() 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/script strategy="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/height or CSS aspect-ratio).
  • Ads and dynamic slots: reserve a fixed container before the content loads.
  • Fonts: font-display: swap + metric-compatible fallbacks (next/font automates 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.