Performance 22 min read UPDATED: January 12, 2026

Core Web Vitals 2026:
INP, LCP, CLS Complete Guide

Senorit
Core Web Vitals 2026: INP, LCP, CLS Masterclass

Summary

Core Web Vitals 2026 are Google's official performance metrics. INP replaced FID in March 2024, LCP measures loading time, CLS layout stability. 47% of websites fail the thresholds - this guide shows optimization with code examples and checklist.

  • 47% of websites fail Core Web Vitals (2026)
  • INP (Interaction to Next Paint) replaced FID - stricter, measures all interactions
  • Target values: INP ≤200ms, LCP ≤2.5s, CLS ≤0.1
  • Field Data (CrUX) counts for rankings, not Lab Data
  • 28-30 days until improvements are visible in Search Console

About the Author

Ebrahim Seyfi

Ebrahim Seyfi

Verified

Founder & Developer at Senorit | Full-Stack Developer since 2020

Published: January 12, 2026
Updated: February 1, 2026

Founder of Senorit in Hamburg. Specialized in web design, development and digital solutions for the DACH region. Full-Stack Developer with expertise in React, Next.js, Astro and TypeScript.

Expertise in:

Core Web Vitals INP LCP CLS Performance PageSpeed
Full-Stack Web Developer Core Web Vitals Specialist WCAG 2.2 Accessibility React, Astro & TypeScript

47% of all websites fail Google's Core Web Vitals. Since INP (Interaction to Next Paint) replaced FID in March 2024, the bar has moved. This guide covers all three Core Web Vitals - how to understand them, measure them, and fix them - with working code examples and a checklist you can act on today.

47%
Websites Fail
200ms
INP Threshold
2.5s
LCP Target
0.1
CLS Maximum

What are Core Web Vitals?

Core Web Vitals are Google's official metrics for user experience on a website. They measure three critical aspects of the user experience:

INP

Interaction to Next Paint - measures responsiveness to user interactions

Good: ≤ 200ms

LCP

Largest Contentful Paint - measures load time of the largest visible element

Good: ≤ 2.5s

CLS

Cumulative Layout Shift - measures unexpected layout shifts

Good: ≤ 0.1

Why are Core Web Vitals so important?

  • SEO Ranking: Core Web Vitals have been an official Google ranking factor since 2021
  • User Experience: Poor scores = higher bounce rate, fewer conversions
  • Mobile-First: Google primarily evaluates mobile performance
  • Competition: When content is equal, the faster page wins
Metric Good Needs Improvement Poor
INP ≤ 200ms 200-500ms > 500ms
LCP ≤ 2.5s 2.5-4.0s > 4.0s
CLS ≤ 0.1 0.1-0.25 > 0.25

INP: The New Metric Since March 2024

Important Change 2024

On March 12, 2024, Google introduced INP (Interaction to Next Paint) as the official Core Web Vital and replaced FID (First Input Delay). INP is significantly stricter and measures overall interactivity, not just the first input.

What does INP measure exactly?

INP measures the response time from user interaction (click, tap, keystroke) to visual feedback. Unlike FID, which only measured the first interaction, INP captures all interactions throughout the entire session.

FID (deprecated)

  • ✗ Only measured first interaction
  • ✗ Input Delay only, not Processing + Paint
  • ✗ Easy to optimize but not meaningful
  • ✗ User experience not fully represented

INP (current)

  • ✓ All interactions measured (75th percentile)
  • ✓ Complete cycle: Input + Processing + Paint
  • ✓ Real user experience represented
  • ✓ More realistic interactivity assessment

INP Optimization Techniques

1. Break up JavaScript Tasks

Long JavaScript tasks block the main thread. Split large functions into smaller chunks.

// BAD: One long task blocks the main thread
function processLargeArray(items) {
  items.forEach(item => heavyComputation(item));
}

// GOOD: Break tasks with requestIdleCallback
function processLargeArrayOptimized(items) {
  let index = 0;

  function processChunk(deadline) {
    while (index < items.length && deadline.timeRemaining() > 0) {
      heavyComputation(items[index]);
      index++;
    }

    if (index < items.length) {
      requestIdleCallback(processChunk);
    }
  }

  requestIdleCallback(processChunk);
}

2. Optimize Event Listeners

Use passive event listeners and debounce/throttle for scroll/resize events.

// BAD: Blocking scroll handler
document.addEventListener('scroll', handleScroll);

// GOOD: Passive listener + Throttling
document.addEventListener('scroll', throttle(handleScroll, 100), { passive: true });

function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  }
}

3. Defer Third-Party Scripts

Load analytics, chat widgets, and ads only after interaction or with a delay.

