Core Web Vitals from real users: LCP, INP and CLS explained, and how to track them
Core Web Vitals are Google's three metrics for how a page feels to a real visitor: how fast the main content shows up, how quickly the page responds, and whether things jump around while it loads. They're part of Google's page experience signals, but the bigger reason to care is simpler: slow, jumpy pages lose visitors and sales.
The three metrics
| Metric | What it measures | Good | Poor |
|---|---|---|---|
| LCP, Largest Contentful Paint | Time until the largest image or text block in the viewport is rendered | ≤ 2.5 s | > 4 s |
| INP, Interaction to Next Paint | Delay between a click, tap or key press and the next visual update, for (nearly) the slowest interaction on the page | ≤ 200 ms | > 500 ms |
| CLS, Cumulative Layout Shift | How much visible content moves unexpectedly (largest burst of shifts) | ≤ 0.1 | > 0.25 |
Google assesses each metric at the 75th percentile of page loads: a page passes when at least 75% of visits are in the "good" range. INP replaced First Input Delay (FID) as a Core Web Vital in March 2024.
Lab data vs field data
Lighthouse and PageSpeed Insights' lab section load your page once, on a simulated device and network. That's great for debugging, but it isn't what your visitors experience:
- Real visitors use slower phones and worse networks than you test on, or much faster ones.
- Lighthouse can't measure INP, because nobody clicks during a lab run. It reports Total Blocking Time as a proxy.
- CLS in the lab only covers loading; shifts caused later, by a cookie banner or a late ad, appear only in the field.
Field data (real user monitoring, RUM) comes from the browsers of actual visitors. Google's own field data is the Chrome UX Report (CrUX), which you see in Search Console and PageSpeed Insights, but it only covers Chrome users who opted in, is aggregated over 28 days, and is missing entirely for pages with little traffic.
Collecting Web Vitals yourself
Browsers expose the raw data through the PerformanceObserver API (largest-contentful-paint, layout-shift and event entries). Google's open-source web-vitals library wraps the edge cases, such as background tabs and back/forward cache restores:
import { onLCP, onINP, onCLS } from 'web-vitals';
function send(metric) {
navigator.sendBeacon('/vitals', JSON.stringify({
name: metric.name, value: metric.value, page: location.pathname,
}));
}
onLCP(send); onINP(send); onCLS(send);
Then store the values per page and report the 75th percentile. Browser support differs: Chromium browsers have had all three APIs for years, while Safari and Firefox added them later and only in recent versions, so check which browsers your field numbers actually come from.
Fixing the usual suspects
- Slow LCP: the hero image is lazy-loaded or huge. Don't lazy-load above-the-fold images, serve them in a modern format at the right size, and add
fetchpriority="high"to the LCP image. Slow server response (TTFB) also delays LCP directly. - Poor INP: long JavaScript tasks on the main thread. Split work, defer non-essential third-party scripts, and keep event handlers light.
- High CLS: images without
widthandheight, banners injected at the top of the page, and web fonts that swap in with different metrics. A size-adjusted fallback font fixes the last one.
Web Vitals next to your traffic
A per-page score is most useful next to how much traffic and money that page brings: a slow page with five visits a month can wait; a slow pricing page can't. PageLens collects LCP, INP and CLS from real visitors with the same lightweight script that counts traffic, and shows the 75th percentile per page on the Performance page, with no extra setup.