Core Web Vitals: a complete overview

What LCP, INP and CLS measure, the thresholds, twelve causes of poor metrics with fixes, field and lab data, four examples with numbers before and after, mistakes and rules.

Stack and technologies Updated

In short

Core Web Vitals are three Google metrics that describe how a page feels to a real visitor: LCP — how fast the main content appears, INP — how fast the page responds to clicks and taps, CLS — how much the layout jumps. A page passes when 75% of real visits meet the thresholds: LCP up to 2.5 seconds, INP up to 200 milliseconds, CLS up to 0.1. Google takes them into account in search as part of page experience, but their main value is elsewhere: a fast, stable page keeps people and sells more. Most problems come from a few causes — heavy images, fonts, scripts and a slow server.

Core Web Vitals at a glance

The main facts in one table — what is measured, how a page is judged and where the data comes from.

What it is
Google metrics of real user experience: loading, response, stability
Metrics
LCP, INP, CLS; INP replaced FID in March 2024
How a page is judged
The 75th percentile of real visits over 28 days, separately for phones and computers
Data source
The Chrome User Experience Report (CrUX) — anonymous data from Chrome users
Where to look
PageSpeed Insights, Search Console, Chrome DevTools
In search
Part of page experience signals since 2021; relevance of the content still comes first
Own measurement
The web-vitals library from Google — the same numbers from your own visitors

The metrics and their thresholds

Three Core Web Vitals and two supporting metrics that explain them. The thresholds are taken from Google’s web-vitals library.

MetricWhat it measuresGoodNeeds workPoor
LCP when the largest element of the first screen appears up to 2.5 s 2.5–4 s over 4 s
INP how long a click or tap waits for a visible response up to 200 ms 200–500 ms over 500 ms
CLS how much the layout shifts by itself up to 0.1 0.1–0.25 over 0.25
FCP when anything appears at all — supporting metric up to 1.8 s 1.8–3 s over 3 s
TTFB when the server starts to answer — supporting metric up to 0.8 s 0.8–1.8 s over 1.8 s

What spoils the metrics and how to fix it

Twelve common causes with the metric they hit and the fix.

CauseMetricFix
A heavy picture on the first screen LCP AVIF or WebP, srcset, fetchpriority="high"
The first-screen picture loads lazily LCP remove loading="lazy" from it
Slow server response LCP cache, fewer queries, a CDN
Fonts from a third-party service LCP own woff2 files with a subset
Content drawn by JavaScript LCP render the HTML on the server
Long tasks in click handlers INP paint the reaction first, work after
Heavy third-party scripts INP load later or remove
A huge DOM INP fewer elements, content-visibility
Pictures without sizes CLS width and height in the HTML
Banners and widgets loading late CLS a reserved place with min-height
A web font replacing the fallback CLS a fitted fallback or font-display: optional
Animations of top and height CLS animate transform and opacity

Field data and lab data

  1. Field data

    Real visits from CrUX — this is what Google uses in search and what Search Console shows.

  2. Lab data

    One run of Lighthouse on an emulated phone — good for finding causes, not for the final verdict.

  3. Why they differ

    Real visitors have different phones, networks and pages; the lab has one device and one load.

  4. INP in the lab

    Lighthouse does not click, so it shows Total Blocking Time — a hint, not INP itself.

  5. 28 days of delay

    Field data is collected over 28 days — a fix shows fully in the reports about a month later.

  6. Small sites

    With little traffic CrUX has no data — then your own measurement with web-vitals helps.

Measuring and fixing: 4 examples

Your own measurement and one fix for each metric. Each was checked in a browser on a test stand, with the numbers before and after.

Your own field data

After a click and closing the tab, the server received all three metrics with their ratings.

vitals.js
// Metrics of real visitors: measured in their browsers, collected on our server
import { onCLS, onINP, onLCP } from 'web-vitals';

function send(metric) {
  const body = JSON.stringify({
    name: metric.name,     // LCP, INP or CLS
    value: metric.value,   // milliseconds; for CLS a score without units
    rating: metric.rating, // good, needs-improvement or poor
    page: location.pathname,
  });
  // sendBeacon survives closing the tab — that is when INP and CLS become final
  if (!navigator.sendBeacon('/api/vitals', body)) {
    fetch('/api/vitals', { method: 'POST', body, keepalive: true });
  }
}

