Core Web Vitals & Performance Metrics
Performance metrics exist because "it feels fast" isn't measurable, reproducible, or actionable. Core Web Vitals give us a shared vocabulary between engineers, product managers, and Google's search algorithm. Senior engineers think in metrics, not intuition.
The Three Core Web Vitals
Largest Contentful Paint (LCP) â Loading
What it measures: Time until the largest visible content element renders. Usually a hero image, <h1>, or video poster.
Thresholds: Good < 2.5s | Needs Improvement 2.5-4s | Poor > 4s
What counts as LCP element:
<img>elements<image>inside<svg><video>poster image- Elements with
background-imagevia CSS - Block-level text elements (
<h1>,<p>, etc.)
Root causes of poor LCP:
| Cause | Impact | Fix |
|---|---|---|
| Slow TTFB (server response) | Delays everything downstream | CDN, edge computing, caching |
| Render-blocking CSS/JS | Delays first paint | Inline critical CSS, defer scripts |
| Slow resource loading | LCP image downloads late | Preload, fetchpriority="high", modern formats |
| Client-side rendering | LCP waits for JS execution | SSR/SSG, move LCP content to HTML |
Optimization playbook:
<!-- Preload the LCP image with high priority -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<!-- Inline critical CSS for above-fold content -->
<style>
.hero { min-height: 60vh; display: grid; place-items: center; }
.hero img { width: 100%; height: auto; }
</style>
<!-- LCP image: no lazy loading, explicit dimensions, high priority -->
<img src="/hero.webp" alt="Hero" width="1200" height="600"
fetchpriority="high" decoding="async">Interaction to Next Paint (INP) â Responsiveness
What it measures: The worst interaction latency during the page visit. Replaced FID (which only measured the first interaction).
Thresholds: Good < 200ms | Needs Improvement 200-500ms | Poor > 500ms
The three phases of an interaction:
User clicks button
â
âââ Input Delay (waiting for main thread) âââ
âââ Processing Time (event handler execution) âââ INP measures all three
âââ Presentation Delay (rendering the update) âââRoot causes of poor INP:
| Cause | Phase | Fix |
|---|---|---|
| Long tasks blocking main thread | Input delay | Break up work, use Web Workers |
| Heavy event handlers | Processing | Optimize logic, defer non-critical work |
| Expensive re-renders | Presentation | Memoization, virtualization, transitions |
| Large DOM size | Presentation | Simplify DOM, use virtualization |
Optimization patterns:
// â Heavy handler blocks next paint
button.addEventListener('click', () => {
const result = processThousandItems(data); // 300ms
updateUI(result);
});
// â
Yield to browser between processing and painting
button.addEventListener('click', async () => {
showSpinner();
await scheduler.yield(); // Let browser paint the spinner
const result = processThousandItems(data);
updateUI(result);
});
// â
React: use transitions for non-urgent updates
const [isPending, startTransition] = useTransition();
function handleFilter(query) {
setInputValue(query); // Urgent: update input
startTransition(() => {
setFilteredResults(filter(query)); // Non-urgent: can be interrupted
});
}Cumulative Layout Shift (CLS) â Visual Stability
What it measures: Sum of all unexpected layout shifts during the page's lifetime. A layout shift happens when a visible element changes position between frames without user interaction.
Thresholds: Good < 0.1 | Needs Improvement 0.1-0.25 | Poor > 0.25
Root causes:
| Cause | Fix |
|---|---|
| Images without dimensions | Always set width and height |
| Ads/embeds without reserved space | Use min-height placeholder |
| Dynamic content inserted above viewport | Insert below or use content-visibility |
| Web fonts causing text reflow | font-display: optional or font-display: swap with size-adjust |
| CSS animations using layout properties | Use transform instead |
<!-- Always set dimensions to prevent shift -->
<img src="photo.jpg" width="800" height="600" alt="Photo">
<!-- Reserve space for dynamic content -->
<div style="min-height: 250px; contain: layout;">
<!-- Ad or async content loads here -->
</div>/* Prevent font-swap layout shift */
@font-face {
font-family: 'Custom';
src: url('/custom.woff2') format('woff2');
font-display: optional;
size-adjust: 105%;
ascent-override: 95%;
}Supporting Metrics
| Metric | What it Measures | Why it Matters |
|---|---|---|
| TTFB (Time to First Byte) | Server response time | Directly delays every other metric |
| FCP (First Contentful Paint) | First text/image visible | User perceives page is loading |
| TBT (Total Blocking Time) | Sum of long task time over 50ms | Lab proxy for INP |
| SI (Speed Index) | How quickly content is visually populated | Overall perceived speed |
| TTI (Time to Interactive) | When page is fully interactive | Deprecated but still referenced |
Measuring: Lab vs Field
| Lab (Synthetic) | Field (RUM) | |
|---|---|---|
| Tools | Lighthouse, WebPageTest, DevTools | web-vitals library, CrUX, Analytics |
| When | Development, CI/CD | Production, real users |
| Conditions | Controlled (simulated device/network) | Real (diverse devices, networks) |
| INP | Can't measure (TBT used as proxy) | Full INP measurement |
| Percentile | Single run | p75 is the target (75th percentile) |
import { onLCP, onINP, onCLS } from 'web-vitals';
function sendToAnalytics({ name, value, id, rating }) {
analytics.track('web_vital', {
metric: name,
value: Math.round(value),
rating,
id,
url: window.location.href,
connection: navigator.connection?.effectiveType,
});
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);Performance Budgets
Set limits, not aspirations:
// Lighthouse CI config
{
"assertions": {
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"total-blocking-time": ["error", { "maxNumericValue": 300 }],
"resource-summary:script:size": ["error", { "maxNumericValue": 200000 }]
}
}Budget violations break the build. Not "warn" â break.
Interview Signal
Senior candidates demonstrate:
- Metric precision â Knowing what each metric measures, the three INP phases, what elements qualify for LCP
- Root cause mapping â Connecting poor scores to specific technical causes
- Fix prioritization â Addressing the highest-impact issues first (TTFB before image optimization)
- Measurement sophistication â Lab vs field, p75 target, RUM vs synthetic
- Business connection â Linking metrics to conversion rates, SEO ranking, and user retention