Skip to content
C Codeloom
Web

Web Vitals Explained: LCP, INP, CLS in Plain English

What Largest Contentful Paint, Interaction to Next Paint, and Cumulative Layout Shift measure, how to read them, and the practical fixes that move each one.

·5 min read · By Yash Kesharwani
Intermediate 9 min read

What you'll learn

  • What LCP, INP, and CLS each measure
  • The threshold values Google uses for good and poor
  • How field data differs from lab data
  • Concrete fixes for each metric
  • How to measure Web Vitals in your own app

Prerequisites

  • Basic web rendering knowledge helps but is not required

Web Vitals are Google’s attempt to reduce performance to three numbers a product team can act on. Each one targets a different user complaint: things take too long to show up, things take too long to react, or things jump around. Getting these green is one of the highest leverage tasks in web work.

The three core metrics

  • LCP: Largest Contentful Paint. How fast the main content appears.
  • INP: Interaction to Next Paint. How fast the page responds when the user does something.
  • CLS: Cumulative Layout Shift. How much the page visually jumps after it loads.

The thresholds:

MetricGoodNeeds workPoor
LCPunder 2.5 s2.5 to 4.0 sover 4.0 s
INPunder 200 ms200 to 500 msover 500 ms
CLSunder 0.10.1 to 0.25over 0.25

Google uses the 75th percentile of real user data over 28 days. That is field data, not lab data, and it is what shows up in Search Console.

LCP: getting the main thing visible

LCP measures when the largest image, video, or text block in the viewport finishes rendering. It is a proxy for perceived load.

Common causes of bad LCP:

  • Slow server response (high TTFB).
  • Render-blocking CSS and JavaScript.
  • The LCP image lives behind a JS waterfall.
  • The image is huge, uncompressed, or served in the wrong format.

Practical fixes:

  • Serve HTML quickly. Use CDN caching for static pages and edge caching where possible.
  • Preload the LCP image:
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high">
  • Add fetchpriority="high" to the LCP image tag itself.
  • Use modern formats: AVIF or WebP, with srcset for responsive sizes.
  • Inline critical CSS for the above-the-fold section.
  • Remove or defer third-party scripts in the head.

If you use a framework like Next.js, its image component handles most of this. See What is Next.js? for context.

INP: making the page feel responsive

INP replaced First Input Delay in 2024. It measures the full latency of an interaction, from input to the next paint, across every interaction during the visit. The metric reports roughly the worst interaction the user had.

The dominant cause is long tasks on the main thread. JavaScript blocks the event loop, the browser cannot respond, and the user sees a stutter.

Practical fixes:

  • Break long tasks. Use scheduler.yield() or setTimeout(fn, 0) to split work.
  • Move heavy work to a Web Worker. Anything CPU-bound belongs there.
  • Defer non-essential scripts. async and defer are still useful.
  • Avoid hydrating the whole page when only a small island is interactive. Frameworks like Astro and React Server Components help here.
  • Use CSS transitions for animations rather than JS where possible.
  • Debounce input handlers that trigger expensive renders.

A simple yield pattern:

async function processChunks(items) {
  for (let i = 0; i < items.length; i++) {
    doWork(items[i]);
    if (i % 50 === 0) await new Promise(r => setTimeout(r, 0));
  }
}

If you build with React, watch out for cascading re-renders. See React Hooks: useState and useEffect for the patterns that keep updates cheap.

CLS: stop the page from jumping

CLS sums up unexpected layout shifts during the visit. Score is the impact fraction times the distance fraction, totaled across shifts.

Top causes:

  • Images and iframes without width and height attributes.
  • Fonts that swap and reflow text.
  • Ads or embeds injected into the page.
  • Cookie banners or modals pushing content down.

Practical fixes:

  • Always set explicit dimensions on media:
<img src="/photo.jpg" width="800" height="600" alt="...">
  • Use aspect-ratio in CSS for responsive containers.
  • Use font-display: optional or self-host fonts and preload them.
  • Reserve space for ads, embeds, and banners with min-height.
  • Avoid inserting content above existing content unless the user asked for it.

Field data vs lab data

Lighthouse runs in a clean lab. Real users have old phones, weak networks, and dozens of tabs. The two will not match.

Use lab tools to diagnose, but trust field data to decide. Three sources:

  • Chrome User Experience Report (CrUX): aggregated real data, surfaced in PageSpeed Insights and Search Console.
  • The web-vitals library: collects metrics from your actual users.
  • Real User Monitoring (RUM) products: Vercel Analytics, Sentry, Datadog, others.

A simple in-page collector:

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

function send(metric) {
  navigator.sendBeacon("/vitals", JSON.stringify(metric));
}

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

Tag each event with the route, device class, and a build version. You will need those slices when you investigate regressions.

A short workflow

  1. Pull field data. Find the worst route by LCP, INP, or CLS.
  2. Reproduce in DevTools with a throttled CPU and slow 4G profile.
  3. Pick one metric to move. Do not chase all three at once.
  4. Apply one or two fixes. Measure in production after a week of field data.
  5. Repeat.

Treat Web Vitals like any other SLO. Pick budgets, alert on regressions, and review them in the same meeting where you talk about uptime.

Wrap up

LCP, INP, and CLS turn user experience into something you can ship against. Most teams find the same culprits: heavy JavaScript, oversized images, and surprise layout shifts from late-loading content. Fix those and the numbers move. Keep field measurement on and the regressions stop sneaking in.