Cloud Tech by Victor
FrontendIntermediate

Web Performance & Core Web Vitals

What LCP, INP, and CLS each actually measure, why "good" is defined at the 75th percentile of real page loads rather than the average, and why a fast average can still hide a genuinely bad user experience.

Updated 2026-07-243 min read

Overview

Core Web Vitals split "is this page fast" into three genuinely distinct measurements: LCP for loading (how quickly the largest visible element renders, good is ≤2.5s), INP for interactivity (the delay between a user's interaction and the browser's next visual response, good is ≤200ms), and CLS for visual stability (how much content unexpectedly shifts, good is ≤0.1). All three are measured at the 75th percentile of real page loads, not the average, because an average can be dominated by fast loads on good connections while masking a real, sizable slice of users having a genuinely bad experience. That 75th percentile is computed separately for mobile and desktop, not pooled across both, since the two populations have different typical devices and network conditions and a combined percentile would let one mask the other. Each metric is also evaluated independently at its own 75th percentile within each device type, there's no requirement that the same visits satisfy all three together, and a page only counts as passing Core Web Vitals when LCP, INP, and CLS each individually meet their own threshold at the 75th percentile, a materially more honest bar than "the mean looks fine."

Quick Reference

MetricMeasures"Good" threshold
LCP (Largest Contentful Paint)Loading performance≤ 2.5 seconds
INP (Interaction to Next Paint)Interactivity/responsiveness≤ 200 milliseconds
CLS (Cumulative Layout Shift)Visual stability≤ 0.1

Syntax

javascript
import { onLCP, onINP, onCLS } from 'web-vitals';

onLCP(console.log);
onINP(console.log);
onCLS(console.log);

Examples

javascript
// Report real-user Core Web Vitals to analytics, so you're
// measuring actual page loads, not just a single lab test.
import { onLCP, onINP, onCLS } from 'web-vitals';

function sendToAnalytics(metric) {
  navigator.sendBeacon('/analytics', JSON.stringify(metric));
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

A good average can hide a bad 75th percentile

Core Web Vitals are evaluated at the 75th percentile of real page loads specifically because averages get dominated by fast loads and hide a meaningful slice of genuinely slow ones. Check the actual percentile value, not the mean, before concluding a page is fast enough.

Visual Diagram

Common Mistakes

  • Optimizing only for a fast average load time while ignoring the slower tail of real users on weaker devices or connections.
  • Treating loading speed (LCP) as the whole performance story while ignoring interactivity (INP) or visual stability (CLS) separately.
  • Testing performance only in a single lab environment (fast machine, fast network) instead of measuring real-user field data across device/connection variety.
  • Not measuring CLS at all, since layout shift issues (ads or images loading without reserved space) are often invisible in a quick manual check but genuinely disruptive to real users.

Performance

  • Reserving space for images, ads, and embeds before they load is one of the most direct levers on CLS, since the shift happens exactly when reserved space is missing.
  • LCP is most directly improved by optimizing whatever the largest visible element actually is (often a hero image or heading), not by generically "making everything faster."

Best Practices

  • Measure Core Web Vitals from real user field data (via the web-vitals library or similar), not only from a single lab test run.
  • Evaluate against the 75th percentile threshold, not the average, when deciding whether a page's performance is actually acceptable.
  • Reserve layout space for images, embeds, and dynamically injected content to keep CLS low.
  • Treat LCP, INP, and CLS as three separate problems to diagnose and fix independently, not one combined "speed" score.

Interview questions

What do LCP, INP, and CLS each measure, and why are all three needed instead of one overall "speed" number?

LCP (Largest Contentful Paint) measures loading performance, specifically how quickly the largest visible element renders, "good" is 2.5 seconds or less. INP (Interaction to Next Paint) measures interactivity/responsiveness, the delay between a user interaction and the browser's next visual response, "good" is 200 milliseconds or less. CLS (Cumulative Layout Shift) measures visual stability, how much content unexpectedly shifts around during the page's lifecycle, "good" is a score of 0.1 or less. A single overall number couldn't distinguish a page that loads fast but jumps around from one that's stable but slow to interact with, three separate metrics are needed because loading, interactivity, and stability are genuinely different failure modes.

Why does Google measure Core Web Vitals at the 75th percentile of real page loads instead of using the average?

An average can be pulled down by a large number of fast loads on good connections and powerful devices while completely hiding a meaningful tail of slow, frustrating experiences on weaker devices or networks, real users don't experience "the average," they experience their own specific load. Measuring at the 75th percentile means a page only passes if at least three out of four real page loads actually meet the threshold, which is a much more honest bar for "most users get a genuinely good experience" than an average that a handful of very fast loads could distort.

A page has a fast average LCP but users still frequently report the page feeling slow. What could the percentile-based measurement reveal that an average wouldn't?

If a substantial slice of real page loads, say the slowest 25%, badly miss the 2.5 second LCP threshold (a slow connection, a busy device, a cold cache), the average can still look fine because it's dominated by the faster majority, while a real, sizable group of users are having a genuinely bad experience the average is actively hiding. Checking the 75th-percentile value directly (rather than the mean) surfaces that gap: if the 75th percentile is well above 2.5 seconds even though the average looks fine, that's a concrete signal that meaningful numbers of real users are missing the threshold, not a false alarm.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement