Core Web Vitals 2026:
LCP, INP & CLS Thresholds
Summary
Core Web Vitals 2026 are Google's official performance metrics. INP replaced FID in March 2024, LCP measures loading time and CLS measures layout stability. Google judges each metric at the 75th percentile of real page loads. This guide shows how to fix each metric, with code examples and a checklist.
- Google judges each metric at the 75th percentile of real page loads
- INP (Interaction to Next Paint) replaced FID. It is stricter and 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
Only 48% of mobile websites reach good Core Web Vitals, according to the HTTP Archive Web Almanac 2025. INP (Interaction to Next Paint), which replaced FID in March 2024, is stricter than FID was, because it counts every interaction and not just the first one. This guide explains each of the three Core Web Vitals and shows how to measure and fix them, with working code examples and a checklist you can start on today.
Table of contents
What are Core Web Vitals?
Core Web Vitals are Google's official metrics for user experience on a website. There are three of them:
INP
Interaction to Next Paint: measures how quickly the page responds to user interactions
LCP
Largest Contentful Paint: measures how long the largest visible element takes to load
CLS
Cumulative Layout Shift: measures unexpected layout shifts
Why Core Web Vitals matter
- Core Web Vitals have been an official Google ranking factor since 2021.
- Poor scores go along with a higher bounce rate and fewer conversions.
- Google's evaluation is mobile-first, so mobile performance counts most.
- If your content is as good as a competitor's, the faster page has the edge.
| 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
The 2024 change
On March 12, 2024, Google made INP (Interaction to Next Paint) an official Core Web Vital, replacing FID (First Input Delay). INP is much stricter: it measures interactivity across the whole visit, where FID only looked at the first input.
What INP measures
INP measures the time from a user interaction (click, tap, keystroke) to the next visual feedback on screen. FID only measured the first interaction. INP records all interactions for as long as the visitor stays on the page.
FID (deprecated)
- Measured only the first interaction
- Input Delay only, not Processing + Paint
- Easy to optimize, but it told you little
- Showed only part of the user experience
INP (current)
- Observes all interactions and reports the slowest, ignoring outliers
- Complete cycle: Input + Processing + Paint
- Reflects what real users experience
- Gives a more realistic picture of interactivity
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 the first interaction or after 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. Usually that is a hero image, a large heading or a video poster.
Elements that can count as LCP:
- • <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)
A slow Time to First Byte delays LCP. Speed up your backend and hosting.
- • Use a CDN such as Cloudflare, Vercel Edge or AWS CloudFront
- • Cache on the server with Redis, Varnish or edge caching
- • Optimize the database with indexes and faster queries
- • Use HTTP/2 or HTTP/3 to multiplex parallel requests
- • Generate pages statically (Astro, Next.js SSG) instead of using 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 unexpected shifts of elements on the page. A high CLS value makes a page frustrating to use. You will know the effect: you go to click a button and the whole layout 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, #131824 25%, #1c2333 50%, #131824 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 the Chrome User Experience Report (CrUX).
- Google Search Console, in the Core Web Vitals report
- PageSpeed Insights, in the "Field Data" section
- CrUX Dashboard for historical data
- web-vitals.js for your own RUM setup
Lab Data (Synthetic)
Simulated tests in controlled environments.
- Lighthouse, built into Chrome DevTools
- PageSpeed Insights, in the "Lab Data" section
- WebPageTest for detailed waterfall analysis
- Debugbear for 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); Tip: attribution for debugging
The attribution build tells you which element caused a slow interaction and where the time went:
import { onINP } from 'web-vitals/attribution';
onINP((metric) => {
}); Advanced optimization techniques
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 -->
<script src="critical.js" fetchpriority="high"></script>
<script src="analytics.js" fetchpriority="low" defer></script> Speculation Rules API (Chromium browsers)
The browser prerenders likely next pages, so navigating to them is close to instant.
<script is:inline type="speculationrules">
{
"prerender": [{
"source": "list",
"urls": ["/about", "/contact", "/pricing"]
}],
"prefetch": [{
"source": "document",
"where": {
"href_matches": "/*"
},
"eagerness": "moderate"
}]
}
</script> View Transitions API
Animate page changes without causing layout shifts.
---
// src/layouts/Layout.astro (Astro 5 and later)
// <ClientRouter /> replaces the old <ViewTransitions /> component
import { ClientRouter } from 'astro:transitions';
---
<head>
<ClientRouter />
</head>
<!-- Animated elements -->
<h1 transition:name="page-title">Page Title</h1>
<img transition:name="hero" src="/hero.jpg" alt="" /> 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
The hero section below puts the LCP, CLS and INP techniques from this guide into one page:
<!-- 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://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: #0a0d14;
color: #e9edf3;
}
/* 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 you ship an improvement, expect 28-30 days before Search Console shows the new values in full. Larger websites may see the change a little sooner because they collect more user data.
My Lab data is good, but my Field data is bad. Why?
Lab tests run under controlled conditions, while Field data comes from real visitors. Possible reasons for the gap: your users have slower devices or poor internet connections (3G/4G), third-party scripts load for them but not in the lab, or geography plays a part because your server is far from your users.
Does the 75th percentile apply to all metrics?
Yes. Google uses the 75th percentile of page loads in the field data for all three Core Web Vitals. At least 75% of recorded page visits must reach the "Good" threshold for the page to pass, which means visits on slower devices count too.
How do poor Core Web Vitals affect rankings?
Core Web Vitals work as a tiebreaker signal. Content still matters more than performance, but when two pages offer equivalent content, the faster one wins. Poor CWV can put you at a disadvantage against competitors with similar content, and in competitive niches that disadvantage can be significant.
Do I need good CWV for every URL or just the domain?
Google evaluates at the URL level. When a URL doesn't have enough data of its own, Google uses aggregated data from similar pages. If traffic is low, Google falls back to the origin level (the entire domain). Optimize each of your important pages on its own.
Where to go from here
Core Web Vitals are an SEO signal, and they also show you how your site feels to use. Fix the scores and you usually see results like these:
- Higher Google rankings when content quality is comparable
- Fewer visitors leaving before the page has loaded
- Forms and checkouts that react without delay, which helps conversions
- A better experience on mobile phones, where most slow-page problems show up
- An edge over competitors whose pages still fail the thresholds
Start by measuring your current scores. Then tackle the biggest gap first (usually INP or LCP) and work through the checklist section by section. Most of the fixes take hours rather than weeks. If you would rather hand it off, our SEO agency in Hamburg includes Core Web Vitals in every engagement.
Sources & references
We checked and used these sources for this article:
Documentation
- 01
- 02
- 03
- 04
Need a Core Web Vitals audit?
We measure your site with field data and Lighthouse, find the bottlenecks and fix them, aiming for "Good" in all three Core Web Vitals. Start with a free initial consultation. You get a fixed-price quote afterwards.