Most teams chase a Lighthouse score without knowing what each metric measures or why Google chose it. The number moves, the team cheers, and a month later the field data has not budged. This post is the working definition of the three Core Web Vitals — and one more you should track even though Google does not grade you on it.
What LCP actually measures
Largest Contentful Paint is the moment the browser paints the largest visible element in the viewport during page load. The element is almost always a hero image, a heading set in a large font, or a background image set on a large container. The number is the timestamp, in milliseconds, from navigation start to that paint.
What blocks it is rarely mysterious. In order of how often it shows up:
- A slow HTML response (the server took 600 ms before sending the first byte, so everything downstream starts late).
- Render-blocking CSS — a stylesheet in the document head that has to download before the browser will paint anything.
- The hero image itself: a 1.2 MB JPEG, no
fetchpriority="high", nopreloadlink, served from a different origin so the connection costs add a few hundred milliseconds. - Web fonts: a heading set in a custom font that does not paint until the font file arrives, even though the rest of the page is ready.
The threshold Google uses is 2.5 seconds at the 75th percentile. That means three out of four of your real users need to see the largest element within 2.5 seconds for the metric to be marked good.
What CLS actually measures
Cumulative Layout Shift is the sum of the unexpected movements of visible elements during a five-second window — specifically, the worst five-second window in the session. Each shift is scored by how much of the viewport moved and by how far each element travelled. The score is unitless: 0.1 is the good threshold, 0.25 is poor.
The four causes that account for almost every real case are:
- Images without dimensions. The browser reserves zero space until the image header arrives, then jumps. Always set
widthandheightattributes or a CSS aspect ratio. - Late-loading fonts. The text paints in a fallback font, then re-paints in the web font with different metrics, and the line wrapping changes underneath every element below it. The
size-adjustdescriptor and matching fallback metrics close most of this gap. - Ads and embeds. An ad slot loads an iframe, the iframe resolves to a banner of a different size, the surrounding content jumps. Reserve a fixed slot height even if the ad fails to fill.
- Dynamic content insertion. A cookie banner, a promotional bar, a "you have unread messages" toast — anything that injects above existing content after first paint.
What INP actually measures
Interaction to Next Paint replaced First Input Delay in March 2024. The difference matters: FID only measured the very first interaction of the session, and only the delay before the handler started running. INP looks at every interaction in the session — clicks, taps, key presses — and measures the full time from input to the next visual update. The reported score is approximately the worst interaction in the session.
The threshold is 200 ms at the 75th percentile. Where the number comes from in practice:
- A heavy event handler that runs synchronously — a filter that re-renders a list of 2,000 items on every keystroke.
- Main-thread work scheduled in a way that blocks the next paint — long animation frames, layout thrashing, large synchronous JSON parses.
- Hydration finishing late. A user taps a button before the framework has attached the handler, the handler then runs all at once, and the first interaction looks slow even though nothing seems to be wrong.
The fourth metric Google does not grade you on but you should track
Time to First Byte is the time from navigation start to the moment the first byte of the response arrives. It is not a Core Web Vital, but it bounds every other metric on the page. If your TTFB is 800 ms, your LCP cannot be under 1 second no matter how much you optimise the front end. Track it. The targets that real teams hit on a CDN-backed app are under 200 ms.
High TTFB usually has one of three causes: an origin server that is geographically far from the user with no caching layer in between, a slow database query in the request path, or server-side rendering that waits for data before returning any HTML.
Lab numbers and field numbers tell you different things
A Lighthouse run from a synthetic Chicago datacenter on simulated slow 4G is a development tool. It tells you whether the page is theoretically capable of meeting the thresholds. It does not tell you what your users actually experience.
Field data — collected via the web-vitals library or the Chrome User Experience Report — is the source of truth. A page that scores 95 in Lighthouse can still fail Core Web Vitals in the field if the real user mix is heavy on mid-range Android phones in Pune on a 4G connection that varies between 3 and 25 Mbps. Lab is where you debug; field is where you grade.
What to do first
Measure before you optimise. Install the official web-vitals library, send the metrics to an endpoint you control, and let it run for a week. Look at which of the three metrics is failing at the 75th percentile for your real traffic. Work on that one. Do not chase Lighthouse improvements on the metrics that are already passing.
import { onCLS, onINP, onLCP } from 'web-vitals';
const report = (metric) => {
navigator.sendBeacon('/vitals', JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
rating: metric.rating,
url: location.pathname,
}));
};
onLCP(report);
onCLS(report);
onINP(report);The habit that compounds
Scores improve when you optimise for users, not for Lighthouse. The teams that ship genuinely fast products are the ones that watch field metrics weekly, identify the worst-performing metric for the traffic they actually have, and ship one targeted change at a time. That habit beats every clever hack you might find on a tooling leaderboard.
Related reading
The single highest-leverage move on most sites is reducing the JavaScript that ships. The bundle size audit guide walks through the 30-minute process. To collect your own field data without a third party, see the real-user monitoring setup. Both sit in the broader web performance topic.
About the writers
Developer educator at ShareCode. Writes the tutorial track — Python, JavaScript debugging, coding-interview prep, and the everyday code-quality habits that hold up in real codebases.
More from Kajal
Founder of ShareCode. Writes the engineering deep-dives on this site — WebRTC, Firebase Auth, real-time sync, and the production patterns behind the editor itself.
More from Kishan
Working on a slow page right now?
Drop the page URL and your current LCP, CLS, and INP numbers into a code space, share the link with a teammate, and walk through the three metrics one at a time. Most pages have one dominant bottleneck — finding it together takes less than thirty minutes.
Open a code space →