Core Web Vitals 2026:
INP, LCP, CLS Complete Guide
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
47% of all websites fail Google's Core Web Vitals. Since INP (Interaction to Next Paint) replaced FID in March 2024, the requirements have become even stricter. This comprehensive guide shows you how to understand, measure, and optimize all three Core Web Vitals - with practical code examples and a complete checklist.
Table of Contents
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
LCP
Largest Contentful Paint - measures load time of the largest visible element
CLS
Cumulative Layout Shift - measures unexpected layout shifts
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 Strategies
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 (Chrome 2026)
Prerendering for nearly instant navigation.
<script type="speculationrules">
{
"prerender": [{
"source": "list",
"urls": ["/about", "/contact", "/pricing"]
}],
"prefetch": [{
"source": "document",
"where": {
"href_matches": "/*"
},
"eagerness": "moderate"
}]
}
</script> 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" /> 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.
Conclusion: Core Web Vitals as Competitive Advantage
Core Web Vitals are more than just an SEO metric - they're an indicator of user satisfaction. With the right optimizations you can:
- Achieve better Google rankings through technical excellence
- Reduce bounce rates by 20-30%
- Increase conversion rates by 10-25%
- Better serve mobile users (70%+ of traffic)
- Stand out from 47% of competitors who fail
Start by measuring your current values, prioritize the biggest problems, and work systematically through our checklist. With this guide, you have all the tools for top performance in 2026.
Sources & References
This article is based on the following verified sources:
Documentation
- 1.
- 2.
- 3. Optimizing Interaction to Next Paint External SourceGoogle • 2024
- 4.
Research
- 1. Core Web Vitals Technology Report - HTTP Archive External SourceHTTP Archive • 2026
Want Core Web Vitals Optimization?
We analyze your website and optimize all Core Web Vitals to "Good". With a guarantee for better scores.