---
title: "Core Web Vitals"
slug: "core-web-vitals"
category: "seo"
tags: ["seo", "performance", "core-web-vitals", "lcp", "inp", "cls"]
status: "stable"
last_updated: 2026-05-14
summary: "LCP under 2.5s, INP under 200ms, CLS under 0.1. The three Core Web Vitals and the concrete techniques that hit each target."
related: ["[[seo/technical]]", "[[seo/content]]", "[[seo/page-speed]]", "[[seo/image-seo]]", "[[seo/mobile-first]]", "[[frontend/html]]", "[[frontend/css]]", "[[ops/cloudflare]]", "[[seo/glossary]]", "[[seo/audit-checklist]]"]
---

## 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 [[seo/technical]]; for TTFB, FCP, and the rest of the speed picture, see [[seo/page-speed]]; for the end-to-end optimization walkthrough, see [[howto/optimize-core-web-vitals]].

## 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.

| Metric | Good | Needs improvement | Poor |
| --- | --- | --- | --- |
| [[glossary/lcp\|LCP]] (Largest Contentful Paint) | < 2.5 s | < 4.0 s | >= 4.0 s |
| [[glossary/inp\|INP]] (Interaction to Next Paint) | < 200 ms | < 500 ms | >= 500 ms |
| [[glossary/cls\|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 [[seo/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. [[ops/cloudflare]] halves LCP for most international traffic.
- Render the hero in HTML, not a client-only React tree. See SSR rules in [[seo/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 [[frontend/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 [[frontend/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`](https://github.com/GoogleChrome/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. [[ops/cloudflare-cache-rules|Cloudflare Cache Rules]] turn these on with a handful of toggles; see [[ops/cloudflare]] for the broader setup. The other half is the page itself: small JS bundles, optimized images, reserved layout. Pair CDN with the [[seo/content]] discipline that keeps the page worth ranking.

## Related

- [[seo/technical]]
- [[seo/content]]
- [[frontend/html]]
- [[frontend/css]]
- [[ops/cloudflare]]
- [[seo/glossary]]
- [[seo/audit-checklist]]
