Core Web Vitals are how Google — and, more importantly, your actual users — quantify whether your site feels good to use. There are three of them, and they map cleanly onto three frustrations every visitor has felt: how long until I see something (LCP), does the page jump around while I read (CLS), and does it respond when I tap (INP). Hit green on all three and you've eliminated the most common reasons people bounce.
This is the field-tested playbook I run on every project. No vague advice — specific metrics, specific fixes, in priority order.
The targets you're aiming for
- LCP (Largest Contentful Paint): under 2.5 seconds. How long until the biggest above-the-fold element — usually a hero image or headline — finishes rendering.
- CLS (Cumulative Layout Shift): under 0.1. How much the page visibly shifts around as it loads. Lower is better; zero is the goal.
- INP (Interaction to Next Paint): under 200 milliseconds. How quickly the page responds to taps, clicks, and key presses. This replaced FID in 2024 and is far stricter.
One critical caveat: these are measured on real user data (field data), not just your lab tests. A green Lighthouse score on your fast laptop means little if your visitors are on mid-range phones over patchy mobile networks. Always check field data, and always test throttled.
Fixing LCP: get the hero painted fast
LCP is almost always gated by one of two things: a slow server response, or a slow-to-load hero image. Work through these in order:
- Preload the LCP image. Tell the browser to fetch your hero image immediately instead of discovering it late in the parse. Add
fetchpriority="high"to the image and a preload hint in the head. - Serve modern formats at the right size. WebP or AVIF instead of JPEG/PNG, sized to the actual rendered dimensions. Don't ship a 3000px image into a 640px slot.
- Self-host fonts and use
font-display: swap. If your headline is the LCP element, a blocking web-font request can stall it. Swap shows fallback text immediately, then upgrades. - Cut server response time. Cache aggressively, put a CDN in front, and avoid render-blocking work. A static page wins here for free.
<link rel="preload" as="image"
href="/img/hero.webp" fetchpriority="high">
<img src="/img/hero.webp" width="1200" height="630"
fetchpriority="high" decoding="async" alt="…"> Fixing CLS: stop the page from jumping
Layout shift is the cheapest vital to fix and the most embarrassing to ignore — it's what makes you tap the wrong button because an ad loaded a half-second late. The root cause is always the same: the browser doesn't know how much space something will take, lays out the page without it, then reflows when it arrives.
- Always set
widthandheight(oraspect-ratio) on images and video. This single habit kills the most common source of CLS. The browser reserves the correct box before the file loads. - Reserve space for embeds and ads. Give iframes, ad slots, and dynamically injected banners a fixed minimum height container.
- Never insert content above existing content. Cookie bars, promo banners and "loaded later" sections should overlay or push from the bottom — not shove the article down after the user has started reading.
- Use
font-display: swapwith a well-matched fallback. A wildly different fallback metric causes a reflow when the web font swaps in. Size-adjust the fallback to minimize it.
CLS is a discipline problem, not a hard technical one. If every image, embed, and dynamic block declares its dimensions up front, your CLS is effectively zero. I treat a missing width/height attribute as a bug, not a style choice.
Fixing INP: keep the main thread free
INP is where bloated JavaScript finally sends the bill. Every interaction — a tap, a click, a keystroke — has to be handled on the main thread, and if that thread is busy executing a giant bundle, the response is delayed and the page feels sluggish. The fixes are all variations of "do less JavaScript, and do it later":
- Defer and split your JavaScript. Load only what the page needs, when it needs it. Use
deferon scripts, and code-split so a heavy feature only loads on the page that uses it. - Break up long tasks. Any task over ~50ms blocks interaction. Chunk heavy work, yield to the main thread, or move it off-thread with a web worker.
- Avoid expensive work in event handlers. Debounce input, avoid synchronous layout reads in scroll handlers, and don't trigger huge re-renders on every keystroke.
- Ship less framework. The single biggest INP win on many sites is simply not shipping a megabyte of framework to render content that didn't need it. This is the performance argument for static-first in one metric.
The infrastructure that makes all three easier
Some choices help every vital at once, which is why I bake them in from day one rather than retrofitting:
- A CDN with long cache lifetimes. Faster LCP for everyone, near-zero server response time, and effortless scaling under load.
- Self-hosted, subsetted fonts. No third-party request on the critical path; smaller files; swap behaviour you control.
- Compression and HTTP caching headers. Brotli or gzip on text assets, immutable cache headers on versioned files. Repeat visits become instant.
- Right-sized, modern images everywhere. WebP/AVIF with explicit dimensions handles a chunk of both LCP and CLS in one move.
The order I work in
When I audit a site, I don't fix randomly — I sequence for impact. First, CLS, because it's the fastest to fix and the most visible to users: set every dimension. Second, LCP: preload the hero, optimize the image, get the server fast. Third, INP: defer, split, and ruthlessly cut JavaScript. Then re-measure against field data, not just your own machine.
Get this right and you don't just pass a Google test — you build a site that feels instant, ranks better, and converts more. On the projects I've tuned this way, sub-2s loads and stable vitals under campaign traffic aren't a lucky outcome. They're the direct result of doing these specific things, in this specific order, every single time.