Topics
v6.0.0
On this page · sections

Core Web Vitals: LCP, INP, CLS

Core Web Vitals are three user-centric metrics Google uses for Search ranking signals — measured at p75 field data, not lab median.

MetricMeasuresGood (p75)Poor (p75)
LCPLargest content paint time≤ 2.5s> 4.0s
INPWorst interaction latency≤ 200ms> 500ms
CLSUnexpected layout shift sum≤ 0.1> 0.25

LCP, INP, and CLS Definitions

Definition

LCP (Largest Contentful Paint) — when the largest above-fold content paints. Candidates: img, video poster, svg image, block with background-image, text blocks (p, h1, …). Excludes below-fold, opacity 0, or elements removed before load.

INP (Interaction to Next Paint) — replaced FID in March 2024. Worst interaction latency across page lifetime: input delay + processing + presentation delay.

CLS (Cumulative Layout Shift) — sum of unexpected layout shift scores. Score = impact fraction × distance fraction. Modern spec uses session windows with caps.

const THRESHOLDS = { LCP: { good: 2500, poor: 4000 }, INP: { good: 200, poor: 500 }, CLS: { good: 0.1, poor: 0.25 }, }; function isLcpCandidate(el) { const tag = el.tagName; if (tag === 'IMG' || tag === 'VIDEO') return true; const style = getComputedStyle(el); if (style.backgroundImage && style.backgroundImage !== 'none') return true; return ['P', 'H1', 'H2', 'H3', 'DIV'].includes(tag); }

All three use 75th percentile field data. CrUX = public Chrome field data (28-day rolling). LCP ≠ TTFB — often after resource load + render.

Interview: p75 because median hides slow tail on low-end devices. INP replaced FID.

Lab vs Field Measurement

Definition

Field (RUM): web-vitals v4+ — onLCP, onINP, onCLS. Send id, delta, rating, navigationType for segmentation.

Lab: Lighthouse, DevTools Performance panel, Performance Insights (LCP breakdown: TTFB, load delay, render delay).

function sendToAnalytics({ name, value, id, rating, navigationType }) { navigator.sendBeacon('/vitals', JSON.stringify({ name, value, id, rating, navigationType, path: location.pathname, })); } import('https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.iife.js') .then(({ onLCP, onINP, onCLS }) => { onLCP(sendToAnalytics); onINP(sendToAnalytics); onCLS(sendToAnalytics); }); // Lab read — use last LCP entry window.addEventListener('load', () => { const entries = performance.getEntriesByType('largest-contentful-paint'); const last = entries[entries.length - 1]; console.log('Lab LCP ms:', last?.renderTime || last?.loadTime); });

LoAF (Long Animation Frames) complements INP for attribution — which callback caused delay. Never ship based on local Lighthouse alone.

Interview: Lab reproduces fixes; field validates impact. CrUX lags 28 days — your RUM is faster feedback.

Fixing Poor LCP

Definition

LCP breakdown in Performance Insights:

Subpart highFix
TTFBCDN, edge cache, SSR cold start
Resource load delayPreload, fetchpriority="high", SSR hero in HTML
Resource load durationWebP/AVIF, compression, CDN
Element render delayCritical CSS inline, defer JS, reduce hydration
<!-- Fix — discoverable in first HTML chunk --> <img src="/hero-800.webp" srcset="/hero-800.webp 800w, /hero-1200.webp 1200w" sizes="(max-width: 768px) 100vw, 1200px" width="1200" height="630" fetchpriority="high" alt="Product hero">

Anti-pattern: fetch hero config in JS after hydration — LCP waits on JS + network + render.

Interview: Put LCP element in server HTML. Preload hero image. Don’t client-fetch the hero.

Fixing Poor INP

Definition

INP = slowest interaction. Common causes:

  • Long tasks on main thread (>50ms) during input handling
  • Large React re-renders on click
  • Heavy third-party scripts
  • Synchronous layout reads in event handlers

Fixes:

  1. Break up long tasks — scheduler.yield(), chunking
  2. Defer non-urgent updates — startTransition, debounce
  3. Optimize event handlers — avoid layout thrashing
  4. Code-split heavy routes
  5. Offload compute to workers
button.addEventListener('click', () => { // Urgent — immediate feedback button.setAttribute('aria-busy', 'true'); // Non-urgent — defer startTransition(() => { applyHeavyFilter(largeDataset); }); });

Debug locally: Performance panel → Interactions → slow tap → follow long task stack. Throttle 4× CPU.

Interview: INP is worst-case interaction, not average. Find the slowest tap/click/keypress.

Fixing Poor CLS

Definition

CLS from elements shifting without user input. Common causes:

  • Images/iframes without width/height
  • Web fonts causing FOIT/FOUT reflow
  • Dynamic content injected above existing content
  • Ads/embeds loading late

Fixes:

/* Reserve space */ .hero img { width: 100%; aspect-ratio: 1200 / 630; } /* Font fallback with similar metrics */ @font-face { font-family: 'Inter'; font-display: swap; size-adjust: 100%; }
<!-- Skeleton with fixed height --> <div class="ad-slot" style="min-height: 250px"></div>

Use transform animations instead of layout-affecting properties. For SPA route changes, maintain scroll position and skeleton dimensions.

Interview: Always set explicit dimensions on media. Use font-display: swap + size-adjust. Never inject content above LCP without reserved space.

Senior Interview Summary

Mental model

LCP → Loading performance (largest paint) INP → Interactivity (worst interaction latency) CLS → Visual stability (unexpected shifts) All measured at p75 field data

Practical rules

  • RUM with web-vitals on 10–30% sample
  • LCP breakdown → fix largest subpart first
  • INP → profile slowest interaction, break long tasks
  • CLS → dimensions on images/ads, font metrics matched
  • Perf budget: LCP < 2.5s, INP < 200ms, CLS < 0.1 at p75

What to say in interviews

“I’d instrument web-vitals in production and segment by device and route. For LCP I’d check the breakdown — if TTFB is high, fix caching; if render delay is high, SSR the hero and inline critical CSS. For INP I’d find the worst interaction in Performance panel and break up long tasks. For CLS I’d reserve space for images and ads and match fallback font metrics.”

Common traps

  • Optimizing Lighthouse score without field validation
  • Confusing LCP with TTFB
  • FID is deprecated — INP is the responsiveness metric
  • Logging every CLS delta in prod instead of session aggregate

Last updated on