Core Web Vitals Explained

Learn what Core Web Vitals are and how LCP, INP, and CLS measure page loading speed, interactivity, and visual stability. Understand thresholds, optimization strategies, SEO impact, and how to improve real user experience.

Core Web Vitals are Google’s standardized metrics for measuring user experience on web pages. The three metrics—Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS)—quantify loading speed, interactivity, and visual stability, directly influencing search rankings since 2021.

How Core Web Vitals Work

Core Web Vitals measure real-world user experience through field data collected from actual page loads (Chrome User Experience Report). Unlike synthetic lab tests, these metrics reflect what users actually experience, making them critical for both SEO and user satisfaction.

┌──────────────────────────────────────────────────────────────────┐
│ Core Web Vitals Framework │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ LCP │ │ INP │ │ CLS │ │
│ │ Loading │ │ Interactivity │ │ Stability │ │
│ │ │ │ │ │ │ │
│ │ "How fast does │ │ "How quickly │ │ "Do elements │ │
│ │ content appear"│ │ does the page │ │ jump around?" │ │
│ │ │ │ respond?" │ │ │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Google Search Ranking Factor ││
│ │ (Page Experience Signal since May 2021) ││
│ └─────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────┘

The Three Core Metrics

1. Largest Contentful Paint (LCP)

Measures loading performance by tracking when the largest visible content element renders in the viewport.

RatingThresholdUser Perception
Good≤2.5 secondsContent feels fast
Needs Improvement2.5-4 secondsNoticeable delay
Poor>4 secondsFrustrating wait

What counts as “largest content”:

  • <img> elements
  • <image> elements inside SVG
  • <video> elements (poster image or first frame)
  • Elements with background-image loaded via url()
  • Block-level elements containing text nodes

Common LCP elements:

<!-- Hero image (typical LCP candidate) -->
<img src="hero.jpg" alt="Product showcase">
<!-- Hero section with background -->
<div class="hero" style="background-image: url('hero-bg.jpg')">
<h1>Welcome to Our Store</h1>
</div>
<!-- Video poster -->
<video poster="video-preview.jpg">
<source src="video.mp4" type="video/mp4">
</video>

2. Interaction to Next Paint (INP)

Measures interactivity by tracking the latency of all click, tap, and keyboard interactions throughout the page lifecycle. INP replaced First Input Delay (FID) as a Core Web Vital in March 2024.

RatingThresholdUser Perception
Good≤200 millisecondsFeels responsive
Needs Improvement200-500 millisecondsSlight lag
Poor>500 millisecondsSluggish, unresponsive

How INP is calculated:

Page Session:
Click #1: 120ms latency
Click #2: 80ms latency
Click #3: 450ms latency ← Highest interaction
Tap #4: 95ms latency
INP = 450ms (or 99th percentile for high interaction counts)

INP vs FID:

AspectFID (Deprecated)INP (Current)
ScopeFirst interaction onlyAll interactions
MeasurementSingle valueWorst-case or 99th percentile
CapturesFirst impressionEntire session
AccuracyLimitedComprehensive

3. Cumulative Layout Shift (CLS)

Measures visual stability by quantifying unexpected layout movements that push content during loading.

RatingThresholdUser Perception
Good≤0.1Stable, predictable
Needs Improvement0.1-0.25Some content jumps
Poor>0.25Frustrating mis-clicks

How CLS is calculated:

Impact Fraction × Distance Fraction = Layout Shift Score
Example:
Element moves down 200px (viewport height: 800px)
Impact Fraction: 0.25 (element occupied area)
Distance Fraction: 0.25 (200/800)
Shift Score: 0.0625
Total CLS = Sum of all unexpected shift scores

Common CLS causes:

<!-- Images without dimensions -->
<img src="product.jpg" alt="Product"> <!-- Bad: causes reflow -->
<!-- Correct: dimensions reserved -->
<img src="product.jpg" alt="Product" width="400" height="300">
<!-- Ad containers without reserved space -->
<div id="ad-slot"></div> <!-- Bad: ad loads, pushes content -->
<!-- Correct: reserve space -->
<div id="ad-slot" style="min-height: 250px;"></div>
<!-- Dynamic content insertion -->
<div class="content"></div>
<script>
// Bad: inserts content without warning
document.querySelector('.content').prepend(banner);
</script>

Metric Thresholds Summary

MetricGoodNeeds ImprovementPoorUnit
LCP≤2.52.5-4>4seconds
INP≤200200-500>500milliseconds
CLS≤0.10.1-0.25>0.25score

Passing threshold: At least 75% of page loads must meet “Good” for all three metrics to pass Core Web Vitals assessment.

How to Measure Core Web Vitals

Field Data (Real User Monitoring)

