Core Web Vitals in Practice: LCP, INP, and CLS
Understand what LCP, INP, and CLS measure, how field data differs from lab data, and the concrete fixes that move each metric.
Core Web Vitals are three numbers that describe how a page feels to load and use. They matter because they are measured from real visits, they feed into search ranking signals, and — more importantly — because each one maps to a specific kind of frustration. This guide is about fixing them, not memorizing their names.
What Each Metric Measures
| Metric | Question it answers | Good | Needs work |
|---|---|---|---|
| LCP | When does the main content appear? | ≤ 2.5 s | > 4.0 s |
| INP | How quickly does the page respond to input? | ≤ 200 ms | > 500 ms |
| CLS | How much does the layout shift while loading? | ≤ 0.1 | > 0.25 |
Scores are evaluated at the 75th percentile: three quarters of visits must pass. A good average with a bad tail still fails.
Field Data vs. Lab Data
Lighthouse runs a lab test on a simulated device. It is useful for finding problems, but it is not the score search engines use. Field data comes from real users:
- CrUX is Chrome’s public dataset for popular sites, visible in PageSpeed Insights.
- RUM is your own telemetry. The
web-vitalslibrary makes it a few lines:
import { onLCP, onINP, onCLS } from "web-vitals";
onLCP((metric) => console.log("LCP", metric.value));
onINP((metric) => console.log("INP", metric.value));
onCLS((metric) => console.log("CLS", metric.value));
Field data tells you whether users are happy; lab data tells you why they are not.
Fixing LCP
LCP is usually one resource: a hero image, a background video, or a slow font blocking text.
- Serve the LCP image in a modern format (AVIF/WebP) and preload it.
- Mark the image
fetchpriority="high"and never lazy-load it. - Set explicit
widthandheightso layout is not recalculated. - Avoid client-side rendering for above-the-fold content; ship HTML.
- Self-host fonts, subset them, and use
font-display: swap.
For most static sites, fixing the hero image alone moves LCP from “needs work” to “good.”
Fixing INP
INP replaced FID because it measures every interaction, not just the first. Long tasks on the main thread delay every click and keypress.
- Split long JavaScript tasks and yield to the browser between chunks.
- Hydrate only what is interactive, and hydrate lazily.
- Audit third-party scripts; one chat widget can dominate the main thread.
- Debounce expensive handlers and move work to a worker where possible.
- Test on a mid-range phone, not just your laptop.
Fixing CLS
CLS is about space that was not reserved:
- Always set
width/heightoraspect-ratioon images, videos, and iframes. - Reserve space for ads, embeds, and cookie banners.
- Avoid injecting content above existing content after load.
- Use
font-display: optionalor preload fonts to reduce swap shifts.
A single unsized image above the fold is the most common cause.
Put a Budget in CI
A performance budget turns “we should be faster” into a build failure:
- LCP under 2.5 s on a simulated 4G connection.
- Total JavaScript under an agreed limit per route.
- No layout shift from images missing dimensions.
Run Lighthouse CI or a synthetic check on every pull request, and compare against the previous build rather than an absolute score.
Conclusion
LCP is about priority, INP is about main-thread work, and CLS is about reserved space. Measure with field data, fix the dominant cause first, then guard the result with a budget. Performance stops being a project and becomes a property of the codebase.