Skip to main content

Core Web Vitals (LCP, FID, CLS, INP)

Overview

Core Web Vitals are a set of specific metrics that Google considers essential for measuring the user experience of a webpage, focusing on loading performance, interactivity, and visual stability.

What are Core Web Vitals?

Core Web Vitals are part of Google's page experience signals and consist of three primary metrics (with INP replacing FID in 2024):

  1. LCP - Largest Contentful Paint
  2. INP - Interaction to Next Paint (replaced FID in March 2024)
  3. CLS - Cumulative Layout Shift

Why Core Web Vitals Matter

  • Ranking Factor: Direct impact on search rankings since 2021
  • User Experience: Measures real user experience quality
  • Conversion Rates: Better metrics correlate with higher conversions
  • Mobile Performance: Critical for mobile-first indexing
  • Competitive Edge: Many sites still struggle with these metrics
  • User Retention: Fast, stable sites keep users engaged

The Three Core Web Vitals

1. Largest Contentful Paint (LCP)

What it Measures: Loading performance - how long it takes for the largest visible content element to render.

Target Thresholds:

  • Good: 2.5 seconds or less
  • Needs Improvement: 2.5 - 4.0 seconds
  • Poor: More than 4.0 seconds

Common LCP Elements:

  • Hero images
  • Video thumbnails
  • Background images with text
  • Large text blocks
  • Header images

How to Improve LCP:

<!-- Prioritize LCP resource -->
<link rel="preload" as="image" href="hero-image.jpg">

<!-- Use optimized images -->
<img src="hero.webp"
srcset="hero-small.webp 480w, hero-large.webp 1200w"
sizes="100vw"
alt="Hero image">

<!-- Optimize CSS delivery -->
<link rel="stylesheet" href="critical.css">
<link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

Best Practices:

  • Optimize server response time (TTFB < 600ms)
  • Remove render-blocking resources
  • Optimize and compress images
  • Use a CDN
  • Implement lazy loading for below-fold content
  • Minimize CSS and JavaScript
  • Use resource hints (preload, preconnect)

2. Interaction to Next Paint (INP)

What it Measures: Responsiveness - time from user interaction to the next visual update.

Target Thresholds:

  • Good: 200 milliseconds or less
  • Needs Improvement: 200 - 500 milliseconds
  • Poor: More than 500 milliseconds

Replaced FID in March 2024: INP provides a more comprehensive measure of interactivity throughout the page lifecycle, not just first interaction.

What Counts as Interaction:

  • Mouse clicks
  • Taps (on touchscreen)
  • Keyboard presses

How to Improve INP:

// Break up long tasks
function processLargeArray(items) {
// Instead of processing all at once
const batchSize = 50;
let index = 0;

function processBatch() {
const end = Math.min(index + batchSize, items.length);

for (; index < end; index++) {
// Process item
processItem(items[index]);
}

if (index < items.length) {
// Schedule next batch
setTimeout(processBatch, 0);
}
}

processBatch();
}

// Use requestIdleCallback for non-critical work
requestIdleCallback(() => {
// Non-critical processing
analyzeUserBehavior();
});

Best Practices:

  • Minimize JavaScript execution time
  • Break up long tasks (>50ms)
  • Optimize event handlers
  • Use code splitting
  • Defer non-critical JavaScript
  • Reduce JavaScript payload size
  • Use web workers for heavy computations

3. Cumulative Layout Shift (CLS)

What it Measures: Visual stability - unexpected layout shifts during page load.

Target Thresholds:

  • Good: 0.1 or less
  • Needs Improvement: 0.1 - 0.25
  • Poor: More than 0.25

CLS Calculation:

CLS = Impact Fraction × Distance Fraction

How to Improve CLS:

<!-- Always include dimensions for images -->
<img src="image.jpg" width="800" height="600" alt="Image">

<!-- Reserve space for ads -->
<div style="min-height: 250px;">
<!-- Ad code -->
</div>

<!-- Use aspect ratio boxes -->
<style>
.video-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
height: 0;
}
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>

CSS for Preventing Shifts:

/* Reserve space for web fonts */
@font-face {
font-family: 'Custom Font';
src: url('font.woff2') format('woff2');
font-display: optional; /* or swap */
size-adjust: 95%; /* Adjust to match fallback */
}

/* Prevent reflow from images */
img {
max-width: 100%;
height: auto;
}

/* Transform instead of layout properties */
.animated {
/* Good - doesn't cause reflow */
transform: translateX(100px);

/* Bad - causes reflow */
/* left: 100px; */
}

Best Practices:

  • Always include size attributes on images and videos
  • Reserve space for ads and embeds
  • Avoid inserting content above existing content
  • Use transform instead of layout properties for animations
  • Preload fonts and use font-display properly
  • Avoid lazy loading above-the-fold content

Measuring Core Web Vitals

Field Data (Real User Metrics)