// Load only when user interacts or after 5 seconds
const loadThirdParty = () => {
  if (window.thirdPartyLoaded) return;
  window.thirdPartyLoaded = true;

  const script = document.createElement('script');
  script.src = 'https://third-party.example.com/widget.js';
  document.head.appendChild(script);
};

// On first interaction
['mousedown', 'touchstart', 'keydown'].forEach(event => {
  document.addEventListener(event, loadThirdParty, { once: true });
});

// Or after timeout
setTimeout(loadThirdParty, 5000);

LCP: Optimizing Largest Contentful Paint

LCP measures how long it takes for the largest visible element in the viewport to render. This is typically a hero image, a large heading, or a video poster.

LCP-relevant elements:

  • • <img> images
  • • <image> inside SVG
  • • <video> poster images
  • • Elements with background-image
  • • Block-level text elements (h1, p, etc.)
  • • <svg> graphics

The 5 Most Important LCP Optimizations

1. Preload the LCP Element

Load the LCP element as early as possible with a preload hint.

<!-- In <head> - BEFORE other resources -->
<link
  rel="preload"
  as="image"
  href="/hero-image.webp"
  fetchpriority="high"
/>

<!-- For responsive images -->
<link
  rel="preload"
  as="image"
  href="/hero-mobile.webp"
  media="(max-width: 768px)"
  fetchpriority="high"
/>

2. Use Optimized Image Formats

Use modern image formats and responsive images with srcset.

<picture>
  <!-- AVIF: Best compression, limited support -->
  <source
    type="image/avif"
    srcset="
      /hero-400.avif 400w,
      /hero-800.avif 800w,
      /hero-1200.avif 1200w
    "
    sizes="(max-width: 768px) 100vw, 50vw"
  />
  <!-- WebP: Good compression, broad support -->
  <source
    type="image/webp"
    srcset="
      /hero-400.webp 400w,
      /hero-800.webp 800w,
      /hero-1200.webp 1200w
    "
    sizes="(max-width: 768px) 100vw, 50vw"
  />
  <!-- Fallback for older browsers -->
  <img
    src="/hero-800.jpg"
    alt="Hero"
    width="1200"
    height="600"
    loading="eager"
    fetchpriority="high"
    decoding="async"
  />
</picture>

3. Optimize Server Response Time (TTFB)

Time to First Byte directly impacts LCP. Optimize your backend and hosting.

  • Use a CDN: Cloudflare, Vercel Edge, AWS CloudFront
  • Server-Side Caching: Redis, Varnish, or Edge Caching
  • Optimize Database: Indexes, query optimization
  • HTTP/2 or HTTP/3: Multiplexing for parallel requests
  • Static Generation: Astro, Next.js SSG instead of SSR

4. Eliminate Render-Blocking Resources

CSS and JavaScript can block rendering. Optimize the loading order.

<!-- Critical CSS inline -->
<style>
  /* Only necessary CSS for Above-the-Fold */
  .hero { ... }
  .nav { ... }
</style>

<!-- Load rest of CSS asynchronously -->
<link
  rel="preload"
  href="/styles.css"
  as="style"
  onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript><link rel="stylesheet" href="/styles.css"></noscript>

<!-- JavaScript defer or async -->
<script src="/app.js" defer></script>

5. Optimize Fonts

Web fonts can delay LCP when text is the LCP element.

<!-- Font preload with font-display: swap -->
<link
  rel="preload"
  href="/fonts/inter.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

<style>
  @font-face {
    font-family: 'Inter';
    src: url('/fonts/inter.woff2') format('woff2');
    font-display: swap; /* Shows fallback font until loaded */
    font-weight: 400;
  }
</style>

CLS: Understanding Cumulative Layout Shift

CLS measures how much elements on the page unexpectedly shift. A high CLS value means a frustrating user experience - everyone knows it: you want to click a button, and suddenly everything jumps.

The most common CLS causes:

  • ✗ Images without width/height attributes
  • ✗ Ads, embeds, iFrames without reserved space
  • ✗ Dynamically inserted content
  • ✗ Web fonts causing FOIT/FOUT
  • ✗ Animations without transform
  • ✗ Cookie banners that shift layout
  • ✗ Lazy-loaded content without placeholder
  • ✗ Sticky headers that shift other elements

CLS Optimization Best Practices

1. Always specify dimensions for images/videos

<!-- BAD: No dimensions -->
<img src="photo.jpg" alt="Photo">

<!-- GOOD: Explicit width/height -->
<img src="photo.jpg" alt="Photo" width="800" height="600">

<!-- ALSO GOOD: CSS aspect-ratio -->
<style>
  .responsive-image {
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    object-fit: cover;
  }
</style>
<img src="photo.jpg" alt="Photo" class="responsive-image">

2. Placeholders for dynamic content

