/back-to-blog
SEOJanuary 24, 20258 min read

How to Improve Core Web Vitals: 7 Steps for LCP, INP and CLS

A step-by-step fix guide for failing Core Web Vitals: find the failing metric in field data, then fix LCP, INP and CLS with code you can copy, and monitor the result.

A

Acefina

Performance · Infrastructure · Security

Abstract illustration of a glowing faceted polyhedron connected by thin lines to smaller cubes, surrounded by flowing orange and purple light streams

This guide assumes Search Console or PageSpeed Insights has already told you something is failing. The targets are an LCP of 2.5 s or less, an INP of 200 ms or less and a CLS of 0.1 or less, measured at the 75th percentile of real visits. If those terms or the 75th percentile rule are new, read what Core Web Vitals measure and how Google scores them first.

Step 1: Find the failing metric and template in field data

Start with field data, because that is what Google scores (web.dev):

  1. Open the Core Web Vitals report in Search Console. URLs are grouped by similar experience, and each group takes the status of its worst metric (Search Console Help). Note which metric fails, on mobile or desktop, and an example URL.
  2. Run that example URL through PageSpeed Insights. The top section is CrUX field data; the Lighthouse run below it is a lab test you can use to diagnose.
  3. Fix the group with the most Poor URLs first. The status covers the whole group, so one fix to a shared template can clear many URLs.

Lighthouse cannot measure INP, because nobody interacts with the page in a lab run. It shows Total Blocking Time (TBT) instead, and a page can pass TBT and still fail INP (web.dev). For INP, record real interactions in the Chrome DevTools Performance panel, which shows your local LCP, CLS and INP live and can compare them with CrUX field data (Chrome for Developers).

Step 2: Fix LCP

LCP time splits into four parts (web.dev): time to first byte (TTFB), the delay before the LCP resource starts downloading, its download time, and the delay before it renders. Find which part is large before changing anything; shrinking one part can just move the time into another.

Let the browser find the LCP image early

Put it in the HTML as a normal <img>, give it fetchpriority="high", and never lazy-load it:

<img
  src="/images/hero.webp"
  width="1200"
  height="600"
  alt="Describe the image"
  fetchpriority="high"
/>

If the image is only referenced from CSS or JavaScript (a CSS background, for example), preload it so the browser does not wait for those files:

<link rel="preload" as="image" href="/images/hero.webp" type="image/webp" fetchpriority="high" />

Set fetchpriority="high" on one or two images at most; on more, it stops helping (web.dev).

Cut server response time

TTFB is not a Core Web Vital, but it is the first part of LCP, and web.dev treats 0.8 s or less as good (web.dev). The fixes in order (web.dev): hosting that can handle your traffic, a CDN, edge caching with the right Cache-Control headers, removing same-origin redirects, and 103 Early Hints if your backend needs a long time to build the page.

Remove render-blocking work

Keep CSS small and avoid synchronous <script> tags in the <head>; web.dev says they are almost never necessary.

Serve smaller images

Use AVIF or WebP with responsive srcset sizes. Google reports lossy WebP files are 25-34% smaller than comparable JPEGs at equivalent quality, and lossless WebP is 26% smaller than PNG (Google for Developers). Download time is rarely the biggest part of LCP, though, so check the breakdown first.

Step 3: Fix INP

An interaction has three parts: input delay (the main thread is busy when the user clicks), processing (your event handlers run) and presentation delay (the browser renders the next frame) (web.dev). Long tasks, meaning any task over 50 ms on the main thread, hold up all three: while one runs, the browser cannot respond to input or paint (web.dev).

Do the visible update first, then yield

Update the UI, hand control back to the browser so it can paint, and run the rest in a new task. scheduler.yield() does this with a prioritised continuation; browsers without it can fall back to setTimeout:

function yieldToMain() {
  if (globalThis.scheduler?.yield) {
    return scheduler.yield();
  }
  return new Promise((resolve) => setTimeout(resolve, 0));
}

button.addEventListener('click', async () => {
  updateUI();          // visible change first
  await yieldToMain(); // let the browser paint
  saveToServer();      // non-visual work in a separate task
  sendAnalytics();
});

This pattern is adapted from web.dev's guide to long tasks, which lists scheduler.yield() as supported in Chrome and Edge 129 and Firefox 142, but not yet in Safari.