Chrome User Experience Report (CrUX):

// PageSpeed Insights API
fetch('https://www.googleapis.com/pagespeedonline/v5/runPagespeed?' +
'url=https://example.com&category=performance')
.then(response => response.json())
.then(data => {
const lcp = data.lighthouseResult.audits['largest-contentful-paint'];
const cls = data.lighthouseResult.audits['cumulative-layout-shift'];
const inp = data.lighthouseResult.audits['interaction-to-next-paint'];
});

web-vitals JavaScript library:

import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log); // Logs LCP value
onINP(console.log); // Logs INP value
onCLS(console.log); // Logs CLS value
// Send to analytics
function sendToAnalytics(metric) {
navigator.sendBeacon('/analytics', JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
id: metric.id
}));
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

Lab Data (Synthetic Testing)

ToolUse Case
LighthouseLocal testing, CI/CD integration
PageSpeed InsightsQuick URL audit with field data
WebPageTestMulti-location synthetic testing
Chrome DevToolsReal-time debugging

Lighthouse CLI:

Terminal window
# Run Lighthouse audit
npx lighthouse https://example.com \
--only-categories=performance \
--output=json \
--output-path=./report.json
# Check specific metrics
npx lighthouse https://example.com \
--only-categories=performance \
--quiet \
--output=json | jq '.audits | {
lcp: .["largest-contentful-paint"].numericValue,
cls: .["cumulative-layout-shift"].numericValue,
inp: .["interaction-to-next-paint"].numericValue
}'

Optimization Strategies

LCP Optimization

1. Preload critical resources:

<head>
<!-- Preload LCP image -->
<link rel="preload" as="image" href="hero.webp">
<!-- Preload critical CSS -->
<link rel="preload" as="style" href="critical.css">
<!-- Preconnect to origins -->
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://analytics.example.com">
</head>

2. Optimize server response time:

# Enable HTTP/2 or HTTP/3
listen 443 http2;
# Enable compression
gzip on;
gzip_types text/html text/css application/javascript image/svg+xml;
# Enable caching headers
location ~* \.(jpg|jpeg|png|gif|webp|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}

3. Use a CDN for edge delivery:

Without CDN: User ──────────────────> Origin Server (100-300ms RTT)
[geographic distance]
With CDN: User ───> Edge Node (10-50ms) ───> Origin (if needed)
[nearest PoP]

INP Optimization

1. Break up long tasks:

// Bad: Long blocking task
function processItems(items) {
items.forEach(item => {
// Heavy computation
item.process();
});
}
// Good: Chunked processing
async function processItems(items) {
for (let i = 0; i < items.length; i += 50) {
const chunk = items.slice(i, i + 50);
chunk.forEach(item => item.process());
await new Promise(r => setTimeout(r, 0)); // Yield to main thread
}
}
// Better: scheduler API
async function processItems(items) {
for (const item of items) {
await scheduler.yield(); // Modern browsers
item.process();
}
}

2. Use web workers for heavy computation:

// Main thread
const worker = new Worker('compute.js');
button.addEventListener('click', () => {
worker.postMessage({ data: largeDataset });
// Main thread stays responsive
});
// compute.js (worker thread)
self.onmessage = (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
};

3. Optimize event handlers:

// Bad: Expensive operation on every keystroke
input.addEventListener('input', (e) => {
searchDatabase(e.target.value); // Blocks main thread
});
// Good: Debounced handler
const debounce = (fn, delay) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
};
input.addEventListener('input', debounce((e) => {
searchDatabase(e.target.value);
}, 300));

CLS Optimization

1. Reserve space for images:

/* Aspect ratio trick */
img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
/* Or use CSS to reserve space */
.image-container {
width: 100%;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
background: #f0f0f0;
}

2. Prevent font swap jumps:

@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: optional; /* Don't swap if loaded too late */
}
/* Or use size-adjust for fallback fonts */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
size-adjust: 98%; /* Match fallback font size */
descent-override: 20%;
}

3. Transform animations instead of layout properties:

/* Bad: Causes layout shift */
.element {
animation: move 0.3s;
}
@keyframes move {
from { left: 0; }
to { left: 100px; }
}
/* Good: Uses transform (compositor) */
.element {
animation: move 0.3s;
}
@keyframes move {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}

Core Web Vitals and SEO

Ranking Impact

Google uses Core Web Vitals as a ranking signal for mobile search results. Pages passing all three metrics receive a boost, while failing pages may see reduced visibility.

Page Experience Signal composition:

