Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
DevelopmentJavaScript

Core Web Vitals explained: LCP, INP and CLS, and how to improve each one

Core Web Vitals measure loading, responsiveness and visual stability with three metrics: LCP, INP and CLS. This explainer covers the current thresholds, how to measure them in the field and the lab, and the fixes that usually move each number.

Cloudsoft Zone

Updated 8 min read

Close-up of a screen showing CSS code in a dark-themed code editor
Photograph: Sai Kiran Anagani _imkiran / CC0, via Wikimedia Commons
In this article

Page speed advice is easy to find and hard to prioritise. Lighthouse lists dozens of audits, analytics tools report their own timings, and it is not obvious which numbers reflect what visitors actually experience. Google's Web Vitals initiative was created to narrow that down to a few metrics that matter for every page.

The current Core Web Vitals are three metrics: Largest Contentful Paint (LCP) for loading, Interaction to Next Paint (INP) for responsiveness and Cumulative Layout Shift (CLS) for visual stability. All three are listed as stable on web.dev, and INP replaced First Input Delay as a Core Web Vital in 2024.

This guide explains what each metric measures, the thresholds that count as good or poor, how to measure them with real-user data, and the optimisations that most often move each number. It is framework-neutral: the fixes apply whether your site is server-rendered, a single-page app or a CMS theme.

The thresholds at a glance

Google assesses each metric at the 75th percentile of page loads, segmented by mobile and desktop. A page passes the Core Web Vitals assessment when all three metrics meet the "good" target at that percentile.

MetricWhat it measuresGoodNeeds improvementPoor
LCPLoading: when the largest image or text block in the viewport renders2.5 s or less2.5 s to 4 sMore than 4 s
INPResponsiveness: delay from a click, tap or key press to the next frame200 ms or less200 ms to 500 msMore than 500 ms
CLSVisual stability: how much visible content moves unexpectedly0.1 or less0.1 to 0.25More than 0.25

Why the 75th percentile? It means most visits, not just the average or the fastest, have a good experience, while ignoring the long tail of extremely slow devices and networks. The web.dev article on defining the thresholds explains the research behind each number.

Three older smartphones on a wooden table each showing a mobile web page
Core Web Vitals are assessed separately for mobile and desktop at the 75th percentile of real visits.

Field data versus lab data

Core Web Vitals are first and foremost field metrics: measurements from real visitors on real devices. Lab tools load a page in a controlled environment and are best for debugging and for catching regressions before release.

  • Field: the Chrome User Experience Report (CrUX) collects anonymised real-user data for all three metrics and powers PageSpeed Insights and the Core Web Vitals report in Search Console. web.dev still "strongly recommends" setting up your own real-user monitoring, because CrUX does not provide per-pageview detail for diagnosing regressions.
  • Lab: Chrome DevTools can measure all three. Lighthouse measures LCP and CLS but cannot measure INP because there is no real user input; it reports Total Blocking Time (TBT) as a proxy instead.

A useful working rule: use field data to decide what to fix and lab tools to find out why.

Collecting field data with the web-vitals library

The simplest way to collect all three metrics from real users is Google's open-source web-vitals library (version 6.2.3 on npm at the time of writing). The snippet below follows the example on web.dev: it registers a callback for each metric and sends the result to your own endpoint.

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

function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  // Use navigator.sendBeacon() if available, falling back to fetch().
  (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
    fetch('/analytics', { body, method: 'POST', keepalive: true });
}

onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);

Store the metric name, value, page URL and device type, then report the 75th percentile per page template. web.dev notes that values measured with JavaScript APIs can differ from CrUX, for example because scripts cannot see inside cross-origin iframes.

Largest Contentful Paint: make the main content arrive early

LCP records when the largest image, video poster or text block in the viewport is rendered. On most pages the LCP element is a hero image or a headline.

web.dev's optimisation guide breaks LCP into four parts, which tells you where the time goes:

  1. Time to First Byte (TTFB): from navigation until the first byte of HTML arrives.
  2. Resource load delay: from TTFB until the browser starts loading the LCP resource. This is zero when the LCP element is text in a system font.
  3. Resource load duration: how long the LCP resource takes to download.
  4. Element render delay: from the end of the download until the element is actually painted.

The guide's rule of thumb is that the LCP resource should start loading at the same time as the first resource the page loads. Common fixes, in the order they usually help:

  • Make the LCP image discoverable in the HTML. An image set in CSS or injected by JavaScript is found late. If it must be, preload it.
  • Raise its priority. Add fetchpriority="high" to the LCP image or its preload.
  • Never lazy-load the LCP image. web.dev is explicit: lazy-loading it "will always lead to unnecessary resource load delay".
  • Reduce TTFB with caching at a CDN, faster server work and fewer redirects.
  • Shrink the resource with modern image formats and correctly sized responsive images.
  • Remove render-blocking work, such as large synchronous scripts in the head, that delays painting after the image arrives.
HTML
<!-- In the <head>: start the hero image download early with high priority -->
<link rel="preload" fetchpriority="high" as="image"
      href="/images/hero-1600.webp" type="image/webp">

<!-- In the page: no loading="lazy" on the LCP image; give it dimensions -->
<img src="/images/hero-1600.webp" width="1600" height="900"
     fetchpriority="high" alt="Team reviewing a release dashboard">