Move heavy computation to a Web Worker

Workers run in parallel with the main thread, but they cannot touch the DOM, so they suit parsing, sorting and number crunching rather than UI work (web.dev).

Keep the DOM small

Rendering work grows with DOM size. content-visibility: auto lets the browser skip rendering off-screen sections until they approach the viewport (web.dev).

Step 4: Fix CLS

These fixes come from web.dev's CLS guide:

  • Give every image and video width and height attributes, or reserve the space with CSS aspect-ratio.
  • Reserve space for ads and embeds with min-height, and keep that space even when no ad fills it. Removing the slot causes a shift too.
  • Put content that loads late (banners, recommendations) further down the page, or load it when the user asks for it. Shifts within 500 ms of a tap, click or key press are not counted.
  • For web fonts, both swap and invisible-text loading can shift the layout. Use font-display: optional, or match the fallback font's size to the web font with size-adjust and the related overrides, and preload the main font file.
  • Animate with transform instead of top or left; transform animations don't count toward CLS.
  • Keep pages eligible for the back/forward cache, so a return visit shows the finished page with no shifts.
<!-- Space is reserved before the ad script runs -->
<div class="ad-slot" style="min-height: 250px;"></div>

Step 5: Reduce third-party script cost

Analytics, chat widgets, tag managers and ad scripts run on the same main thread as your own code. Following web.dev's guidance on third-party JavaScript:

  • Remove any script that doesn't add clear value.
  • Load the rest with async, or defer for less critical ones, unless a script has to run before the page renders.
  • Use <link rel="preconnect"> only for the most critical third-party origins and dns-prefetch for the others.
  • Lazy-load below-the-fold embeds with the native loading="lazy" attribute.

Step 6: Cache and compress static files

Give fingerprinted files (for example app.3f9a2c.js) a one-year cache and make the browser revalidate anything unversioned, such as HTML (web.dev):

# Versioned assets
Cache-Control: max-age=31536000, immutable

# HTML and other unversioned URLs (pair with an ETag)
Cache-Control: no-cache

Some browsers ignore immutable; it is an optional extra. Serve text files (HTML, CSS, JS, JSON, SVG) compressed with Brotli where the browser supports it, with gzip as the fallback. Check the content-encoding response header in the DevTools Network tab to confirm (Chrome for Developers).

Step 7: Monitor the result with field data

CrUX uses a rolling 28-day window, so a fix takes up to four weeks to show fully in Search Console. To see the effect sooner, collect your own field data with Google's web-vitals library:

import { onCLS, onINP, onLCP } from 'web-vitals';

function send(metric) {
  navigator.sendBeacon('/analytics', JSON.stringify(metric));
}

onCLS(send);
onINP(send);
onLCP(send);

Then:

  • Add Lighthouse CI to your build to catch lab regressions on every commit.
  • In Search Console, click Start tracking on the fixed issue. It starts a 28-day monitoring period; it does not trigger re-indexing (Search Console Help).
ToolDataMeasures INP?Cost
PageSpeed InsightsCrUX field data plus a Lighthouse lab runIn the field section onlyFree
Search Console Core Web Vitals reportCrUX field data, grouped by URLYesFree
Chrome DevTools Performance panelYour live session, optional CrUX comparisonYes, when you interactFree
Lighthouse CILab runs in CINo (uses TBT)Free, open source
web-vitals libraryField data from your own visitorsYesFree, open source

Will better scores lift rankings?

Core Web Vitals are used by Google's ranking systems, but relevance comes first, and Google says good scores don't guarantee top positions (Google Search Central). Fix failing metrics because visitors feel them, and judge the work by your field data and conversions rather than by a ranking jump. The Core Web Vitals guide covers what Google says in more detail.

Get help with the fixes

If the failing template sits in a theme, framework or hosting setup you'd rather not touch yourself, our speed optimization service starts with a free review. On WordPress, our WordPress speed and caching work covers the theme, plugins and hosting side. If the trouble goes beyond speed, for example redirects, canonicals or pages Google has not indexed, our technical SEO audits for UK businesses look at those signals together.

Need help with your infrastructure?

Let's turn this article into action on your site.

If your site needs faster performance, better security, or streamlined DevOps, we can help you plan and deliver.