Skip to content
Back to the blog

Core Web Vitals: how to measure them and what to fix first

What LCP, INP and CLS measure, why Lighthouse and Search Console disagree, and in what order to fix each metric, according to Google's official documentation.

A site can score well in Lighthouse and still fail Core Web Vitals in Search Console, or the other way around. It is not a bug: Lighthouse loads your page once, on a simulated device; Search Console summarizes what your real visitors experienced over the last few weeks.

Passing is not trivial. According to Chrome's data (CrUX) for September 2026, 55.9% of the 18.4 million origins measured had good Core Web Vitals. LCP fails most often: 68.3% were "good", versus 81.7% for CLS and 85.4% for INP.

This guide explains what they measure, how to measure them, how much they weigh in Search and what to fix first, based on Google and Chrome official documentation reviewed in November 2026.

The three metrics and their thresholds

Metric

What it measures

Good

Needs improvement

Poor

LCP (Largest Contentful Paint)

When the largest visible element is painted: image, video or text block

up to 2.5 s

up to 4 s

over 4 s

INP (Interaction to Next Paint)

How long the page takes to respond visually to clicks, taps and key presses

up to 200 ms

up to 500 ms

over 500 ms

CLS (Cumulative Layout Shift)

How much content moves unexpectedly

up to 0.1

up to 0.25

over 0.25

Three details that change how to read them:

  • They are assessed at the 75th percentile, separately for mobile and desktop. A page passes if all three are "good" at that percentile. Google chose the 75th percentile to reflect the most frustrating experiences, not the average.
  • INP is not an average. It is the slowest interaction of the visit, ignoring the highest one for every 50 interactions; since most visits don't reach 50, the worst one is almost always reported. Scrolling, zooming and hovering don't count.
  • INP replaced FID on March 12, 2024. FID only measured the first interaction. In September 2024 FID was removed from CrUX, and the web-vitals library removed onFID() in version 5, released in May 2025. If your dashboard still shows FID, it measures something Google no longer uses.

Did anything change in 2026?

The thresholds did not. According to web.dev, stable metrics don't change more than once per year and every change is announced in their documentation; the CrUX release notes for 2025 and 2026 record no threshold changes. If you read that the good LCP threshold dropped to 2 seconds, ask for the official source: we could not find one.

What is new: on October 13, 2026, CrUX published experimental variants of LCP, INP and CLS that include the "soft navigations" of single-page applications (SPAs). Stable scores don't change while they are evaluated; Google aims to graduate them from experimental in May 2027, and Search Console will then switch to them. If your site is an SPA, review them now.

Field data and lab data

Field data comes from real visits. Google's source is CrUX: PageSpeed Insights shows its data for the previous 28 days, and the Search Console report is also based on CrUX. It only includes Chrome users on desktop and Android who meet certain conditions (not Chrome on iOS or Edge), and the page must be public and have enough visits.

Lab data is a simulated load on a single device and a single network. That is what Lighthouse does, and it is useful for diagnosis.

Why they differ, according to web.dev:

  • The lab loads with an empty cache; your returning visitors don't.
  • A different screen size or personalized content changes which element is the LCP.
  • Pages restored from the back/forward cache (bfcache) load almost instantly and only count in the field.
  • In the field people interact and trigger layout shifts that an automated test doesn't.
  • Lighthouse cannot measure INP because nobody interacts. It uses TBT (Total Blocking Time), which web.dev considers a reasonable proxy, not a substitute.

The official rule: if you have both, prioritize with field data.

How to measure

PageSpeed Insights

Paste the URL into PageSpeed Insights. Field data is at the top; if the URL doesn't have enough, the tool uses data for the whole origin, so check which level you are looking at. The Lighthouse diagnosis is below: use it to find causes, not to decide whether you pass.

Search Console

The Core Web Vitals report groups similar URLs and assigns them the worst status of their metrics: poor CLS with LCP that needs improvement is labeled "poor". A URL only appears if it has LCP and CLS data. After fixing, click "Start tracking": validation takes 28 days.

The web-vitals library

To measure with your own users, including those on Safari and Firefox, use web-vitals, from Google. As of November 4, 2026 the current version on npm is 6.2.3:

// npm install web-vitals
import {onCLS, onINP, onLCP} from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify({
    name: metric.name,            // 'CLS', 'INP' or 'LCP'
    value: metric.value,
    rating: metric.rating,        // 'good', 'needs-improvement' or 'poor'
    id: metric.id,                // use it to deduplicate reports
    navigationType: metric.navigationType,
    page: metric.navigationURL ?? location.href,
  });
  // sendBeacon sends even while the page is closing
  navigator.sendBeacon('/analytics', body);
}

onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
  • Call each function only once per page.
  • A report can arrive more than once: CLS and INP are sent every time the page goes to the background. Keep the last value per id. Going back with the back button produces new metrics, with a different id.
  • If the values are bad, import from web-vitals/attribution: each metric carries clues about the cause, such as the LCP element or the script that delayed an interaction.
  • onCLS() only works in Chromium-based browsers; LCP and INP are also measured in Firefox and Safari.