Interaction to Next Paint: keep the main thread free

INP observes clicks, taps and key presses during the whole visit and reports a value that represents the slowest interactions. Each interaction has three parts: input delay (waiting for the main thread), processing time (running event handlers) and presentation delay (rendering the next frame). Hovering, scrolling and zooming are not counted.

Most poor INP comes from JavaScript occupying the main thread. web.dev defines a long task as any task over 50 milliseconds; while it runs, the browser cannot respond to input. The fixes follow from that:

  • Do less in event handlers. Update the UI first, then do non-urgent work (analytics, saving drafts) later.
  • Break up long tasks by yielding to the main thread. scheduler.yield() returns a promise that resolves in a new task and gives the continuation priority. web.dev's browser support table lists it in Chrome and Edge 129 and Firefox 142, but not Safari, so use a fallback.
  • Reduce script evaluation at startup by shipping less JavaScript and deferring what is not needed for the first interaction.
  • Keep the DOM and layout work small: large DOMs and repeated forced layouts make rendering after each interaction slower.
  • Move heavy computation to a web worker where it does not need the DOM.

This helper, adapted from web.dev's long-tasks guide, yields with scheduler.yield() when it exists and falls back to setTimeout:

JavaScript
function yieldToMain() {
  if (globalThis.scheduler?.yield) {
    return scheduler.yield();
  }
  // Fall back to yielding with setTimeout.
  return new Promise((resolve) => {
    setTimeout(resolve, 0);
  });
}

async function saveSettings(tasks) {
  let lastYield = performance.now();
  for (const task of tasks) {
    task();
    // Yield roughly every 50 ms so input can be handled between tasks.
    if (performance.now() - lastYield > 50) {
      await yieldToMain();
      lastYield = performance.now();
    }
  }
}

web.dev no longer recommends isInputPending() for this purpose; it advises yielding regardless of whether input is pending.

Cumulative Layout Shift: reserve space before content arrives

CLS adds up unexpected movements of visible content, such as text jumping down when an image, advert or web font loads. Shifts within 500 milliseconds of a discrete input such as a tap, click or key press are flagged by the browser so they can be excluded; scrolling and dragging do not count as recent input.

  • Give images and videos dimensions with width and height attributes (or CSS aspect-ratio) so the browser reserves space.
  • Reserve space for adverts, embeds and late banners with a minimum height, and avoid inserting content above existing content.
  • Control web font swaps. font-display: optional avoids a re-layout; always specify a fallback font family; and the size-adjust, ascent-override, descent-override and line-gap-override descriptors can make the fallback match the web font's size.
  • Keep pages eligible for the back/forward cache (bfcache), which restores pages instantly without the shifts of a fresh load.
  • Animate with transforms rather than properties that change layout, such as top or height.
Rows of rack-mounted servers with cables in a data centre
Time to First Byte, the first part of LCP, depends on how quickly servers and caches return HTML.

A practical workflow

  1. Check field data in PageSpeed Insights or Search Console and find which metric fails, on which device type and on which page templates.
  2. Reproduce in the lab with Chrome DevTools' performance panel, throttled to a mid-range mobile profile. Identify the LCP element, the slowest interactions and the elements that shift.
  3. Fix the biggest part first: for LCP, the largest of the four sub-parts; for INP, the longest tasks around slow interactions; for CLS, the largest single shift.
  4. Prevent regressions with a performance budget or Lighthouse check in CI (using TBT as the INP proxy), and keep real-user monitoring running.
  5. Confirm in the field. Lab improvements are a prediction; only real-user data shows whether the 75th percentile actually moved, so compare your monitoring before and after each release.

Summary

Core Web Vitals reduce page experience to three questions: does the main content appear quickly (LCP of 2.5 s or less), does the page respond quickly to input (INP of 200 ms or less), and does it stay still while loading (CLS of 0.1 or less), each measured at the 75th percentile of real visits. Measure in the field, debug in the lab, and work on the largest contributor first: early discovery and priority for the LCP resource, short main-thread tasks for INP and reserved space for CLS.

Frequently asked questions

  • Is First Input Delay still a Core Web Vital?

    No. web.dev states that INP became a stable Core Web Vital in 2024 and replaced FID. The current Core Web Vitals are LCP, INP and CLS.

  • Why does Lighthouse not show INP?

    Lighthouse loads pages without a real user, so there are no interactions to measure. It reports Total Blocking Time (TBT) as a lab proxy; improving TBT often improves INP in the field.

  • What percentile should I report?

    The 75th percentile of page loads, separately for mobile and desktop. This is the level Google uses to assess whether a page meets each threshold.

  • Should I lazy-load every image to improve LCP?

    No. Lazy-load images below the fold, but never the LCP image. web.dev warns that lazy-loading the LCP image always adds resource load delay.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. Web Vitals — web.dev (Google)
  2. Defining the Core Web Vitals metrics thresholds — web.dev (Google)
  3. Interaction to Next Paint (INP) — web.dev (Google), 2025-09-02
  4. Optimize Largest Contentful Paint — web.dev (Google)
  5. Optimize long tasks — web.dev (Google)
  6. Optimize Cumulative Layout Shift — web.dev (Google)
  7. web-vitals package (npm registry, latest version) — npm

Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.