Overview

Core Web Vitals are Google’s three field-measured performance thresholds: LCP, INP, and CLS. Pass them at the 75th percentile of real users, not on a lab run. They are a ranking signal, not a top-five one; the bigger win is that fast pages convert better. This page lists thresholds, common offenders, and concrete fixes. For the broader pre-publish checklist, see technical; for TTFB, FCP, and the rest of the speed picture, see page-speed.

Know the three metrics and the three buckets

Each metric has a “good”, “needs improvement”, and “poor” band. The “good” threshold is the only one worth chasing.

MetricGoodNeeds improvementPoor
LCP (Largest Contentful Paint)< 2.5 s< 4.0 s>= 4.0 s
INP (Interaction to Next Paint)< 200 ms< 500 ms>= 500 ms
CLS (Cumulative Layout Shift)< 0.1< 0.25>= 0.25

“Pass” means all three in the good band at the 75th percentile of a 28-day field window. One slow metric drops the whole page.

Fix LCP at the source: the hero element

LCP is almost always the hero image, the H1, or a hero video poster. Ship that one element fast.

  • Preload it: <link rel="preload" as="image" href="/hero.avif" fetchpriority="high">. Mirror fetchpriority="high" on the <img>.
  • Serve modern formats (AVIF or WebP) via <picture> with a JPEG fallback. Compress to the smallest visually lossless size; usually 50 to 150 KB for a hero. See image-seo for the full image checklist.
  • Self-host fonts and use font-display: swap. A blocking font swap can push LCP past 2.5 s on slow connections.
  • Put a CDN in front of the origin. cloudflare halves LCP for most international traffic.
  • Render the hero in HTML, not a client-only React tree. See SSR rules in technical.

Fix INP by breaking long JavaScript tasks

INP replaced FID in 2024. It measures the worst interaction on the page, not just the first. Every click, tap, and key press counts.

  • Break tasks over 50 ms with scheduler.yield() or setTimeout(fn, 0). The main thread should never block input longer than a frame.
  • Lazy-load third-party scripts (analytics, chat, A/B testing). Use async or defer; better, gate the load behind interaction.
  • Use content-visibility: auto on off-screen sections so the browser skips layout for what is not visible. See css.
  • Avoid hydrating the whole page on load. Astro islands, React Server Components, and Qwik resumability exist to keep INP under 200 ms by default.

Fix CLS by reserving space

Layout shifts come from elements that arrive after layout. Reserve their boxes first.

  • Every <img> and <iframe> has width and height attributes (or a CSS aspect-ratio). The browser computes the box from the ratio and skips the reflow when the image loads. See html.
  • Fonts use size-adjust, ascent-override, and descent-override in @font-face so the swap from fallback to web font does not nudge text.
  • Reserve a fixed-height container for ad slots and embeds. A late-inserted ad without a reserved slot is the most common CLS killer.
  • Animate transform and opacity only. Animating top, left, width, or height shifts neighbors.

Measure field data, not just lab data

Lab tools (Lighthouse, WebPageTest) are useful for the fix loop. Field data is what Google ranks against.

  • Field source: the CrUX dataset, surfaced in PageSpeed Insights, Search Console’s CWV report, and the public CrUX API.
  • In-app: install the web-vitals JS library and ship metrics to your analytics. Near-real-time, not 28-day-stale.
  • Watch the 75th percentile, not the average. A page can have a 1.8 s median LCP and still fail at p75.

Keep the CDN doing the heavy lifting

A CDN with sane caching halves the work before any frontend change ships. Set far-future Cache-Control on hashed asset URLs, stale-while-revalidate on HTML, Brotli on text. cloudflare turns these on with a handful of toggles. The other half is the page itself: small JS bundles, optimized images, reserved layout. Pair CDN with the content discipline that keeps the page worth ranking.