┌─────────────────────────────────────────────────────────────────┐
│ Page Experience Signal │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Core │ │ Mobile │ │ HTTPS & │ │
│ │ Web │ │ Friendly │ │ Safe Browsing │ │
│ │ Vitals │ │ Test │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
│ │
│ Weight: Major factor for competitive keywords │
│ Scope: Mobile search (desktop since 2022) │
│ Data source: CrUX (28-day rolling window) │
└─────────────────────────────────────────────────────────────────┘

Search Console Integration

Google Search Console → Experience → Core Web Vitals
Shows:
- URLs failing Core Web Vitals
- Mobile vs Desktop performance
- Historical trends (90 days)
- Issue severity breakdown

Common Issues and Fixes

LCP Issues

IssueFixImpact
Slow server responseCDN, caching, optimize database-0.5 to -2s
Render-blocking JS/CSSAsync/defer, inline critical CSS-0.3 to -1s
Large image filesWebP/AVIF, responsive images-0.5 to -2s
Client-side renderingSSR, SSG, streaming-1 to -3s

INP Issues

IssueFixImpact
Long JavaScript tasksCode splitting, web workers-100 to -400ms
Heavy event handlersDebounce, throttle, chunking-50 to -200ms
Third-party scriptsFacades, lazy loading, removal-50 to -300ms
Complex DOM updatesVirtual DOM, batch updates-50 to -150ms

CLS Issues

IssueFixImpact
Images without dimensionswidth/height attributes-0.1 to -0.5
Dynamic content insertionReserve space, skeleton screens-0.05 to -0.2
Web fontsfont-display: optional, preload-0.05 to -0.15
Ads/embedsReserve space, placeholder sizing-0.1 to -0.3

Tools and Resources

Testing Tools

ToolURLBest For
PageSpeed Insightspagespeed.web.devQuick URL audit
LighthouseChrome DevToolsLocal debugging
WebPageTestwebpagetest.orgMulti-location testing
Search Consolesearch.google.com/search-consoleSite-wide monitoring

JavaScript Libraries

Terminal window
# web-vitals library
npm install web-vitals
# Lighthouse CLI
npm install -g lighthouse
# Puppeteer for automated testing
npm install puppeteer

CI/CD Integration

# GitHub Actions example
name: Core Web Vitals
on: [push, pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Lighthouse
uses: treosh/lighthouse-ci-action@v10
with:
urls: |
https://staging.example.com/
https://staging.example.com/product
budgetPath: ./budget.json
uploadArtifacts: true

budget.json:

{
"ci": {
"assert": {
"assertions": {
"largest-contentful-paint": ["error", {"maxNumericValue": 2500}],
"cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}],
"interaction-to-next-paint": ["error", {"maxNumericValue": 200}]
}
}
}
}

When to Use

Use Core Web Vitals when:

  • Launching new pages or redesigns
  • Monitoring production site performance
  • Debugging user experience complaints
  • Optimizing for SEO rankings
  • Setting performance budgets for CI/CD

When Not to Use

Core Web Vitals limitations:

  • Single-page apps may need additional metrics
  • Doesn’t capture all UX dimensions (e.g., accessibility)
  • Field data requires sufficient traffic volume
  • Lab data may not reflect real user conditions

FAQ

What is the difference between LCP and page load time? LCP measures when the largest content element renders, while page load time (onload) measures when all resources finish loading. LCP better reflects user perception of when content is usable.

Why did INP replace FID? FID only measured the first interaction, missing subsequent responsiveness issues. INP captures all interactions throughout the session, providing a complete picture of interactivity.

Do Core Web Vitals affect desktop rankings? Yes. Originally mobile-only, Core Web Vitals became a ranking factor for desktop search results in 2022.

How much traffic is needed for CrUX data? Pages need approximately 1,000 monthly visits to appear in CrUX with statistically significant field data. Lower-traffic sites should rely on lab testing and RUM (Real User Monitoring).

Can I pass Core Web Vitals without a CDN? Yes, but it’s harder. CDNs reduce LCP significantly by serving static assets from edge locations close to users, eliminating round-trip latency to origin servers.

How often does Google update Core Web Vitals data? CrUX data updates monthly with a 28-day rolling average. Search Console shows updates every 1-2 weeks.


How to Implement on Azion

Azion’s global edge network optimizes Core Web Vitals by serving content from points of presence within 50ms of users worldwide:

  1. LCP improvement: Edge caching delivers static assets with <50ms latency vs. 100-300ms from centralized origins
  2. Functions: Run server-side rendering at the edge for dynamic content without origin round-trips
  3. Image Optimization: Automatic WebP/AVIF conversion and responsive image delivery
  4. HTTP/3 support: Reduced connection establishment time improves all metrics

See: Edge Caching | Functions

stay up to date

Subscribe to our Newsletter

Get the latest product updates, event highlights, and tech industry insights delivered to your inbox.