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.
| Rating | Threshold | User Perception |
|---|---|---|
| Good | ≤2.5 seconds | Content feels fast |
| Needs Improvement | 2.5-4 seconds | Noticeable delay |
| Poor | >4 seconds | Frustrating wait |
What counts as “largest content”:
<img>elements<image>elements inside SVG<video>elements (poster image or first frame)- Elements with
background-imageloaded viaurl() - 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.
| Rating | Threshold | User Perception |
|---|---|---|
| Good | ≤200 milliseconds | Feels responsive |
| Needs Improvement | 200-500 milliseconds | Slight lag |
| Poor | >500 milliseconds | Sluggish, 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:
| Aspect | FID (Deprecated) | INP (Current) |
|---|---|---|
| Scope | First interaction only | All interactions |
| Measurement | Single value | Worst-case or 99th percentile |
| Captures | First impression | Entire session |
| Accuracy | Limited | Comprehensive |
3. Cumulative Layout Shift (CLS)
Measures visual stability by quantifying unexpected layout movements that push content during loading.
| Rating | Threshold | User Perception |
|---|---|---|
| Good | ≤0.1 | Stable, predictable |
| Needs Improvement | 0.1-0.25 | Some content jumps |
| Poor | >0.25 | Frustrating 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 scoresCommon 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
| Metric | Good | Needs Improvement | Poor | Unit |
|---|---|---|---|---|
| LCP | ≤2.5 | 2.5-4 | >4 | seconds |
| INP | ≤200 | 200-500 | >500 | milliseconds |
| CLS | ≤0.1 | 0.1-0.25 | >0.25 | score |
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 APIfetch('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 valueonINP(console.log); // Logs INP valueonCLS(console.log); // Logs CLS value
// Send to analyticsfunction 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)
| Tool | Use Case |
|---|---|
| Lighthouse | Local testing, CI/CD integration |
| PageSpeed Insights | Quick URL audit with field data |
| WebPageTest | Multi-location synthetic testing |
| Chrome DevTools | Real-time debugging |
Lighthouse CLI:
# Run Lighthouse auditnpx lighthouse https://example.com \ --only-categories=performance \ --output=json \ --output-path=./report.json
# Check specific metricsnpx 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/3listen 443 http2;
# Enable compressiongzip on;gzip_types text/html text/css application/javascript image/svg+xml;
# Enable caching headerslocation ~* \.(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 taskfunction processItems(items) { items.forEach(item => { // Heavy computation item.process(); });}
// Good: Chunked processingasync 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 APIasync function processItems(items) { for (const item of items) { await scheduler.yield(); // Modern browsers item.process(); }}2. Use web workers for heavy computation:
// Main threadconst 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 keystrokeinput.addEventListener('input', (e) => { searchDatabase(e.target.value); // Blocks main thread});
// Good: Debounced handlerconst 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 breakdownCommon Issues and Fixes
LCP Issues
| Issue | Fix | Impact |
|---|---|---|
| Slow server response | CDN, caching, optimize database | -0.5 to -2s |
| Render-blocking JS/CSS | Async/defer, inline critical CSS | -0.3 to -1s |
| Large image files | WebP/AVIF, responsive images | -0.5 to -2s |
| Client-side rendering | SSR, SSG, streaming | -1 to -3s |
INP Issues
| Issue | Fix | Impact |
|---|---|---|
| Long JavaScript tasks | Code splitting, web workers | -100 to -400ms |
| Heavy event handlers | Debounce, throttle, chunking | -50 to -200ms |
| Third-party scripts | Facades, lazy loading, removal | -50 to -300ms |
| Complex DOM updates | Virtual DOM, batch updates | -50 to -150ms |
CLS Issues
| Issue | Fix | Impact |
|---|---|---|
| Images without dimensions | width/height attributes | -0.1 to -0.5 |
| Dynamic content insertion | Reserve space, skeleton screens | -0.05 to -0.2 |
| Web fonts | font-display: optional, preload | -0.05 to -0.15 |
| Ads/embeds | Reserve space, placeholder sizing | -0.1 to -0.3 |
Tools and Resources
Testing Tools
| Tool | URL | Best For |
|---|---|---|
| PageSpeed Insights | pagespeed.web.dev | Quick URL audit |
| Lighthouse | Chrome DevTools | Local debugging |
| WebPageTest | webpagetest.org | Multi-location testing |
| Search Console | search.google.com/search-console | Site-wide monitoring |
JavaScript Libraries
# web-vitals librarynpm install web-vitals
# Lighthouse CLInpm install -g lighthouse
# Puppeteer for automated testingnpm install puppeteerCI/CD Integration
# GitHub Actions examplename: Core Web Vitalson: [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: truebudget.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:
- LCP improvement: Edge caching delivers static assets with <50ms latency vs. 100-300ms from centralized origins
- Functions: Run server-side rendering at the edge for dynamic content without origin round-trips
- Image Optimization: Automatic WebP/AVIF conversion and responsive image delivery
- HTTP/3 support: Reduced connection establishment time improves all metrics
See: Edge Caching | Functions