Skip to content
Development11 min read

Core Web Vitals: What Actually Fixes Each Metric

LCP, CLS, and INP explained by cause rather than definition — and the specific fixes that moved each one on real sites, in the order we try them.

Bilal Rehman

Lead Developer, Quesiono

Most Core Web Vitals articles define the metrics and then tell you to optimise your images. Here's the version that assumes you already know what LCP stands for and want to know what to change.

One thing before the detail: use field data, not lab data. Lighthouse on your laptop over office wifi tells you very little. The Core Web Vitals report in Search Console shows what real users on real devices experienced over the last 28 days, and that's the number Google uses. We've seen sites score 98 in Lighthouse and fail LCP in the field, every time because of mobile network conditions the lab test doesn't simulate.

LCP: the biggest thing above the fold, and when it appears

Largest Contentful Paint measures when the largest visible element finishes rendering. Target is 2.5 seconds at the 75th percentile. Almost always it's a hero image, a headline, or a background.

First, find out what the element actually is. Chrome DevTools' Performance panel labels it directly. People guess wrong about this constantly — we've had projects where everyone assumed the hero photo was the LCP element and it was a large block of heading text waiting on a web font.

Then, in the order we work through them:

1. Serve the right image format at the right size

This is the single biggest lever on most sites. A 2.4MB hero JPEG becomes a 90KB AVIF at the same visual quality. Generate multiple widths, serve them with srcset, and let the browser pick.

On a property site with eight to fourteen full-resolution photos per listing, moving to AVIF with WebP fallback at five widths took listing-page LCP from 9.2 seconds to 1.5. Same photography. Roughly an eighth of the bytes.

2. Preload the LCP image, lazy-load everything else

Lazy-loading the hero image is a common and costly mistake — it delays the exact element being measured. Set loading="eager" and fetchpriority="high" on it, add a preload hint in the head, and lazy-load everything below the fold.

3. Stop web fonts blocking text

If your LCP element is text, the font is in the critical path. Use font-display: swap, self-host rather than pulling from a third-party origin, preload the one weight that renders above the fold, and subset to the characters you need. A variable font in one file usually beats four static weights.

4. Cut server response time

If time to first byte is over 800ms, no amount of image work saves you. Static generation and edge delivery reduce this to near zero for content that doesn't change per request. For a database-driven page, cache the query — a fifteen-panel dashboard we built fires one API request instead of fifteen because of a Redis layer in front.

5. Get render-blocking resources out of the head

Every synchronous stylesheet and script in the head delays first paint. Inline the critical CSS, defer the rest, and load third-party scripts after interaction wherever the vendor allows it.

CLS: things moving after you've started reading

Cumulative Layout Shift measures unexpected movement. Target is under 0.1. It's the most preventable of the three and the most annoying to experience, because the thing that moves is usually the thing you were about to tap.

Causes, in rough order of frequency:

  • Images without dimensions. Set width and height, or an aspect ratio, so the browser reserves the space before the file arrives. Next.js does this for you if you pass both dimensions to the Image component.
  • Ads and embeds. Reserve a fixed slot. If the height varies, reserve the largest plausible height rather than letting the page jump.
  • Cookie banners injected at the top. Overlay them instead of inserting them into the document flow.
  • Font swap reflow. Match the fallback font's metrics to the web font using size-adjust and ascent-override, or use the automatic fallback adjustment Next's font loader generates.
  • Content injected above existing content. A promo bar that loads late, a "you might also like" strip inserted client-side. Reserve the space or render it server-side.

One detail people miss: CLS is measured across the whole session, not just page load. A layout shift when someone expands an accordion halfway down the page counts. Animate height with transform where you can, since transforms don't trigger layout.

INP: how long the page takes to respond to a tap

Interaction to Next Paint replaced First Input Delay in March 2024, and it's stricter. FID measured only the delay before processing started. INP measures the whole interaction — input delay, processing, and the next paint. Target is 200ms.

This is the metric most sites now fail, and the cause is almost always JavaScript occupying the main thread.

Find the long tasks first

DevTools Performance panel, record an interaction, look for tasks over 50ms. On a typical marketing site with a tag manager, two analytics tools, a chat widget, and a consent platform, you'll find plenty.

Then, in order

  • Remove third-party scripts you can't justify. On one store we found two analytics tools loading the same tracking library. Removing the duplicate cost nothing and gave back 340ms of main-thread time.
  • Load non-essential scripts after interaction. Chat widgets, review platforms, and heatmap tools rarely need to be present at first paint. Next's Script component with strategy="lazyOnload" handles most cases.
  • Break up your own long tasks. If a click handler does 300ms of work, yield to the main thread between chunks. scheduler.yield() where supported, setTimeout with zero delay as a fallback.
  • Ship less JavaScript. Server components, dynamic imports for below-the-fold interactivity, and a hard look at any library over 40KB. We benchmarked a charting library on one project and swapped it for a lighter one because the bundle cost outweighed what it added.
  • Debounce expensive input handlers. A search field re-filtering 3,400 products on every keystroke is a guaranteed INP failure. Debounce at 150ms and move the filtering off the main thread if the dataset is large.

The order that actually works

When we take on a speed project, this is the sequence, and it's deliberately front-loaded with the cheap wins:

  1. Pull 28 days of field data and identify which metric fails, on which templates
  2. Audit third-party scripts and remove or defer everything unjustified
  3. Fix the image pipeline — format, sizing, priority
  4. Fix font loading
  5. Reserve space for everything that loads late
  6. Reduce first-party JavaScript on the failing templates
  7. Address server response time, caching, and delivery
  8. Re-measure in the field after 28 days, not the same afternoon

Steps two and three usually account for most of the improvement. We've had projects where the third-party audit alone moved a site from failing to passing, which is a slightly deflating result after scoping a fortnight of engineering.

What to expect, honestly

Field data updates on a rolling 28-day window, so you won't see the improvement immediately no matter how good the fix is. Plan for a month before the Search Console report reflects reality.

Also: passing Core Web Vitals is not a ranking cheat code. It's a tie-breaker between comparable results and a genuine conversion factor. The commercial argument is the second one. On the store where we took mobile LCP from 5.4 seconds to 1.9, the conversion lift was worth considerably more than any ranking movement.

And some platforms cap what's achievable. A Shopify store carrying necessary apps, or a WordPress site on shared hosting with a page builder, has a floor you can't engineer past. We'll tell you where that floor is before quoting rather than after.

If you want to know which metric your site fails and what it would take to fix, our speed optimisation work starts with exactly that assessment.

Want us to do it

The services behind this article

If you'd rather not run this yourself, these are the pages that cover it.

Got a project

Prefer to hand this to someone else?

Tell us what the site needs to do. You'll get a straight answer on scope, timeline and cost — usually inside one working day.

Rather write first?hello@quesiono.com— we reply within one business day.