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.
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.
| Metric | What it measures | Good | Needs work | Poor |
|---|---|---|---|---|
| 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.
| Cause | Metric | Fix |
|---|---|---|
| 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
-
Field data
Real visits from CrUX — this is what Google uses in search and what Search Console shows.
-
Lab data
One run of Lighthouse on an emulated phone — good for finding causes, not for the final verdict.
-
Why they differ
Real visitors have different phones, networks and pages; the lab has one device and one load.
-
INP in the lab
Lighthouse does not click, so it shows Total Blocking Time — a hint, not INP itself.
-
28 days of delay
Field data is collected over 28 days — a fix shows fully in the reports about a month later.
-
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.
// 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.
<!-- 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.
/* 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.
// 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
-
Chasing 100 in Lighthouse
Search uses field data; a perfect lab score with poor field data changes nothing.
-
Testing on a fast computer
Most visits come from mid-range phones on a mobile network.
-
Lazy loading everything
loading="lazy"on the first-screen picture delays LCP. -
Checking only the home page
Product pages and articles bring most of the traffic and usually suffer more.
-
Scripts of every service
Chats, pixels and widgets add up and spoil INP more than your own code.
-
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
-
01
A budget before design
The target numbers are agreed before the first layout — then the design does not fight them.
-
02
HTML from the server
The main content comes in the first response, not after scripts.
-
03
One priority picture
fetchpriority="high"only on the first-screen picture,loading="lazy"on the rest. -
04
Own fonts
woff2 with only the needed characters, from your own server.
-
05
Sizes for everything that loads
Pictures, video, iframes and widgets get their place before they arrive.
-
06
Third-party scripts on a list
Each one is justified and loads after the main content.
-
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.