onLCP(send);
onINP(send);
onCLS(send);

LCP: the first-screen picture

The hero picture is requested with High priority and becomes the LCP element; the lower one is requested only after scrolling.

index.html
<!-- The first-screen picture: the browser loads it first and never lazily -->
<img src="/img/hero-1200.avif"
     srcset="/img/hero-800.avif 800w, /img/hero-1200.avif 1200w"
     sizes="(max-width: 800px) 100vw, 1200px"
     width="1200" height="630" alt="Oak table in a bright kitchen"
     fetchpriority="high">

<!-- Pictures further down: loaded only when the person scrolls to them -->
<img src="/img/table-top.webp" width="600" height="400"
     alt="Oak table top, close-up" loading="lazy" decoding="async">

CLS: reserved space

The same page with a late picture, a player and a promo block: CLS 0.326 without these rules, 0 with them.

stable.css
/* Space is reserved before the content arrives — nothing below jumps */
img,
video {
  max-width: 100%;
  height: auto;          /* proportions come from width and height in the HTML */
}

.embed {
  aspect-ratio: 16 / 9;  /* video players, maps and other iframes */
}
.embed > iframe {
  width: 100%;
  height: 100%;
  border: 0;
}

.promo-slot {
  min-height: 250px;     /* a block that a script fills in later */
}

INP: response before work

A handler with 300 ms of work: the click waited 304 ms for a response without this, and 16 ms with it.

filter.js
// Let the browser paint the reaction before the heavy work —
// the click gets a visible response at once, and that is what INP measures
const afterPaint = () =>
  new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve, 0)));

button.addEventListener('click', async () => {
  button.classList.add('is-loading'); // painted right away
  await afterPaint();
  const items = filterCatalogue();    // the heavy part runs after the paint
  render(items);
  button.classList.remove('is-loading');
});

Common mistakes with Core Web Vitals

  1. Chasing 100 in Lighthouse

    Search uses field data; a perfect lab score with poor field data changes nothing.

  2. Testing on a fast computer

    Most visits come from mid-range phones on a mobile network.

  3. Lazy loading everything

    loading="lazy" on the first-screen picture delays LCP.

  4. Checking only the home page

    Product pages and articles bring most of the traffic and usually suffer more.

  5. Scripts of every service

    Chats, pixels and widgets add up and spoil INP more than your own code.

  6. Waiting for results the next day

    The field data catches up over 28 days — your own measurement shows the effect at once.

7 rules for good Core Web Vitals

  1. 01

    A budget before design

    The target numbers are agreed before the first layout — then the design does not fight them.

  2. 02

    HTML from the server

    The main content comes in the first response, not after scripts.

  3. 03

    One priority picture

    fetchpriority="high" only on the first-screen picture, loading="lazy" on the rest.

  4. 04

    Own fonts

    woff2 with only the needed characters, from your own server.

  5. 05

    Sizes for everything that loads

    Pictures, video, iframes and widgets get their place before they arrive.

  6. 06

    Third-party scripts on a list

    Each one is justified and loads after the main content.

  7. 07

    Own measurement in production

    web-vitals sends the numbers from real visitors — problems are visible before the reports.

Questions about Core Web Vitals

What are Core Web Vitals?

Three Google metrics of real user experience: LCP for loading, INP for response, CLS for stability.

Do they affect search rankings?

Yes, as part of page experience, but relevance of the content matters more; between equal pages the faster one wins.

What happened to FID?

In March 2024 INP replaced it: FID measured only the first click, INP measures all of them.

Why is PageSpeed Insights different every time?

The lab part is one run with network and server noise; the field part at the top is stable.

Where do I see my site’s data?

In Search Console, the Core Web Vitals report, and in PageSpeed Insights for a specific page.

My site has no field data. Why?

CrUX shows only sites and pages with enough visits; own measurement with web-vitals fills the gap.

Is a CMS or a builder a problem?

Often yes: themes and plugins bring scripts and styles for every page whether they are needed or not.

Online form

Speed up
the site

I bring Core Web Vitals into the green zone on real phones: images, fonts, scripts and the server. On new projects the metric budget is agreed before design starts. Tell me about the site — I answer within one working day.

Or write to [email protected]