The gap between the performance advice people follow and the changes that actually shift field data is wide. Here is what moves the number, in order of effect.
LCP: it is almost always the hero
In four out of five audits, the largest contentful paint element is the hero image, and the problem is one of three things:
It is not preloaded. The browser cannot discover it until it has parsed CSS. Use priority on the Next.js Image component, or a <link rel="preload"> with fetchpriority="high".
It is the wrong format or size. A 480 KB JPEG where a 60 KB AVIF would look identical. Serve AVIF with a WebP fallback and correct sizes.
There is no hero image at all and the problem is a font. If your LCP element is text, the font is blocking. Self-host, use font-display: swap, and provide a metric-matched fallback with size-adjust so the swap does not shift layout.
The single highest-return change we make: replace a hero image with server-rendered text on a CSS gradient. LCP becomes whatever your TTFB is. It also usually looks better.
INP: find the long task, not the framework
INP problems are almost never "React is slow". They are one specific handler doing too much work synchronously.
Open the performance panel, interact, and look for the long task. In our audits it is usually:
- A filter that re-renders 400 cards on every keystroke. Fix:
useDeferredValue, and virtualise above 100 items. - A third-party script — chat widget, analytics, tag manager — executing during interaction. Fix: load it on interaction or after idle, not eagerly.
- An animation on
widthortop, forcing layout on every frame. Fix: animatetransformandopacityonly.
The third one is worth emphasising. A single CSS transition on the wrong property, on a heavily nested element, can be the entire INP problem on a site.
CLS: three causes, all trivially fixable
Images without dimensions. Set width and height, or an aspect-ratio box. Every time.
Late-injected content. Cookie banners, promo bars, ads. Reserve the space in the initial layout even if the element renders later.
Font swap. The fallback and the web font have different metrics, so the text reflows. Fix with size-adjust, ascent-override and descent-override on the fallback.
That is essentially the whole list. CLS is the easiest of the three to get to zero and the one most often left at 0.15.
Measure the field, not the lab
Lighthouse gives you a lab score on a simulated connection. Field data — real visitors, real devices, real networks — is what Google uses and what actually reflects your users.
Ship a web-vitals beacon to your own endpoint and aggregate p75 by page and device type. The first time you do this, expect a surprise: the mobile p75 is usually much worse than the lab score suggested, and one specific template is usually responsible for most of it.
What does not move the number
Minifying already-minified assets. Removing a 3 KB library. Switching CSS frameworks. Endless micro-optimisation of the bundle while a 480 KB unoptimised hero image sits above the fold.
Find the LCP element. Find the long task. Fix those two, and the rest is rounding.