Core Web Vitals 2026 — Optimizing LCP, INP and CLS for Faster Websites
Posted on: 4/27/2026 4:11:58 AM
Table of contents
- 1. What Are Core Web Vitals and Why Do They Matter More Than Ever?
- 2. LCP — Optimizing Main Content Display Speed
- 3. INP — Optimizing Interaction Responsiveness
- 4. CLS — Eliminating Layout Shifts
- 5. Speculation Rules API — Near-Instant Navigation
- 6. View Transitions API — Smooth Page Transitions Without SPAs
- 7. Islands Architecture — Hydrate Only What's Needed
- 8. Back/Forward Cache — Instant Back Navigation
- 9. Continuous Measurement and Monitoring
- 10. Core Web Vitals 2026 Optimization Checklist
- References
1. What Are Core Web Vitals and Why Do They Matter More Than Ever?
Core Web Vitals (CWV) are three metrics that measure real-world user experience on the web, defined by Google and used directly in search ranking algorithms. After the December 2025 Core Update, Google tightened evaluation thresholds and increased CWV weight in rankings — turning optimization from "nice-to-have" into a must-have for competitive search visibility.
In 2026, the three Core Web Vitals are:
Key Takeaway
INP officially replaced FID (First Input Delay) in March 2024. Unlike FID which only measured the first interaction, INP measures every interaction throughout the page lifecycle and reports the worst value at the 75th percentile — much harder to game and far more representative of actual user experience.
2. LCP — Optimizing Main Content Display Speed
Largest Contentful Paint measures the time from when the user starts loading the page until the largest content element (hero image, heading, video poster) is fully rendered in the viewport. This is the most visible metric — if the page stays "blank" too long, users will bounce.
2.1. Make LCP Resources Discoverable from HTML
According to HTTP Archive data, 73% of mobile pages have images as their LCP element, but 35% of those don't have the image URL in the initial HTML — the browser must execute JavaScript before knowing which image to load. This is the biggest LCP killer.
<!-- BAD: Browser can't preload because URL is in JS -->
<div class="hero" data-bg="/images/hero.avif"></div>
<!-- GOOD: URL is directly in HTML, browser can scan immediately -->
<img src="/images/hero.avif"
width="1200" height="600"
fetchpriority="high"
alt="Hero banner">
2.2. Using fetchpriority and Preload
Only 15% of websites currently use the fetchpriority attribute — a surprisingly low number for such a simple yet highly effective optimization. This attribute tells the browser which resources to prioritize downloading.
<!-- High priority for LCP image -->
<img src="/hero.avif" fetchpriority="high" alt="Banner">
<!-- Preload with fetchpriority for responsive images -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high"
imagesrcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1200.avif 1200w"
imagesizes="100vw">
<!-- Preconnect to third-party CDN -->
<link rel="preconnect" href="https://cdn.example.com">
Important Warning
Do NOT add loading="lazy" to the LCP element. Lazy loading delays image loading until it's near the viewport — the exact opposite of the LCP goal. Only lazy-load below-the-fold images.
2.3. Optimizing Image Formats: AVIF and JXL
In 2026, AVIF has become the new image compression standard replacing WebP, with 30-50% better compression ratios. JPEG XL (JXL) is also gaining broader support with the advantage of progressive decoding.
<picture>
<source srcset="/hero.jxl" type="image/jxl">
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" width="1200" height="600"
fetchpriority="high" alt="Hero banner">
</picture>
| Format | Compression (vs JPEG) | Progressive Decode | Browser Support |
|---|---|---|---|
| JPEG | Baseline | Yes | 100% |
| WebP | ~25-35% better | No | ~97% |
| AVIF | ~50% better | No | ~93% |
| JPEG XL | ~55-60% better | Yes | ~72% (growing) |
2.4. Server-Side Rendering at the Edge
Moving HTML rendering from central servers to edge functions (Cloudflare Workers, Vercel Edge, Deno Deploy) significantly reduces TTFB — a direct factor affecting LCP. When HTML is generated at the CDN node closest to the user, network latency drops from 200-500ms to 20-50ms.
// Cloudflare Workers — SSR at the Edge
export default {
async fetch(request, env) {
const url = new URL(request.url);
const cache = caches.default;
// Check edge cache first
let response = await cache.match(request);
if (response) return response;
// Render HTML at edge
const html = await renderPage(url.pathname, env);
response = new Response(html, {
headers: {
'Content-Type': 'text/html;charset=utf-8',
'Cache-Control': 's-maxage=60, stale-while-revalidate=600',
}
});
// Store in edge cache
await cache.put(request, response.clone());
return response;
}
};
3. INP — Optimizing Interaction Responsiveness
Interaction to Next Paint measures the time from when a user interacts (click, tap, key press) to when the browser paints the response. INP records all interactions throughout the session and reports the value at the 75th percentile — meaning 75% of interactions must be faster than the 200ms threshold.
graph LR
A["User clicks"] --> B["Input Delay"]
B --> C["Processing Time"]
C --> D["Presentation Delay"]
D --> E["Next Paint"]
style A fill:#e94560,stroke:#fff,color:#fff
style B fill:#ff9800,stroke:#fff,color:#fff
style C fill:#2c3e50,stroke:#fff,color:#fff
style D fill:#ff9800,stroke:#fff,color:#fff
style E fill:#4CAF50,stroke:#fff,color:#fff
Figure 1: INP time breakdown — Input Delay + Processing Time + Presentation Delay
3.1. Breaking Long Tasks with scheduler.yield()
When a JavaScript task runs longer than 50ms, it becomes a "Long Task" — blocking the main thread and preventing the browser from responding to interactions. The solution: break tasks into smaller chunks and yield back to the browser between steps.
// BAD: One long task blocking main thread for 300ms
function processAllItems(items) {
for (const item of items) {
heavyComputation(item); // 300ms blocked!
}
}
// GOOD: Yield between items so browser can handle interactions
async function processAllItems(items) {
for (const item of items) {
heavyComputation(item);
// Yield to browser after each item
if (navigator.scheduling?.isInputPending?.() ||
performance.now() - startTime > 50) {
await scheduler.yield();
}
}
}
// ALT: Use requestIdleCallback for non-urgent tasks
function processInBackground(items) {
let index = 0;
function chunk(deadline) {
while (index < items.length && deadline.timeRemaining() > 5) {
heavyComputation(items[index++]);
}
if (index < items.length) {
requestIdleCallback(chunk);
}
}
requestIdleCallback(chunk);
}
3.2. Code Splitting and Tree Shaking
Unused JavaScript is INP's number one enemy. Use Chrome DevTools Coverage tool to detect unused code, then apply route-based code splitting.
// Vue.js — Lazy load components per route
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
},
{
path: '/settings',
component: () => import('./views/Settings.vue')
}
];
// Dynamic import for heavy components
const HeavyChart = defineAsyncComponent(() =>
import('./components/HeavyChart.vue')
);
3.3. Web Workers for Heavy Computation
Offload computationally intensive tasks (image processing, large data parsing, crypto) to Web Workers — separate threads that don't block the main thread.
// main.js — Send task to Worker
const worker = new Worker('/heavy-worker.js');
worker.postMessage({ type: 'PARSE_CSV', data: rawCsvData });
worker.onmessage = (e) => {
const parsedData = e.data;
updateUI(parsedData); // Main thread only updates UI
};
// heavy-worker.js — Heavy processing in separate thread
self.onmessage = (e) => {
if (e.data.type === 'PARSE_CSV') {
const result = parseAndTransform(e.data.data);
self.postMessage(result);
}
};
4. CLS — Eliminating Layout Shifts
Cumulative Layout Shift measures how much page elements "jump" during loading. When an ad banner or cookie consent pushes content down, users reading suddenly lose their place — that's high CLS. The good threshold is below 0.1.
4.1. Always Declare Image and iframe Dimensions
According to HTTP Archive, 66% of web pages have at least one unsized image. This is the most common and easiest-to-fix CLS cause.
<!-- BAD: Browser doesn't know dimensions, causes layout shift when image loads -->
<img src="/photo.jpg" alt="Photo">
<!-- GOOD: Declare width/height, browser reserves space immediately -->
<img src="/photo.jpg" width="800" height="600" alt="Photo">
<!-- GOOD: Use aspect-ratio for responsive images -->
<img src="/photo.jpg" style="aspect-ratio: 4/3; width: 100%;" alt="Photo">
<!-- Reserve space for dynamic content -->
<div style="min-height: 250px;">
<!-- Ads or async-loaded content -->
</div>
4.2. Cookie Consent: Overlay Instead of Push
Cookie consent banners that push content down are one of the most common CLS sources in 2026. The solution: use fixed/sticky positioning to overlay content instead of inserting at the top of the DOM.
/* BAD: Banner inserted at top of body, pushes everything down */
.consent-banner {
width: 100%;
padding: 16px;
/* No position: fixed → causes layout shift */
}
/* GOOD: Overlay doesn't affect layout */
.consent-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
z-index: 1000;
background: #fff;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
}
4.3. Use transform Instead of Layout Properties
Animations using top, left, margin, width trigger layout recalculation — causing CLS. Use transform and opacity to run animations on the GPU compositor without affecting layout.
/* BAD: Changing layout property → CLS */
@keyframes slideIn {
from { left: -100%; }
to { left: 0; }
}
/* GOOD: Transform runs on compositor, no CLS */
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
/* Containment — tell browser this element is independent */
.card {
contain: layout style paint;
content-visibility: auto;
contain-intrinsic-size: 0 200px;
}
5. Speculation Rules API — Near-Instant Navigation
The Speculation Rules API allows browsers to prefetch or prerender destination pages before the user actually clicks a link. The result: when the user clicks, the page is already ready in memory — LCP approaches zero.
<!-- Prefetch: Pre-load destination page resources -->
<script type="speculationrules">
{
"prefetch": [
{
"source": "list",
"urls": ["/pricing", "/features", "/docs"]
}
]
}
</script>
<!-- Prerender: Fully render page in background -->
<script type="speculationrules">
{
"prerender": [
{
"source": "document",
"where": {
"and": [
{"href_matches": "/*"},
{"not": {"href_matches": "/logout"}}
]
},
"eagerness": "moderate"
}
]
}
</script>
| Eagerness | Trigger Timing | Use Case |
|---|---|---|
| immediate | As soon as rule is parsed | Pages certain to be visited (checkout flow) |
| eager | As early as possible | Primary CTAs, prominent links |
| moderate | After hovering link for 200ms | Regular navigation links |
| conservative | When click/tap begins | Bandwidth-saving, mobile |
Combine with View Transitions API
When using Speculation Rules together with the View Transitions API, you can create smooth page transitions like a native app — pre-rendered pages + GPU-accelerated transition animations. This is the most powerful combo for Multi-Page Applications in 2026.
6. View Transitions API — Smooth Page Transitions Without SPAs
Previously, smooth page transition animations required SPA frameworks (Vue Router, React Router). The View Transitions API brings this capability to Multi-Page Applications (MPA) — the browser automatically creates a snapshot of the old page and animates to the new one.
/* CSS for cross-document view transitions */
@view-transition {
navigation: auto; /* Enable for all same-origin navigations */
}
/* Custom animation for specific elements */
.hero-image {
view-transition-name: hero;
}
::view-transition-old(hero) {
animation: fadeOut 0.3s ease-out;
}
::view-transition-new(hero) {
animation: fadeIn 0.3s ease-in;
}
/* Default animation for entire page */
::view-transition-old(root) {
animation: slide-out 0.25s ease;
}
::view-transition-new(root) {
animation: slide-in 0.25s ease;
}
7. Islands Architecture — Hydrate Only What's Needed
Islands Architecture is a rendering model where most of the page is static HTML (the ocean), while interactive components are individual "islands" that get hydrated separately. Instead of loading and executing JavaScript for the entire page, the browser only hydrates the parts that actually need interactivity.
graph TD
A["Complete Web Page"] --> B["Static HTML Ocean"]
A --> C["Interactive Islands"]
B --> D["Header - Static"]
B --> E["Article Content - Static"]
B --> F["Footer - Static"]
C --> G["Search Bar - Hydrate on load"]
C --> H["Comment Form - Hydrate on visible"]
C --> I["Share Button - Hydrate on idle"]
style A fill:#e94560,stroke:#fff,color:#fff
style B fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style C fill:#4CAF50,stroke:#fff,color:#fff
style G fill:#ff9800,stroke:#fff,color:#fff
style H fill:#ff9800,stroke:#fff,color:#fff
style I fill:#ff9800,stroke:#fff,color:#fff
Figure 2: Islands Architecture — Static HTML as the base, only hydrating interactive components
<!-- Astro — Islands with hydration directives -->
<!-- Hydrate immediately on page load (critical interactive) -->
<SearchBar client:load />
<!-- Hydrate when component enters viewport -->
<CommentSection client:visible />
<!-- Hydrate when browser is idle -->
<ShareButtons client:idle />
<!-- Only hydrate on wide enough screens -->
<DesktopSidebar client:media="(min-width: 768px)" />
8. Back/Forward Cache — Instant Back Navigation
bfcache (Back/Forward Cache) stores a complete page snapshot in memory when the user navigates away. When pressing Back or Forward, the page is instantly restored from memory — LCP = 0, CLS = 0. This is the highest-ROI optimization that many developers overlook.
Factors that prevent bfcache from working:
// BAD: These prevent bfcache
// 1. Using the unload event
window.addEventListener('unload', () => {
// Prevents bfcache! Use pagehide instead
});
// 2. Cache-Control: no-store
// This header prevents bfcache on some browsers
// 3. Window.opener reference
window.open(url); // Pages opened with opener don't bfcache
// GOOD: Use pagehide + pageshow
window.addEventListener('pagehide', (event) => {
if (event.persisted) {
// Page will enter bfcache, cleanup connections
}
});
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
// Page restored from bfcache
refreshStaleData();
}
});
9. Continuous Measurement and Monitoring
Optimization means nothing without measurement. Combine Lab data (Lighthouse, WebPageTest) to discover issues during development with Field data (CrUX, RUM) to measure real user experience.
| Tool | Type | Advantage | Free |
|---|---|---|---|
| Lighthouse | Lab | Integrated in Chrome DevTools, CI/CD | Yes |
| PageSpeed Insights | Lab + Field | Combines CrUX real-user data | Yes |
| WebPageTest | Lab | Test from multiple locations, connections | Yes |
| CrUX Dashboard | Field | Real data from Chrome users | Yes |
| web-vitals.js | RUM | Collect CWV from your own site | Yes |
// Integrate web-vitals for Real User Monitoring
import { onLCP, onINP, onCLS } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // "good", "needs-improvement", "poor"
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType,
url: location.href,
});
// Use sendBeacon to avoid performance impact
navigator.sendBeacon('/api/vitals', body);
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
10. Core Web Vitals 2026 Optimization Checklist
Here's a prioritized checklist of techniques — from easy/high-impact to more complex:
| # | Technique | Metric | Difficulty | Impact |
|---|---|---|---|---|
| 1 | Declare width/height on all images | CLS | Low | High |
| 2 | fetchpriority="high" for LCP image | LCP | Low | High |
| 3 | Remove loading="lazy" from LCP image | LCP | Low | High |
| 4 | Convert images to AVIF/JXL | LCP | Medium | High |
| 5 | Inline critical CSS | LCP | Medium | High |
| 6 | Route-based code splitting | INP | Medium | High |
| 7 | Cookie consent overlay (fixed) | CLS | Low | Medium |
| 8 | Speculation Rules prefetch | LCP | Low | Very High |
| 9 | scheduler.yield() for long tasks | INP | Medium | High |
| 10 | Web Workers for heavy computation | INP | High | High |
| 11 | SSR at Edge | LCP | High | Very High |
| 12 | Ensure bfcache eligibility | LCP + CLS | Medium | Very High |
Where to Start?
Run PageSpeed Insights on your most important page. Check the "Diagnostics" section to see exactly which issues are affecting each metric. Prioritize fixes with High Impact and Low Difficulty first — often just 30 minutes can significantly improve your scores.
References
- The most effective ways to improve Core Web Vitals — web.dev
- Speculative Loading — MDN Web Docs
- Understanding Core Web Vitals and Google Search Results — Google
- The Ultimate Guide to Modern Web Performance Optimization in 2026 — DEV Community
- The Most Important Core Web Vitals Metrics in 2026 — NitroPack
Azure Container Apps Dynamic Sessions — Hyper-V Sandbox for Secure AI Agent Code Execution
Cloudflare Pages — Free Full-Stack Deployment on the Global Edge
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.