Google Search Console

  • Page Experience Report: Shows CWV performance
  • Core Web Vitals Report: Detailed by URL
  • Real user data from Chrome User Experience Report

Chrome User Experience Report (CrUX)

  • 28-day rolling data
  • Real user measurements
  • Device and connection type breakdown

PageSpeed Insights

  • Combines field and lab data
  • Shows real user metrics
  • Provides optimization suggestions

Lab Data (Synthetic Testing)

Lighthouse

# CLI usage
npm install -g lighthouse
lighthouse https://example.com --view

WebPageTest

  • Detailed performance waterfall
  • Multiple location testing
  • Connection throttling
  • Video capture

Chrome DevTools

  • Performance panel
  • Lighthouse integration
  • Real-time monitoring

Optimization Strategies

Server Optimization

# Nginx configuration for performance
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss
application/javascript application/json;

# Enable HTTP/2
listen 443 ssl http2;

# Add caching headers
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}

Image Optimization

<!-- Modern image formats with fallback -->
<picture>
<source type="image/avif" srcset="image.avif">
<source type="image/webp" srcset="image.webp">
<img src="image.jpg" alt="Description" width="800" height="600">
</picture>

<!-- Lazy loading -->
<img src="image.jpg" loading="lazy" alt="Description">

<!-- Priority hints for LCP image -->
<img src="hero.jpg" fetchpriority="high" alt="Hero">

JavaScript Optimization

<!-- Defer non-critical JavaScript -->
<script src="analytics.js" defer></script>

<!-- Async for independent scripts -->
<script src="widget.js" async></script>

<!-- Module/nomodule pattern -->
<script type="module" src="modern.js"></script>
<script nomodule src="legacy.js"></script>

CSS Optimization

<!-- Critical CSS inline -->
<style>
/* Critical above-the-fold CSS */
.hero {
height: 400px;
background: #f0f0f0;
}
</style>

<!-- Non-critical CSS async -->
<link rel="preload" href="styles.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>

Resource Hints

<!-- DNS prefetch -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">

<!-- Preconnect -->
<link rel="preconnect" href="https://cdn.example.com">

<!-- Prefetch -->
<link rel="prefetch" href="/next-page.html">

<!-- Preload -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

Advanced Techniques

Code Splitting

// Dynamic imports
const button = document.querySelector('#loadModule');
button.addEventListener('click', async () => {
const module = await import('./heavy-module.js');
module.initialize();
});

// React lazy loading
import React, { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}

Service Workers

// Cache-first strategy for static assets
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});

Intersection Observer for Lazy Loading

const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
imageObserver.unobserve(img);
}
});
});

images.forEach(img => imageObserver.observe(img));

Common Issues and Solutions

Poor LCP

Issue: Large hero image loading slowly Solution: Preload, optimize format, use CDN, implement responsive images

Issue: Render-blocking CSS/JS Solution: Inline critical CSS, defer non-critical resources

Issue: Slow server response Solution: Optimize backend, use caching, upgrade hosting

Poor INP

Issue: Heavy JavaScript execution Solution: Code splitting, defer non-essential JS, optimize algorithms

Issue: Long tasks blocking main thread Solution: Break into smaller chunks, use web workers

Issue: Third-party scripts Solution: Load asynchronously, consider alternatives

High CLS

Issue: Images without dimensions Solution: Always specify width and height

Issue: Ads causing shifts Solution: Reserve fixed space for ad slots

Issue: Web fonts loading Solution: Use font-display: optional, preload fonts

Monitoring and Maintenance

Continuous Monitoring Tools

  • Google Search Console: Weekly checks
  • PageSpeed Insights: Monthly audits
  • Lighthouse CI: Automated testing in deployment
  • Real User Monitoring (RUM): Services like SpeedCurve, Calibre

Setting Up Alerts

// Web Vitals library
import {getCLS, getFID, getLCP} from 'web-vitals';

function sendToAnalytics(metric) {
// Send to your analytics endpoint
const body = JSON.stringify(metric);
navigator.sendBeacon('/analytics', body);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);

Core Web Vitals Checklist

LCP Optimization

  • Optimize server response time
  • Remove render-blocking resources
  • Optimize images (format, compression, sizing)
  • Preload LCP element
  • Use CDN
  • Implement caching

INP Optimization

  • Minimize JavaScript
  • Defer non-critical scripts
  • Break up long tasks
  • Optimize event handlers
  • Remove unused code
  • Use code splitting

CLS Optimization

  • Add size attributes to images/videos
  • Reserve space for ads
  • Avoid injecting content above existing content
  • Preload fonts
  • Use CSS containment
  • Avoid animations that cause layout shifts

Impact on Rankings

  • Part of Page Experience update (June 2021)
  • More important for mobile than desktop
  • Tiebreaker when content quality is similar
  • Indirect impact through improved UX and engagement
  • Mobile-first indexing makes mobile vitals crucial

Further Reading