Web Performance
Core Web Vitals: Complete Guide 2026

LCP, INP and CLS — three Google metrics that directly affect rankings. How to measure, diagnose and improve each one.
Core Web Vitals (CWV) are Google's set of metrics measuring real user experience: loading speed, interface responsiveness and visual stability. Since May 2021 they are part of Page Experience and factor into ranking.
All three metrics are evaluated per URL — great scores on your homepage won't help product pages. Google sources data from Chrome UX Report (real users), not lab simulations.
What are Core Web Vitals
LCP — good
Largest element rendered
INP — good
Interface responded to input
CLS — good
Minimal layout shift
Assessment threshold
Share of users in the green zone
History: from FID to INP
Google introduced Core Web Vitals in 2020 with three metrics: LCP, FID (First Input Delay) and CLS. FID measured the delay of the first tap — valuable, but covering only a fraction of page interactivity.
Google introduces three metrics: LCP, FID, CLS. Tools begin supporting the new signals.
CWV officially enter the ranking algorithm. Rollout completes in August 2021.
Interaction to Next Paint replaces First Input Delay. INP covers all interactions during the session, not just the first.
Sites with heavy JS (SPAs, e-commerce, filters) feel INP pressure. Long Task optimisation becomes a priority.
LCP — Largest Contentful Paint
LCP records the moment the largest visible contentful block on the page is fully painted. Typically this is a hero image, article cover or a large H1 heading.
| LCP value | Rating | Action |
|---|---|---|
| ≤ 2.5 s | Good | Maintain, monitor in GSC |
| 2.5 – 4.0 s | Needs improvement | Optimise images, reduce TTFB |
| > 4.0 s | Poor | Critical priority, audit immediately |
Common causes of slow LCP
- Slow TTFB (server, CDN, no caching) — accounts for up to 40% of LCP time
- Hero image not prioritised: missing
fetchpriority="high" - Hero image not preloaded (
<link rel="preload">) - Render-blocking CSS/JS delays first paint
- Images too large: no WebP/AVIF, no
srcset - LCP element behind JS render (React SSR not complete at LCP time)
<!-- Prioritise the LCP image -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
<img
src="/hero.webp"
alt="Hero"
fetchpriority="high"
loading="eager"
decoding="async"
width="1280"
height="720"
/>loading="lazy" on hero images — the browser will defer loading them and LCP will increase. Use lazy only for images below the fold.INP — Interaction to Next Paint
INP measures the delay from any user interaction (click, keypress, tap) to the next visual paint of the page. Unlike FID, INP tracks all interactions during the session and uses the worst case (with outlier trimming).
What breaks INP
- Long Tasks > 50 ms block the main thread — browser can't handle input
- Heavy event handlers: synchronous setState in React, mass DOM updates
- No
scheduler.yield()orsetTimeout(0)to break up long tasks - Third-party scripts (chat, ads, analytics) occupy the main thread
- JS-driven animations instead of CSS
transform/opacity
// Breaking up a Long Task with scheduler.yield()
async function processLargeList(items) {
for (let i = 0; i < items.length; i++) {
processItem(items[i]);
// Yield to the main thread every 50 items
if (i % 50 === 0) {
await scheduler.yield();
}
}
}CLS — Cumulative Layout Shift
CLS is a unitless metric describing the total unexpected shift of page elements. Formula: impact fraction × distance fraction. Shifting a pressed button doesn't count — only elements that shift without user action.
| CLS value | Rating | Typical cause |
|---|---|---|
| ≤ 0.1 | Good | Space reserved for all elements |
| 0.1 – 0.25 | Needs improvement | Fonts without font-display: swap, banners |
| > 0.25 | Poor | Images without dimensions, dynamic inserts |
Common CLS sources
- Images without
widthandheightattributes — browser doesn't know the size before load - Web fonts without
font-display: optionalorswap— FOUT shifts text - Dynamically injected banners, cookie notices at the top of the page
- CSS animations changing
top/left/margininstead oftransform - Ads without reserved space (
min-heighton container)
/* Reserve space for an ad slot */
.ad-container {
min-height: 250px;
width: 100%;
}
/* Animation without CLS: transform and opacity only */
.card:hover {
transform: translateY(-4px);
/* Wrong: margin-top: -4px — this shifts layout */
}Impact on rankings
Google confirmed: CWV is one of the Page Experience signals that participates in ranking. But it's a tiebreaker, not the primary factor. A page with great content but poor CWV will outrank a page with great CWV but weak content.
The indirect effect matters more: fast pages reduce bounce rate, increase session depth and conversion. The business case for CWV is stronger than the SEO case. Amazon found: +100 ms to LCP = -1% revenue.
How to measure CWV
There are two data types: lab data (simulation — Lighthouse, WebPageTest) and field data (real users — CrUX, GSC). Google ranks based on field data. Lab is for diagnosis.
| Type | Tool | What it shows | Use case |
|---|---|---|---|
| Field | Google Search Console | CWV for all site URLs (p75) | Prioritise problematic pages |
| Field | CrUX Dashboard (Looker) | 28-day window trends | Track progress over time |
| Lab | PageSpeed Insights | LCP/INP/CLS + recommendations | Diagnose a specific URL |
| Lab | Lighthouse (DevTools) | Detailed waterfall, trace | Deep-dive root cause analysis |
| Lab | WebPageTest | Video recording, filmstrip | Before/after comparison |
Diagnostic tools
| Tool | Free | LCP | INP | CLS | Field data |
|---|---|---|---|---|---|
| Google Search Console | Yes | ✓ | ✓ | ✓ | ✓ |
| PageSpeed Insights | Yes | ✓ | ✓ | ✓ | ✓ (CrUX) |
| Chrome DevTools | Yes | ✓ | ✓ | ✓ | — |
| Lighthouse CI | Yes | ✓ | ✓ | ✓ | — |
| WebPageTest | Yes (limits) | ✓ | ✓ | ✓ | — |
| SpeedCurve | Paid | ✓ | ✓ | ✓ | ✓ |
| Sentry (web-vitals SDK) | Freemium | ✓ | ✓ | ✓ | ✓ |
// web-vitals v4 — collect real CWV
import { onLCP, onINP, onCLS } from 'web-vitals';
function sendToAnalytics({ name, value, id }) {
// Send to GA4
gtag('event', name, {
value: Math.round(name === 'CLS' ? value * 1000 : value),
metric_id: id,
metric_delta: value,
});
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);Optimization checklist
LCP
- Add
fetchpriority="high"and removeloading="lazy"from the hero image - Add
<link rel="preload" as="image">for hero in<head> - Convert images to WebP or AVIF, configure
srcset - Reduce TTFB to ≤ 800 ms: CDN, caching, Edge SSR
- Remove render-blocking scripts from
<head>(async/defer/module) - Set
Cache-Control: max-age=31536000for static assets
INP
- Profile Long Tasks in Chrome DevTools → Performance → Main Thread
- Break tasks > 50 ms with
scheduler.yield()orsetTimeout(0) - Debounce
input/scrollhandlers (16–100 ms) - Optimise React:
useMemo,useCallback,React.memo, list virtualisation - Move third-party scripts to Web Workers or load after LCP
- CSS animations via
transformandopacityonly
CLS
- Set
widthandheighton all<img>elements — browser reserves space - Use
aspect-ratiofor containers with unknown dimensions - Configure
font-display: optionalfor critical fonts - Reserve
min-heightfor ad slots and dynamic content - Place notices (cookie, banners) at the bottom, not top of the page
- Check FOUT: fonts without a fallback shift text on load