How much do they weigh in Google?

What Google says in its Search Central documentation:

  • Its ranking systems use Core Web Vitals. Other page experience aspects don't directly help you rank, and there is no single "page experience signal".
  • Google seeks to show the most relevant content even if the page experience is sub-par. But when there is lots of helpful content for a query, a good experience can contribute.
  • Good results in Search Console don't guarantee the top spot, and chasing a perfect score just for SEO may not be the best use of your time.
  • Evaluation is generally page-specific, although there are some site-wide assessments.

In short: they don't make up for irrelevant content, but among similar results they can matter.

What to fix for LCP

web.dev breaks LCP into four parts: time to first byte (TTFB), delay before the resource starts loading, download duration, and delay before it is painted. Ideally almost all the time goes to TTFB and download, with minimal delays.

  • The main image has no priority. Use fetchpriority="high" on the image that is likely to be the LCP, and on no more than one or two. In a Google test on Google Flights, this attribute lowered LCP from 2.6 to 1.9 seconds.
  • The LCP image is lazy-loaded. Never use loading="lazy" on it.
  • The browser discovers it late. If it is inserted with JavaScript, sits in data-src or is a CSS background, put it in the initial HTML or preload it with <link rel="preload">.
  • Render-blocking resources. Synchronous scripts in the <head> and unnecessary CSS delay the first paint: defer what is not critical and use async or defer. Since Lighthouse 13 this appears as "Render-blocking requests".
  • Slow server. A high TTFB can make reaching 2.5 s hard or impossible. web.dev suggests 0.8 s or less as a guide: review your hosting, use a CDN with caching and remove chained redirects.
<img src="/img/hero.webp" width="1200" height="630"
     alt="Image description" fetchpriority="high">

What to fix for INP

An interaction has three phases: input delay, event handler processing, and the delay until the next frame is painted.

  • Long tasks. Any task over 50 ms; while it runs, the browser can't respond. Split the work and yield control between parts.
  • Third-party JavaScript. Chat widgets, pixels and embeds compete for the same thread. Load them with async or defer unless they are critical, and remove those that don't add clear value.
  • Heavy handlers. Do the visible work first and defer the rest, such as saving or sending analytics.
  • Expensive rendering. A very large DOM or generating lots of HTML on the client lengthens the last phase.

web.dev suggests yielding with scheduler.yield() plus a fallback for browsers without support:

function yieldToMain() {
  if (globalThis.scheduler?.yield) {
    return scheduler.yield();
  }
  return new Promise((resolve) => setTimeout(resolve, 0));
}

async function save() {
  validateForm();
  showSpinner();
  await yieldToMain();   // the browser paints before continuing
  saveToServer();
  sendAnalytics();
}

What to fix for CLS

  • Images and videos without dimensions. Always include width and height, or use aspect-ratio.
  • Ads, embeds and iframes. Reserve their space with min-height or aspect-ratio. If no ad is returned, leave a placeholder: collapsing the gap also causes a shift.
  • Content inserted at the top. Banners and notices that push content down: the higher they are, the bigger the shift usually is. Reserve the space or overlay them.
  • Web fonts. If the final font has different metrics than the fallback, text jumps. Use font-display: optional, a suitable fallback font adjusted with size-adjust, and preload critical fonts.
  • Animations. Use transform, not top or left.

Shifts within 500 ms after a click, tap or key press don't count, because the user expects them.

Order of work

  1. Look at field data: which metric fails, on which device and in which group of pages.
  2. Start with what is "poor" on the templates with the most traffic. A fix in one template improves many URLs at once.
  3. Install web-vitals with attribution to learn which element, script or interaction causes the problem.
  4. Apply the cheap fixes: dimensions on images, fetchpriority on the LCP image, no loading="lazy" on it, and async or defer on third-party scripts.
  5. Tackle the structural issues: TTFB, long tasks, JavaScript weight and fonts.
  6. Validate in Search Console and wait the 28 days.

Checklist

Check

Metric

Your dashboards report INP, not FID

INP

The LCP image is in the initial HTML, with fetchpriority="high" and without loading="lazy"

LCP

TTFB around 0.8 s or less: hosting, CDN, caching, no chained redirects

LCP

Third-party scripts with async or defer; those that add nothing removed

INP

Images and videos with width and height or aspect-ratio

CLS

Space reserved for ads, embeds and banners

CLS

Your own measurement with web-vitals, reviewing the 75th percentile by device

All

If it is an SPA, review of the experimental metrics with soft navigations

All

Sources


If you want to review your site's metrics and decide what to fix first, let's talk.

Share this post
Send by email

Keep reading

Web Development

WCAG 2.2: common accessibility errors and how to fix them

10 min
Web Development

HTTP security headers: what to set and how to check

12 min