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
| Metric | Measures | "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
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);Examples
// 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-vitalslibrary 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.