Core Web Vitals scores read as three numbers, but they measure unrelated failure modes, and treating them as one "performance score" to chase leads to fixes aimed at the wrong metric. LCP is about the slowest thing to appear. INP is about the slowest thing to respond. CLS is about layout moving after the user already started looking at it. Each has its own diagnostic path.
Largest Contentful Paint: Find the Actual Bottleneck
LCP measures when the largest visible element renders — usually a hero image, a large text block, or a background image. The fix depends entirely on which of four things is slow: server response time, render-blocking resources, resource load time, or client-side rendering delay.
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
<img src="/hero.webp" fetchpriority="high" alt="Product hero shot" />fetchpriority="high" on the LCP image is frequently the single highest-leverage change available — browsers otherwise discover an <img> late in parsing, after CSS and scripts ahead of it, and treat it as low priority by default. Combine it with preload only for the actual LCP candidate; preloading everything defeats the purpose by competing for the same bandwidth.
The most common silent LCP killer is a client-rendered hero — a component that fetches its content after mount. If the largest element depends on a client-side data fetch, LCP cannot happen until that round trip completes; server-rendering that specific piece of content, even if the rest of the page stays client-rendered, usually recovers seconds.
Interaction to Next Paint: The Main Thread Is the Suspect
INP measures the delay between a user interaction and the next visual update, sampled across the entire page lifetime — not just at load, unlike its predecessor FID. A slow INP almost always traces back to long tasks blocking the main thread when the interaction fires.
function handleFilterChange(value) {
updateUrgentUI(value); // must run before yielding
scheduler.yield().then(() => {
runExpensiveFiltering(value); // deferred past the next paint
});
}Breaking a large synchronous handler into an urgent piece and a yielded piece lets the browser paint the immediate feedback (a pressed state, an input update) before continuing the expensive work. For third-party scripts — the other common INP culprit — auditing what runs on every click handler (analytics beacons, tag manager listeners) often surfaces surprising amounts of blocking work that has nothing to do with your own code.
Cumulative Layout Shift: Reserve Space Before Content Arrives
CLS penalizes any unexpected shift of visible elements. The fix is almost always the same shape: know the dimensions of a thing before it loads, and reserve that space.
.hero-image {
aspect-ratio: 16 / 9;
width: 100%;
}
.ad-slot {
min-height: 250px; /* reserved even before the ad script injects content */
}aspect-ratio solves the classic image-without-dimensions shift without needing explicit width/height attributes, though setting both remains the more broadly compatible option for older rendering paths. Web fonts are the other frequent offender — a fallback font rendering at a different metric than the webfont causes reflow when the real font loads; font-display: optional or matching fallback metrics with size-adjust avoids the jump entirely.
Measure in the Field, Not Just the Lab
Lighthouse and other lab tools run on a single, controlled device and connection — useful for catching regressions in CI, but they don't reflect what real users experience across device tiers and network conditions. The Chrome UX Report and a real-user-monitoring script (using the web-vitals library to send actual field data to your analytics) are what Search Console and similar tools actually score you on. A page that looks perfect in Lighthouse and still fails field CWV usually has a device- or network-specific bottleneck — commonly a JavaScript bundle that's fine on a development machine and brutal on a mid-tier Android phone.
Prioritization
Fix LCP first if it's failing — usually the most visible to users and the most straightforward to diagnose. INP next, since main-thread blocking tends to compound across other metrics. CLS last, not because it matters less, but because it's typically the cheapest fix once you find the specific unsized element, and chasing it before the bigger issues are resolved often means re-measuring against a moving baseline.