Core Web Vitals are Google's attempt to quantify "page experience" — how a real user perceives a page loading, responding, and rendering. Since the May 2021 rollout, the metrics have evolved: First Input Delay (FID) was replaced by Interaction to Next Paint (INP) in March 2024, raising the bar significantly. In 2026, passing Core Web Vitals is no longer a competitive advantage — failing them is a competitive disadvantage. This guide explains what each metric measures, the thresholds Google enforces, and the specific code-level fixes that move the needle.
The Three Metrics, Explained Simply
Core Web Vitals consists of three metrics that map to three phases of the user experience:
| Metric | What it measures | Good | Poor |
|---|---|---|---|
| LCP — Largest Contentful Paint | How fast the main content appears | ≤ 2.5s | > 4.0s |
| INP — Interaction to Next Paint | How responsive the page feels to interaction | ≤ 200ms | > 500ms |
| CLS — Cumulative Layout Shift | How stable the layout is during load | ≤ 0.1 | > 0.25 |
To pass the Core Web Vitals assessment for a URL or origin, the 75th percentile of field data must fall in the Good range for all three metrics. That is, three out of every four real page views must hit Good. This 75th-percentile rule is critical: averages hide outliers, and Google explicitly does not use averages.
Field Data vs. Lab Data
The single biggest source of confusion about Core Web Vitals is the difference between field data and lab data — and which Google actually uses for ranking.
Field data is collected from real users. Chrome reports anonymized performance metrics from users who have opted in to "Make searches and browsing better." These reports feed the Chrome User Experience Report (CrUX), a publicly available dataset Google uses to evaluate Core Web Vitals for ranking purposes. Field data is what shows up in the Search Console Core Web Vitals report and in the PageSpeed Insights "Origin" and "URL" CrUX sections.
Lab data comes from synthetic tests — Lighthouse audits, WebPageTest runs, the Chrome DevTools Performance panel. Lab tests are reproducible, controllable, and great for debugging, but they run in standardized conditions (specific network throttling, specific device emulation) that may not match your real user mix.
Lab data can show wildly different numbers than field data. A lab test that runs on a fast cable connection with a powerful CPU might report LCP of 1.2 seconds, while your field data (representing users on mid-range Android phones over 3G) shows LCP of 3.8 seconds. Always optimize for field data. Use lab data to debug a specific symptom and verify a fix; use field data to know whether you have passed.
Largest Contentful Paint (LCP)
LCP measures the render time of the largest image or text block visible in the viewport. It is Google's best proxy for "how fast does the page feel like it loaded?" Most LCP problems fall into a small number of categories.
What element is your LCP?
Use the PageSpeed Insights field data to see which element is most commonly the LCP on your page. It is usually a hero image, a featured product image, an above-the-fold headline, or (less ideally) a full-screen background image. Identify the LCP element first; everything else flows from that.
LCP fix 1: Preload the LCP image
If your LCP is an image, add a preload hint in the document head:
<link rel="preload" as="image" href="/hero.avif"
fetchpriority="high" imagesrcset="/hero-480.avif 480w, /hero-960.avif 960w"
imagesizes="100vw">
The fetchpriority="high" attribute tells the browser to prioritize this image above other resources. Preloading an LCP image typically improves LCP by 200–800 milliseconds.
LCP fix 2: Modern image formats and compression
Serve AVIF (best compression, broad 2026 support) or WebP with JPEG fallback. Lossy quality 75–85 is indistinguishable from the original for photos. Use responsive srcset so mobile devices download a smaller image. Tools like Squoosh, ImageOptim, Sharp (Node.js), or any modern CDN's on-the-fly transformation will handle this.
LCP fix 3: Remove render-blocking resources
CSS in the <head> blocks rendering. Inline critical CSS for above-the-fold content (under 14 KB ideally), and load the full stylesheet asynchronously. Defer non-critical JavaScript with defer or async. Tools like Critical, CriticalCSS, or framework-native options (Next.js' built-in critical CSS, Nuxt's render.bundleRenderer) automate this.
LCP fix 4: Server response time
Time To First Byte (TTFB) is the floor for LCP — your LCP can never be faster than the time it takes the server to respond. Aim for TTFB under 600 milliseconds. Use a CDN, enable HTTP/2 or HTTP/3, cache aggressively at the edge, and avoid expensive synchronous database queries during page render.
LCP fix 5: Font loading
If your LCP is a text element, web font loading is often the bottleneck. Self-host fonts, use font-display: swap so fallback text shows immediately, and preload the specific font file used for the LCP text. Variable fonts and subsetting reduce file size further.
Interaction to Next Paint (INP)
INP is the metric most sites struggle with. It measures the latency between a user input (click, tap, key press) and the next paint that visually responds. Unlike FID — which only measured the first interaction — INP measures every interaction throughout the page lifetime and reports the worst (or near-worst, depending on interaction count).
Why INP is harder than FID
FID could pass simply by deferring JavaScript that ran during page load. INP fails any time the main thread is blocked for too long during an interaction — clicking a menu, opening a modal, expanding an accordion, typing in a search box. Real-world responsiveness is a much higher bar than first-interaction responsiveness.
INP fix 1: Break up long tasks
A "long task" is any chunk of JavaScript that occupies the main thread for more than 50 milliseconds. Long tasks delay the browser's ability to paint. Break long tasks into smaller pieces using setTimeout(fn, 0), requestAnimationFrame, or the newer scheduler.yield() API (Chrome 129+). The goal is to give the browser a chance to paint and process input between work chunks.
// Bad: one giant synchronous loop
items.forEach(processItem);
// Better: yield to the browser between chunks
async function processInChunks(items) {
for (const item of items) {
processItem(item);
if (navigator.scheduling?.isInputPending()) {
await scheduler.yield();
}
}
}
INP fix 2: Reduce JavaScript payload
Less JavaScript means less work for the main thread. Audit your bundle with source-map-explorer or webpack-bundle-analyzer. Common culprits: full Moment.js (replace with date-fns or Day.js), full Lodash (use individual imports), unused framework components, and dependency-of-dependency bloat. Code-split aggressively so each route only ships what it needs.
INP fix 3: Move work off the main thread
Heavy computation (parsing, encryption, image manipulation, large data transformations) belongs in a Web Worker. The main thread should be reserved for UI work. Comlink makes Web Workers as easy as async function calls.
INP fix 4: Optimize event handlers
An expensive click handler that triggers a full re-render of the page will fail INP. Use requestAnimationFrame to defer non-urgent work until after paint. Debounce or throttle handlers that fire rapidly (search-as-you-type, scroll listeners). In React, use useTransition or startTransition to mark non-urgent updates as low priority.
INP fix 5: Tame third-party scripts
Third-party tags — analytics, A/B testing, chat widgets, ad networks, tag managers — are often the biggest INP offenders because they run code you cannot control. Load them with async, defer them until after first interaction, or use Partytown to relocate them to a Web Worker. Audit tags relentlessly: every third-party script should justify its INP cost.
Cumulative Layout Shift (CLS)
CLS measures unexpected layout movement — content jumping around after the page begins to render. CLS is the easiest metric to fix because the causes are usually visible and discrete.
CLS fix 1: Reserve space for media
Every image and iframe must declare explicit width and height attributes (or equivalent CSS aspect-ratio). The browser uses these to reserve correct space before the resource loads. Without dimensions, the browser allocates zero space and then jumps when the image arrives.
<img src="hero.avif" width="1200" height="600" alt="...">
/* or with CSS aspect-ratio */
img { aspect-ratio: 16 / 9; width: 100%; height: auto; }
CLS fix 2: Reserve space for ads and embeds
Ads are the leading CLS culprit on content sites. Wrap every ad slot in a container with a fixed min-height matching the expected ad dimensions. If the ad does not load, the space is empty — acceptable. If you let the ad inject and push content down, CLS rockets.
CLS fix 3: Never inject content above existing content
Cookie banners, notification bars, app-install prompts — anything that appears above existing rendered content forces everything below it to shift. Either pre-render the space (a min-height container at the top), animate the content in from outside the viewport, or display it as an overlay rather than a layout-affecting block.
CLS fix 4: Web font swap CLS
When a web font loads after fallback text has rendered, characters often have different widths, causing layout shift as text reflows. Use the size-adjust, ascent-override, and descent-override font descriptors to match the fallback metrics to the web font, eliminating the shift. The Fontaine library automates this.
CLS fix 5: Animate with transform, not layout
CSS animations that change width, height, top, or left trigger layout shifts. Use transform: translate() or transform: scale() instead — they animate on the compositor thread without affecting CLS or repaint cost.
Measurement Toolkit
- Chrome DevTools Performance panel — Frame-by-frame breakdown of LCP, INP, and long tasks. Best for debugging a specific page.
- PageSpeed Insights — Combines Lighthouse lab data with CrUX field data. Best for one-off URL audits.
- Search Console Core Web Vitals report — Field data for your entire site, grouped by URL pattern. Best for understanding which page templates need work.
- web-vitals JavaScript library — Capture real Core Web Vitals from your own users and send them to your analytics. Best for ongoing monitoring with custom segmentation.
- CrUX BigQuery dataset — Public dataset with monthly CWV data for millions of origins. Best for benchmarking against competitors.
- Lighthouse CI — Run Lighthouse on every pull request in your CI pipeline. Best for preventing regressions.
Setting Up Real User Monitoring
Lab data is great for debugging, but only field data drives ranking decisions. Implement the official web-vitals library to capture real measurements:
import {onLCP, onINP, onCLS} from 'web-vitals';
function sendToAnalytics({name, value, id}) {
navigator.sendBeacon('/analytics', JSON.stringify({name, value, id}));
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
Pipe the measurements into your analytics tool (Google Analytics 4, Plausible, Cloudflare Web Analytics, or a custom endpoint). Segment by device type, country, browser, and page template. The segmentation usually reveals where your bottleneck is — for example, "Android users in Indonesia on the product detail template fail INP" is a much more actionable insight than "INP is too high."
Common Anti-Patterns
- Optimizing for Lighthouse score instead of field data. A 95 Lighthouse score is meaningless if your CrUX 75th percentile says Poor. Lab and field measure different things.
- Treating CWV as a project, not a process. Performance regresses every release without continuous monitoring. Wire performance budgets into your CI pipeline.
- Ignoring third-party scripts. "It is just the analytics tag" turns into 12 third-party scripts that collectively block the main thread for 800 milliseconds.
- Optimizing the homepage only. Most traffic enters on long-tail pages. Audit your page templates, not just the homepage.
- Single device-class testing. Throttle to mid-range Android and 4G to match the median real user. Optimizing on a MacBook Pro misses the bottleneck.
The Ranking Impact in Practice
Google has repeatedly described Core Web Vitals as a small ranking signal — a tiebreaker between otherwise comparable pages. In practice, the effect varies wildly by query and competition. For highly competitive commercial queries, CWV often does not move rankings by itself; the top results are all already passing. For long-tail informational queries, an under-optimized but content-rich page can outrank a faster but thinner competitor.
The strongest case for prioritizing CWV is not ranking — it is conversion. Studies across retail, news, and SaaS consistently show double-digit conversion lifts from reducing LCP by 1 second or INP by 100 milliseconds. The ranking gain is a bonus on top of the revenue gain.
Optimize Images for Better LCP
Compress and convert images to modern formats — one of the highest-impact fixes for LCP and overall page speed.
Image Compressor →