Core Web Vitals Optimization
Core Web Vitals — three Google metrics that directly affect ranking since 2021. Poor metrics give a "Page Experience" penalty; good ones give an advantage when other factors are equal.
Three metrics and target values
| Metric | Good | Needs improvement | Poor |
|---|---|---|---|
| LCP (Largest Contentful Paint) | ≤ 2.5 s | 2.5–4.0 s | > 4.0 s |
| INP (Interaction to Next Paint) | ≤ 200 ms | 200–500 ms | > 500 ms |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | 0.1–0.25 | > 0.25 |
INP replaced FID in March 2024. FID measured only the first interaction; INP — all interactions during a session.
LCP (Largest Contentful Paint) — speed of main element loading
LCP is the moment when the largest element (usually hero image or H1) appears on screen.
Main causes of poor LCP:
- Hero image not preloaded
- Slow TTFB server
- Render-blocking CSS/JS
- Image in WebP/AVIF not optimized
Key optimizations:
<!-- Preload LCP image -->
<link rel="preload" as="image" href="/images/hero.webp"
imagesrcset="/images/hero-400.webp 400w, /images/hero-800.webp 800w, /images/hero-1200.webp 1200w"
imagesizes="100vw">
<!-- fetchpriority for img tag -->
<img src="/images/hero.webp" fetchpriority="high" loading="eager"
width="1200" height="630" alt="...">
# Accelerate TTFB through cache on Nginx
location ~* \.(html)$ {
proxy_cache_valid 200 5m;
add_header X-Cache-Status $upstream_cache_status;
}
INP (Interaction to Next Paint) — interface responsiveness
INP measures delay between user action (click, key press) and next render.
Causes of poor INP:
- Long JavaScript tasks (> 50ms) block main thread
- Synchronous computations in event handlers
- Too frequent setState in React without batching
Optimizations:
// Break long tasks through scheduler
async function processLargeList(items) {
for (let i = 0; i < items.length; i++) {
processItem(items[i]);
// Yield main thread every 50 items
if (i % 50 === 0) {
await new Promise(r => setTimeout(r, 0));
}
}
}
// React 18: automatic batching already enabled
// For explicit control — startTransition for non-critical updates
import { startTransition } from 'react';
function handleSearch(query) {
// Urgent update (input field)
setInputValue(query);
// Non-critical (results list) — can defer
startTransition(() => {
setSearchResults(filterResults(query));
});
}
CLS (Cumulative Layout Shift) — layout stability
CLS is the sum of element shifts when page loads.
Typical causes:
- Images without
width/height(browser doesn't reserve space) - Fonts cause FOUT/FOIT
- Dynamically inserted banners/ads
- Animations changing element sizes
Optimizations:
<!-- Always specify image dimensions -->
<img src="photo.webp" width="800" height="600" alt="...">
<!-- For responsive — aspect-ratio in CSS -->
<style>
.hero-image {
aspect-ratio: 16 / 9;
width: 100%;
}
</style>
/* Reserve space for fonts */
@font-face {
font-family: 'Inter';
font-display: optional; /* or swap + size-adjust */
src: url('/fonts/inter.woff2') format('woff2');
}
/* For font-display: swap — compensate size difference */
@font-face {
font-family: 'InterFallback';
src: local('Arial');
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
size-adjust: 107%;
}
Measurement tools
Laboratory data (synthetic):
- PageSpeed Insights — quick check of specific page
- Lighthouse (DevTools) — detailed audit
- WebPageTest — extended analysis with waterfall
Field data (real users):
- CrUX (Chrome User Experience Report) — in Search Console
- web-vitals npm package for analytics collection
// Send Web Vitals to GA4
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals';
function sendToAnalytics({ name, value, rating, id }) {
gtag('event', name, {
event_category: 'Web Vitals',
event_label: id,
value: Math.round(name === 'CLS' ? value * 1000 : value),
non_interaction: true,
custom_map: { metric_rating: rating }
});
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
Priorities
- LCP — usually gives greatest effect: preload hero-image, FastCGI/Redis cache for TTFB
- CLS — fixed quickly by adding dimensions to images and font setup
- INP — more complex: requires profiling specific interactions through Performance DevTools
Optimization time: 1–2 weeks for comprehensive work on all three metrics.







