Google's framework for measuring real-user experience. Three core signals that directly affect search ranking — with live demos you can run right here.
LCP — loading
≤ 2.5s
INP — interactivity
≤ 200ms
CLS — visual stability
≤ 0.1
TBT — lab proxy
≤ 200ms
TTFB — server
≤ 800ms
FCP — first paint
≤ 1.8s
↓ scroll to explore each metric with live demos
01 / Core Web Vital
Largest Contentful Paint LCP
Time to render the largest visible element — hero image, h1, big text block. Measures perceived loading speed. Google's most weighted Core Vital.
LCP
Largest Contentful Paint CORE
Marks the point when the largest image or text block in the viewport becomes visible. Threshold measured at the 75th percentile of page loads from real field data.
Good <2.5sNeeds <4sPoor >4s
Common causes of poor LCP
Slow server response time (high TTFB)
Hero image not preloaded — browser discovers it too late
Render-blocking CSS / JS in <head>
Font blocking render (FOUT / FOIT)
loading="lazy" on above-the-fold image
No CDN — server far from user
Image served without correct dimensions (triggers reflow)
How to fix
<link rel="preload" as="image" href="hero.webp">
fetchpriority="high" on hero img
Serve WebP/AVIF at correct dimensions
CDN + HTTP/2 edge caching
font-display: swap or optional
<!-- ✓ Correct LCP setup --><link rel="preload" as="image"
href="hero.webp"/><img
src="hero.webp"
width="1200" height="630"
fetchpriority="high"/* never lazy on LCP! *//>
Live demo — LCP simulation
[ hero image not loaded ]
Above-the-fold heading text — this would be LCP candidate if largest
LCP time
—
02 / Core Web Vital
Interaction to Next Paint INP
Delay from user click/tap/keypress to next browser paint. Replaced FID in 2024. Measures responsiveness throughout the entire page lifetime — not just on load.
INP
Interaction to Next Paint CORE
Observes the latency of all clicks, taps, and keyboard interactions during page lifetime. Reports the worst-case interaction (above the 98th percentile).
Good <200msNeeds <500msPoor >500ms
Common causes of poor INP
Long JS tasks blocking main thread (>50ms)
Heavy event handlers without debounce/throttle
Third-party scripts (analytics, chat) on main thread
Excessive DOM nodes — slow style recalculation
Heavy React / Vue hydration at initial load
Synchronous XHR or heavy computation in handler
How to fix
scheduler.yield() to give browser a breath
Debounce / throttle heavy event handlers
Move heavy work to Web Worker
Code-split and lazy-load non-critical components
Use requestIdleCallback for non-urgent work
// ✗ BAD — blocks main thread
btn.addEventListener('click', () => {
heavySync(200); // 200ms sync loop!
updateUI();
});
// ✓ GOOD — yield to browser
btn.addEventListener('click', async () => {
await scheduler.yield();
heavySync(200); // runs after paint
updateUI();
});
Live demo — main thread blocking
Main thread timeline (50ms = 1 block):
■ Long task (blocked)
■ Paint / idle
■ Web Worker
INP latency
—
UI frozen
—
Status:idle
⚠ The "Bad" demo runs a synchronous while(Date.now() < end) loop for 300ms — you'll see the UI freeze. The counter below updates only after the task finishes.
Live counter (stops updating when main thread blocked)
0
03 / Core Web Vital
Cumulative Layout Shift CLS
Total amount of unexpected layout shifts throughout the page lifetime. Each shift = impact fraction × distance fraction. Sum must stay below 0.1.
CLS
Cumulative Layout Shift CORE
Measures unexpected visual instability. Score = sum of (impact_fraction × distance_fraction) for all layout shifts. Only shifts >500ms after last input count — rapid-fire interactions don't penalise.
Good <0.1Needs <0.25Poor >0.25
Common causes of poor CLS
Images missing width / height attributes
Ads, embeds, iframes loaded after initial paint
Font swap causing text reflow (FOUT)
Cookie banners / popups injected above the fold
Animations using top / left instead of transform
Dynamic content injected above the fold without reserved space
How to fix
Always set width + height on img/video/iframe
Use aspect-ratio CSS for responsive containers
Reserve space for ads before they load
Use transform for animations, never top/left
font-display: optional to avoid FOUT
/* ✗ BAD — no size = layout shift */<img src="hero.jpg"/>/* ✓ GOOD — browser reserves space */<img src="hero.jpg"
width="800" height="450"
style="aspect-ratio: 16/9"/>/* ✗ BAD animation */
.slide { top: 0 → 100px }
/* ✓ GOOD animation */
.slide { transform: translateY(100px) }
Live demo — layout shift visualiser
This is the content you were about to read.
It's positioned here before any dynamic content loads.
Watch what happens when an ad/banner injects above it...
CLS: 0.00
CLS score
0.00
04 / Lab Metric (INP proxy)
Total Blocking Time TBT
Sum of all periods the main thread was blocked >50ms between FCP and TTI. This is Lighthouse's lab-mode proxy for INP. High TBT almost always means poor real-world INP.
TBT
Total Blocking Time
For each long task (>50ms), TBT = task_duration − 50ms. Example: a 250ms task contributes 200ms to TBT. Sum all such tasks between FCP → TTI.
// TBT calculation example:// Task 1: 250ms → contributes 200ms TBT// Task 2: 80ms → contributes 30ms TBT// Task 3: 45ms → contributes 0ms TBT (under 50ms)// Task 4: 120ms → contributes 70ms TBT// Total TBT = 300ms ← Poor!// ✓ FIX: break task 1 into chunksasync function chunkedWork(items) {
for (let i = 0; i < items.length; i++) {
process(items[i]);
if (i % 50 === 0)
await scheduler.yield(); // breathe!
}
}
Live demo — long task visualiser
Timeline (each block = 50ms, red = blocking >50ms threshold):
Total TBT
—
Long tasks
—
05 / Supporting Metrics
TTFB · FCP Server & Paint
Not direct ranking signals, but high TTFB drags LCP down every time. Fix these and Core Vitals improve as a side effect.
TTFB
Time to First Byte
Time from navigation start until the first byte of the response arrives. The foundation all other metrics rest on — if TTFB is slow, everything is slow.
Good <800msPoor >1.8s
Causes
Slow server — no caching, cold starts
Heavy DB queries at render time (SSR)
No CDN — high latency to origin
Multi-step redirect chains
Serverless function cold start
Fix
CDN + edge caching (Cloudflare, Vercel Edge)
SSG / ISR for content that doesn't change per-request
Stale-While-Revalidate cache strategy
Eliminate redirect chains
Live demo — TTFB waterfall
Navigation waterfall:
FCP
First Contentful Paint
When the first pixel of text or image appears on screen. Different from LCP — FCP fires earlier, on the first content element, not the largest.
Good <1.8sPoor >3s
Causes
Render-blocking stylesheets in <head>
High TTFB dragging FCP down with it
Fonts blocking render (font-display: block)
JavaScript in <head> without async/defer
Fix
Inline critical CSS, defer the rest
font-display: swap or optional
Always async or defer scripts in <head>
Reduce TTFB first — FCP will follow
FCP vs LCP — what fires when
▼ FCP
▼ LCP
■ FCP fires at 1.2s■ LCP fires at 2.2s (hero image)
Summary
The full picture
All metrics, thresholds, and their relationship to each other.
LCP — CORE
≤ 2.5s
Loading — largest visible element rendered
INP — CORE
≤ 200ms
Interactivity — click to next paint latency
CLS — CORE
≤ 0.1
Stability — no unexpected layout shifts
TBT
≤ 200ms
Lab proxy for INP — long task sum
TTFB
≤ 800ms
Server speed — first byte from origin
FCP
≤ 1.8s
First pixel of text or image on screen
The dependency chain — fix in this order
1. TTFB server speed→2. FCP first paint→3. LCP largest paint→4. TBT/INP interactivity→5. CLS stability
⚠ A slow TTFB will make LCP bad no matter what else you do. Always fix the server layer first. ℹ TBT is only measured in Lighthouse (lab). Real-world INP is collected by Chrome from actual users (field data). ✓ The three Core Vitals (LCP, INP, CLS) are the only ones Google uses as search ranking signals.
Quick-win checklist
Live audit — align.vn
align.vn CWV Audit
Analysis from HTML source of align.vn/blog/what-is-a-moodboard/ Each criterion checked against Core Web Vitals standards.