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​