<!-- Reserved space for ads -->
<div class="ad-container" style="min-height: 250px;">
  <!-- Ad loads here -->
</div>

<!-- Skeleton for lazy-loaded content -->
<div class="card-skeleton" style="height: 300px;">
  <div class="skeleton-image" style="height: 180px;"></div>
  <div class="skeleton-text"></div>
</div>

/* CSS for Skeleton */
.skeleton-image {
  background: linear-gradient(90deg, #1a1a1a 25%, #2a2a2a 50%, #1a1a1a 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

3. Use transform instead of position for animations

/* BAD: Shifts the layout */
.modal-open {
  top: 100px; /* Causes Layout Shift */
  left: 50px;
}

/* GOOD: Uses GPU, no Layout Shift */
.modal-open {
  transform: translateY(100px) translateX(50px);
}

/* BAD: Changes dimensions */
.expanded {
  width: 200px;
  height: 300px;
}

/* GOOD: Scales without layout change */
.expanded {
  transform: scale(1.2);
}

4. Implement cookie banners correctly

/* BAD: Banner shifts content */
.cookie-banner {
  position: relative; /* Takes space in flow */
}

/* GOOD: Fixed or Sticky, no shift */
.cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 9999;
}

/* ALSO GOOD: Overlay instead of banner */
.cookie-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.8);
}

Measurement and Monitoring

There are two types of Core Web Vitals data: Field Data (real user data) and Lab Data (simulated tests). For Google rankings, only Field Data counts!

Field Data (RUM)

Real user data from Chrome User Experience Report (CrUX).

  • Google Search Console: Core Web Vitals Report
  • PageSpeed Insights: "Field Data" section
  • CrUX Dashboard: Historical data
  • web-vitals.js: Custom RUM implementation

Lab Data (Synthetic)

Simulated tests in controlled environments.

  • Lighthouse: Integrated in Chrome DevTools
  • PageSpeed Insights: "Lab Data" section
  • WebPageTest: Detailed waterfall analysis
  • Debugbear: Continuous monitoring

Implementing web-vitals.js

// Installation: npm install web-vitals

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

// Send to analytics
function sendToAnalytics(metric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    delta: metric.delta,
    id: metric.id,
    rating: metric.rating, // 'good', 'needs-improvement', or 'poor'
  });

  // Beacon API for reliable transmission
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/analytics', body);
  } else {
    fetch('/analytics', { body, method: 'POST', keepalive: true });
  }
}

// Measure all Core Web Vitals
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onCLS(sendToAnalytics);

Pro Tip: Attribution for Debugging

Use the attribution builds for detailed debug information:

import { onINP } from 'web-vitals/attribution';

onINP((metric) => {

});

Advanced Optimization Techniques

1

Priority Hints for Resources

With fetchpriority you can tell the browser which resources are important.

<!-- Hero image with high priority -->
<img src="hero.jpg" fetchpriority="high" alt="Hero">

<!-- Below-the-fold images with low priority -->
<img src="footer-logo.png" fetchpriority="low" loading="lazy" alt="Logo">

<!-- Scripts -->
&lt;script src="critical.js" fetchpriority="high"&gt;&lt;/script&gt;
&lt;script src="analytics.js" fetchpriority="low" defer&gt;&lt;/script&gt;
2

Speculation Rules API (Chrome 2026)

Prerendering for nearly instant navigation.

<script is:inline type="speculationrules">
{
  "prerender": [{
    "source": "list",
    "urls": ["/about", "/contact", "/pricing"]
  }],
  "prefetch": [{
    "source": "document",
    "where": {
      "href_matches": "/*"
    },
    "eagerness": "moderate"
  }]
}
</script>
3

View Transitions API

Smooth transitions between pages without Layout Shifts.

// In Astro 4.0+
---
// In astro.config.mjs
export default defineConfig({
  experimental: {
    viewTransitions: true
  }
});
---

<!-- In your Layout -->
<head>
  <ViewTransitions />
</head>

<!-- Animated elements -->
<h1 transition:name="page-title">Page Title</h1>
<img transition:name="hero" src="/hero.jpg" />
4

Scheduler API for JavaScript

Prioritize JavaScript tasks for better INP values.

// High priority: User-visible updates
scheduler.postTask(() => {
  updateUI();
}, { priority: 'user-blocking' });

// Low priority: Analytics, logging
scheduler.postTask(() => {
  sendAnalytics();
}, { priority: 'background' });

// Yielding to Main Thread
async function processItems(items) {
  for (const item of items) {
    processItem(item);
    await scheduler.yield(); // Release main thread
  }
}

Complete Code Example: Optimized Hero Section

Here is a fully optimized hero section example that implements all Core Web Vitals best practices:

<!-- HTML: Optimized Hero Section -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <!-- Preconnect for external resources -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://cdn.example.com" crossorigin>

  <!-- Preload critical resources -->
  <link
    rel="preload"
    as="image"
    href="/hero-mobile.webp"
    media="(max-width: 768px)"
    fetchpriority="high"
  >
  <link
    rel="preload"
    as="image"
    href="/hero-desktop.webp"
    media="(min-width: 769px)"
    fetchpriority="high"
  >
  <link rel="preload" as="font" href="/fonts/inter.woff2" type="font/woff2" crossorigin>

  <!-- Critical CSS inline -->
  <style>
    /* Reset and Critical Styles */
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    @font-face {
      font-family: 'Inter';
      src: url('/fonts/inter.woff2') format('woff2');
      font-display: swap;
    }

    body {
      font-family: 'Inter', system-ui, sans-serif;
      background: #0a0a0a;
      color: #fff;
    }

    /* Hero with fixed dimensions for CLS */
    .hero {
      min-height: 100vh;
      display: flex;
      align-items: center;
      position: relative;
      overflow: hidden;
    }

    .hero-image {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    .hero-content {
      position: relative;
      z-index: 1;
      max-width: 1200px;
      margin: 0 auto;
      padding: 2rem;
    }

    /* Animations with transform (no Layout Shift) */
    .hero-title {
      opacity: 0;
      transform: translateY(20px);
      animation: fadeInUp 0.6s ease-out 0.1s forwards;
    }

    @keyframes fadeInUp {
      to { opacity: 1; transform: translateY(0); }
    }

    /* Button with fixed size */
    .cta-button {
      display: inline-block;
      padding: 1rem 2rem;
      min-width: 200px;
      text-align: center;
      /* ... more styles */
    }
  </style>
</head>
<body>
  <section class="hero">
    <!-- Hero image with all optimizations -->
    <picture>
      <source
        type="image/webp"
        media="(max-width: 768px)"
        srcset="/hero-mobile.webp"
      >
      <source
        type="image/webp"
        media="(min-width: 769px)"
        srcset="/hero-desktop.webp"
      >
      <img
        src="/hero-desktop.jpg"
        alt="Hero Image"
        class="hero-image"
        width="1920"
        height="1080"
        fetchpriority="high"
        decoding="async"
      >
    </picture>

    <div class="hero-content">
      <h1 class="hero-title">Your Headline Here</h1>
      <a href="/contact" class="cta-button">Get Started</a>
    </div>
  </section>

  <!-- Non-critical CSS async -->
  <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">

  <!-- JavaScript defer -->
  <script src="/app.js" defer></script>
</body>
</html>

Core Web Vitals Checklist 2026

INP Optimization

LCP Optimization

CLS Optimization

Frequently Asked Questions

How long until improvements are visible in Search Console?

Google uses a 28-day rolling average of CrUX data. After an improvement, it typically takes 28-30 days for the new values to be fully reflected in Search Console. For larger websites, it may be slightly faster as more user data is collected.

My Lab data is good, but Field data is bad - why?

Lab tests are conducted under controlled conditions, while Field data represents real user experiences. Possible reasons: Slower devices of your users, poor internet connections (3G/4G), Third-Party Scripts that aren't loaded in lab, or geographic differences (server far from users).

Does the 75th percentile apply to all metrics?

Yes, Google uses the 75th percentile for all Core Web Vitals. This means: 75% of your users must reach the "Good" thresholds for the page to pass. This ensures the majority of users have a good experience, not just those with fast devices.

How do poor Core Web Vitals affect rankings?

Core Web Vitals are a tiebreaker signal: when content is equivalent, the faster page wins. Content remains more important than performance, but poor CWV can disadvantage you against competitors with similar content. In competitive niches, the difference can be significant.

Do I need good CWV for every URL or just the domain?

Google evaluates at the URL level, but uses aggregated data from similar pages when there isn't enough URL-specific data available. With low traffic, the Origin-level (entire domain) evaluation is used. Ideally, you should optimize all important pages individually.

Where to Go from Here

Core Web Vitals aren't just an SEO signal - they tell you how your site actually feels to use. Fix the scores and you typically see real downstream effects:

  • Higher Google rankings when content quality is comparable
  • Lower bounce rates - typically 20-30%
  • Conversion rate improvements of 10-25%
  • Better experience for mobile users (70%+ of traffic)
  • An edge over the 47% of sites still failing the thresholds

Start by measuring your current scores. Then prioritize the biggest gap - usually INP or LCP - and work through the checklist section by section. Most of the fixes take hours, not weeks.

Need a Core Web Vitals Audit?

We audit your site, find the bottlenecks, and optimize all three Core Web Vitals to "Good" - with a written commitment to the outcome.