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):
- LCP - Largest Contentful Paint
- INP - Interaction to Next Paint (replaced FID in March 2024)
- 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
transforminstead 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