Core Web Vitals: The Numbers That Actually Move the Needle
LCP, INP, and CLS without the hand-waving — the concrete fixes I reach for first, why most 'performance audits' chase the wrong metric, and the techniques that actually turn a red score green.
The short answer: The Core Web Vitals numbers that actually move the needle are the three field metrics Google ranks on — LCP, INP, and CLS — not the composite Lighthouse score. In practice LCP is almost always the hero image (preload it, size it right, kill render-blocking resources), INP is main-thread work you can break up or defer, and CLS is reserving space so nothing shifts. Chase those three in the field and the red score turns green on its own.
Every few months someone hands me a Lighthouse report with a sea of red and asks me to "make the site faster." The problem is that Lighthouse scores a lab environment, and your users live in the field — on a mid-range Android, on hotel wifi, with your largest image still downloading. After eight years of shipping frontends, I've learned to ignore the composite score and go straight for the three field metrics that Google actually ranks on: LCP, INP, and CLS. Here's where I spend my time.
LCP: it's almost always the image
Largest Contentful Paint is usually your hero image or headline, and the fix is boring: get the important pixels to the user sooner. The wins, in the order I try them:
- Preload the LCP image and stop lazy-loading it. Lazy-loading your hero is self-sabotage — the browser waits to even discover it.
- Serve the right size. A 2340px-wide image downscaled to a 600px container is pure waste. Responsive
srcsetplus modern formats (AVIF, then WebP) routinely cuts hero payloads by 60%. - Kill render-blocking resources ahead of it. A synchronous font or a blocking third-party script delays the whole paint.
That priority flag alone has rescued more LCP scores for me than any amount of JavaScript tree-shaking.
INP: the one that replaced FID, and it's harsher
Interaction to Next Paint measures how long your UI takes to respond when someone taps or clicks — across the whole session, not just the first input. It's unforgiving because it catches the thing users actually feel: the janky dropdown, the frozen tap, the 400ms delay before a menu opens.
The culprit is almost always a long task blocking the main thread. My playbook:
- Break up long tasks. If a click handler does heavy work, yield to the browser so it can paint first.
- Defer non-urgent state updates. React's
useTransitionlets the input feel instant while the expensive re-render happens in the background.
The input stays responsive even while filtering ten thousand rows, because the keystroke render and the list render are no longer the same, blocking, chunk of work.
CLS: stop the page from jumping
Cumulative Layout Shift is the cheapest to fix and the most embarrassing to leave broken — it's the "I tried to tap a button and an ad shoved it down" experience. The fixes are almost entirely about reserving space before content arrives:
- Always set width and height (or an aspect ratio) on images and embeds, so the browser reserves the box.
- Reserve space for anything injected late — banners, cookie bars, ads. Give them a fixed-height container instead of letting them push content.
- Preload fonts and use
font-display: optionalor a well-matched fallback so swapping the webfont doesn't reflow your text.
Measure the field, not the lab
The real lesson: a green Lighthouse score on your gigabit office connection means nothing. Install the web-vitals library, send real INP and LCP to your analytics, and look at the 75th percentile of actual users. That p75 number is what Google ranks and what your users on a bad connection actually endure. Chase that, and the lab score tends to follow — but never the other way around.