DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Core Web Vitals: LCP, INP, and CLS — What They Measure and How to Fix Them

Core Web Vitals are Google's set of performance metrics that directly affect search ranking. More importantly, they measure real user experience: whether your page loads visibly fast, responds quickly to interaction, and stays stable as it renders.

The three metrics, what they measure, and how to improve them.

The Three Core Web Vitals

LCP — Largest Contentful Paint

Measures when the largest visible element (hero image, h1, article body) finishes rendering. The goal is under 2.5 seconds.

LCP is the "does the page look done?" metric. It's what users notice when they're waiting — the blank space before the main content appears.

What hurts LCP:

  • Large, unoptimized hero images
  • Server response time (TTFB)
  • Render-blocking scripts and stylesheets
  • Client-side rendering (JS renders the content)

How to improve LCP:

Optimize the LCP element itself:

<!-- Preload the LCP image -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">

<!-- Use modern formats -->
<picture>
  <source srcset="/hero.avif" type="image/avif">
  <source srcset="/hero.webp" type="image/webp">
  <img src="/hero.jpg" alt="Hero" width="1200" height="600">
</picture>
Enter fullscreen mode Exit fullscreen mode

Remove render-blocking resources:

<!-- Non-critical CSS: load async -->
<link rel="preload" href="/styles/non-critical.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'">

<!-- Defer non-critical scripts -->
<script src="/analytics.js" defer></script>
Enter fullscreen mode Exit fullscreen mode

INP — Interaction to Next Paint (replaced FID in 2024)

Measures the latency between user interaction (click, tap, keypress) and the next visual update. Goal: under 200ms.

INP replaced First Input Delay (FID) in March 2024. FID only measured the first interaction; INP measures all interactions throughout the page lifecycle.

What hurts INP:

  • Long JavaScript tasks blocking the main thread
  • Heavy event handlers
  • Synchronous layout reads during interaction (layout thrashing)

How to improve INP:

Break up long tasks:

// Bad: one long synchronous task
function processLargeList(items) {
  items.forEach(item => heavyOperation(item));
}

// Better: yield to the browser between chunks
async function processLargeList(items) {
  const CHUNK_SIZE = 50;
  for (let i = 0; i < items.length; i += CHUNK_SIZE) {
    const chunk = items.slice(i, i + CHUNK_SIZE);
    chunk.forEach(item => heavyOperation(item));
    await new Promise(r => setTimeout(r, 0));  // yield
  }
}
Enter fullscreen mode Exit fullscreen mode

Use scheduler.yield() when available (Chrome 115+, proposal stage for others):

async function handleClick() {
  doUrgentWork();
  await scheduler.yield();  // let browser render first
  doNonUrgentWork();
}
Enter fullscreen mode Exit fullscreen mode

Avoid layout thrashing:

// Bad: alternating read/write forces multiple layout recalculations
elements.forEach(el => {
  const height = el.offsetHeight;  // read
  el.style.height = height + 10 + 'px';  // write → forces layout
});

// Good: batch reads, then batch writes
const heights = elements.map(el => el.offsetHeight);  // all reads
elements.forEach((el, i) => {
  el.style.height = heights[i] + 10 + 'px';  // all writes
});
Enter fullscreen mode Exit fullscreen mode

CLS — Cumulative Layout Shift

Measures unexpected visual shifts — elements jumping around as the page loads. Goal: under 0.1.

CLS is the "why did the button move when I was about to click it?" metric.

What causes CLS:

  • Images without dimensions
  • Ads, embeds, iframes without reserved space
  • Dynamically injected content above existing content
  • Web fonts causing FOIT/FOUT

How to fix CLS:

Always set image dimensions:

<!-- Reserve space before image loads -->
<img src="photo.jpg" width="800" height="450" alt="...">

<!-- Or use CSS aspect-ratio -->
<style>
.image-wrapper {
  aspect-ratio: 16 / 9;
  width: 100%;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Reserve space for ads and embeds:

.ad-slot {
  min-height: 250px;  /* reserve before ad loads */
}
Enter fullscreen mode Exit fullscreen mode

Prevent font-swap layout shift:

@font-face {
  font-family: 'MyFont';
  src: url('myfont.woff2') format('woff2');
  font-display: optional;  /* don't show fallback if font loads quickly */
}
Enter fullscreen mode Exit fullscreen mode

Or use size-adjust to match fallback font metrics:

@font-face {
  font-family: 'FallbackFont';
  src: local('Arial');
  size-adjust: 103%;  /* scale fallback to match custom font */
  ascent-override: 90%;
}
Enter fullscreen mode Exit fullscreen mode

Measuring Core Web Vitals

In Firefox DevTools

Firefox's Performance profiler doesn't show CWV directly, but you can measure:

  • LCP: Watch the timeline for when the largest paint fires
  • CLS: Check Layout panel for unexpected reflows
  • INP: Performance recording shows main thread blocking during interaction

For accurate field measurements, use PageSpeed Insights (which uses real Chrome User Experience data) or install the Web Vitals extension.

In Code (JavaScript)

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

onLCP(metric => {
  console.log('LCP:', metric.value, metric.rating);  // 'good', 'needs-improvement', 'poor'
});

onINP(metric => {
  console.log('INP:', metric.value);
});

onCLS(metric => {
  console.log('CLS:', metric.value);
});
Enter fullscreen mode Exit fullscreen mode

The web-vitals library (from Google) is the reference implementation.

Lighthouse

Run from Chrome DevTools → Lighthouse tab, or via CLI:

npx lighthouse https://yoursite.com --output=html --view
Enter fullscreen mode Exit fullscreen mode

Lighthouse scores are lab measurements (controlled environment). They don't match field data (real users on varied connections). Both matter — lab for reproducible testing, field for actual impact.

Priority Order for Fixes

  1. LCP first — it's the most visible to users and usually has the biggest impact
  2. CLS second — easy to fix (add image dimensions) and directly affects usability
  3. INP last — hardest to fix, requires JS profiling, but matters for interactive apps

Quick LCP Checklist

  • [ ] LCP element is an image? Preload it with fetchpriority="high"
  • [ ] Image in WebP or AVIF format?
  • [ ] Image properly sized (not 4000px wide for a 800px slot)?
  • [ ] Server response under 800ms? (Check TTFB in Network panel)
  • [ ] No render-blocking scripts before the LCP element?
  • [ ] CDN serving static assets?

Quick CLS Checklist

  • [ ] All images have width and height attributes?
  • [ ] Ad slots have min-height reserved?
  • [ ] No content injected above the fold after initial render?
  • [ ] Font font-display: optional or swap with size-adjust?

Core Web Vitals scores are now a Google search ranking factor — not the dominant one, but measurable. More importantly, improving them correlates with higher conversion rates: Google's data shows that sites meeting "Good" thresholds for all three metrics have significantly lower bounce rates than those that don't.

Top comments (0)