Field data is the only score that counts
Lighthouse runs on a simulated mid-tier phone on a throttled connection in a data centre. Your users are not that. Google ranks on the Chrome User Experience Report — real sessions, 28-day rolling window, scored at the 75th percentile — so chasing a lab number can leave the metric that matters untouched. Install the web-vitals library and send LCP, INP and CLS to your own analytics with the page template attached, so you can see that the pricing page passes and the search results page does not. Treat lab tools as a debugger, not a scoreboard: they tell you why a specific load was slow, and they are excellent at that. Just never report a Lighthouse score to a stakeholder as evidence that real users are having a good time.
LCP is almost always one image or one font
Largest Contentful Paint has a 2.5 second budget at the 75th percentile, and in our experience four causes explain most failures. First, the hero image is lazy-loaded — the browser will not even start fetching it until layout runs. Set fetchpriority="high" on it and remove loading="lazy"; on Next.js that is the priority prop. Second, the image is a 900KB PNG that should be a 60KB AVIF at the right dimensions. Third, a render-blocking webfont delays the text paint: self-host it, preload the one weight above the fold, and use font-display: swap with a metric-matched fallback so the swap does not also cost you CLS. Fourth, a slow server response — if time to first byte exceeds 600ms, no front-end work will save you. Fix that first.
INP is a JavaScript problem you can measure
Interaction to Next Paint replaced First Input Delay in March 2024, and it is a much harder test: it measures every interaction across the visit, not just the first, and the budget is 200ms. Long tasks on the main thread are the cause, and on React sites hydration is usually the largest. Ship less JavaScript to the client — server components, islands, or plain progressive enhancement all work, and the framework matters less than the byte count. Break work over 50ms with scheduler.yield or a yield to the event loop. Watch for the specific killers: a heavy onChange handler filtering a large list on every keystroke, an analytics script running synchronously inside a click handler, and third-party tag managers that queue arbitrary vendor code. Debounce, defer to a worker, or delete the vendor.
CLS is cheap, and some advice is overrated
Cumulative Layout Shift is the easiest of the three to pass at 0.1: put width and height attributes on every image and video, reserve space for ad slots, banners and embeds with min-height, and never insert content above existing content after load — a cookie banner or promo bar pushing the page down is a classic self-inflicted failure. Now the overrated list. Critical CSS inlining is fiddly, breaks on redesign, and rarely beats simply shipping less CSS. Preloading a dozen resources makes everything compete and usually slows LCP. Micro-optimising bundle size below roughly 100KB gzipped has far less effect than removing a single blocking third-party script. And a perfect 100 in Lighthouse on a page that fails CrUX is not a result — it is a false negative you paid for.