All posts
·9 min read

Core Web Vitals Explained: LCP, CLS, and INP (With Fixes)

Google uses Core Web Vitals as ranking signals. This guide explains exactly what LCP, CLS, and INP measure, what counts as a good score, and how to fix each one.

Google confirmed Core Web Vitals as a ranking signal in 2021 and has been expanding their weight ever since. Unlike most ranking factors, these are measurable, specific, and fixable — which makes them rare and valuable.

There are three metrics. Each has a specific threshold. Here is what they measure, why they matter, and how to move the needle on each one.

LCP — Largest Contentful Paint

LCP measures when the largest visible element on the page finishes loading. This is typically a hero image, a large heading, or a background image. It's Google's proxy for "when does the page feel loaded?"

Thresholds: Good = under 2.5s · Needs improvement = 2.5–4s · Poor = over 4s

LCP is measured at the 75th percentile of real user sessions (CrUX field data).

What causes slow LCP

  • Hero image not preloaded — browser discovers it late in the render chain
  • Hero image served as WebP but with no width/height attributes — causes layout recalculation
  • Server response time (TTFB) over 600ms — the browser can't start rendering until HTML arrives
  • Render-blocking CSS or JS delaying First Contentful Paint
  • LCP image fetched from a slow third-party CDN

How to fix LCP

The single highest-impact fix for most sites: add <link rel="preload"> for your LCP image in the <head>:

<link rel="preload" as="image" href="/hero.webp"
  imagesrcset="/hero-480.webp 480w, /hero-800.webp 800w"
  imagesizes="100vw">

This tells the browser to fetch the image immediately, before it parses the CSS and discovers it. In practice this moves LCP 0.5–1.5 seconds earlier.

Other effective fixes:

  • Convert hero images to WebP (30–50% smaller than JPEG)
  • Set explicit width and height attributes on the LCP image to prevent layout shifts
  • Move to a faster host or CDN — if your TTFB is over 600ms, fix that first
  • Use fetchpriority="high" on the LCP image element

CLS — Cumulative Layout Shift

CLS measures visual stability: how much elements jump around as the page loads. If an ad loads and pushes the article down, that's a layout shift. If you click a button and an image loads above it causing your click to land on the wrong element, that's a layout shift.

Thresholds: Good = under 0.1 · Needs improvement = 0.1–0.25 · Poor = over 0.25

Common CLS causes

  • Images without dimensions — browser reserves no space, element shifts when image loads
  • Ads, embeds, iframes without reserved space — a 250px ad slot collapses to 0 then jumps
  • Web fonts causing FOUT/FOIT — text renders in fallback font then re-renders in web font at a different size
  • Dynamically injected content above existing content — cookie banners, notification prompts
  • CSS transitions on layout properties — animating top, height, or margin

How to fix CLS

For images, always include explicit width and height:

<!-- Bad -->
<img src="hero.webp" alt="Hero">

<!-- Good -->
<img src="hero.webp" alt="Hero" width="1200" height="630">

For ad slots, reserve space in CSS with a fixed min-height. For web fonts, use font-display: optional (no FOUT, no shift) or font-display: swap with a closely-matched fallback font.

For cookie banners and popups: render them at the bottom of the viewport or as overlays, not inline content that pushes other elements.

INP — Interaction to Next Paint

INP replaced FID (First Input Delay) as a Core Web Vital in March 2024. It measures the delay between a user interaction (click, tap, keypress) and when the browser paints the next frame in response. It's a better measure of responsiveness across the entire page lifetime, not just the first interaction.

Thresholds: Good = under 200ms · Needs improvement = 200–500ms · Poor = over 500ms

What causes high INP

  • Long-running JavaScript tasks blocking the main thread
  • Synchronous localStorage or IndexedDB operations in event handlers
  • Heavy re-renders in React triggered by a single interaction
  • Third-party scripts (analytics, chat widgets) running on every frame
  • Unoptimized event listeners with expensive DOM queries

How to fix INP

The goal is to keep the main thread free to respond. Strategies:

  • Break long tasks into smaller chunks using scheduler.postTask() or setTimeout(..., 0)
  • Use startTransition in React 18+ to defer non-urgent state updates
  • Avoid synchronous operations in click handlers — move them to requestIdleCallback
  • Use Chrome DevTools Performance tab → record a slow interaction → look for "Long Task" events over 50ms
  • Audit third-party scripts — these are the most common culprit. Load them after DOMContentLoaded

How Core Web Vitals affect rankings

Google uses CrUX (Chrome User Experience Report) field data — not lab data. This means your PageSpeed Insights score and your actual ranking signal can differ if your real users have different hardware or connection speeds than the lab simulation assumes.

The signal operates at the page group and domain level. Pages without sufficient CrUX data inherit the domain-level signal. Sites with consistently poor CWV across many pages face a domain-wide ranking penalty, not just per-page.

To check your field data: Google Search Console → Experience → Core Web Vitals report. This shows which URLs are in Poor, Needs Improvement, or Good status based on real user sessions — not lab measurements.

Scan your site and find these issues automatically

SEO Improvement detects render-blocking resources, gateway blocks, missing meta tags, and 20+ other issues — then gives you AI-generated steps to fix each one.

Scan your site free