# Core Web Vitals > Google's three user-experience metrics: LCP, INP, and CLS. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/core-web-vitals Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## Summary Core Web Vitals are three field-measurable metrics that quantify real user experience: LCP tracks loading, INP tracks responsiveness (replacing FID in March 2024), and CLS tracks layout stability. Good thresholds are LCP ≤2.5 s, INP ≤200 ms, CLS ≤0.1. They drive Google Search ranking and are the sharpest signal for user-facing performance regressions. ## The three Core Web Vitals at a glance | Metric | What it measures | Good threshold | Top cause of failure | | --- | --- | --- | --- | | **LCP** | Time to render largest above-fold image or text block | ≤ 2.5 s | Slow TTFB, render-blocking CSS, unpreloaded hero image | | **INP** | 98th-percentile input-to-next-paint delay across all interactions | ≤ 200 ms | Long JS tasks blocking main thread between input and frame | | **CLS** | Sum of impact × distance for unexpected layout shifts | ≤ 0.1 | Images or iframes without explicit dimensions, late-injected DOM | ## Note: Lab vs field data Lighthouse (lab) runs a single cold load in a throttled environment — good for catching regressions in CI. CrUX (field) reports 28-day rolling percentiles from real Chrome users and is what Google uses for Search ranking. A score that looks fine in Lighthouse can still fail in the field if ads, late fonts, or real interactions spike the metrics. ## Measure all three vitals with web-vitals v4 Install `web-vitals` (npm package by Google). Each `on*` function fires when the metric value is ready and again on updates. Pass `reportAllChanges: true` so INP updates as the user interacts rather than only on page unload. **RUM instrumentation with web-vitals v4** ```ts import { onLCP, onINP, onCLS } from "web-vitals"; import type { Metric } from "web-vitals"; function send(metric: Metric): void { // Replace with your RUM endpoint or analytics call. navigator.sendBeacon("/vitals", JSON.stringify({ name: metric.name, // "LCP" | "INP" | "CLS" value: metric.value, // ms for LCP/INP, unitless for CLS rating: metric.rating, // "good" | "needs-improvement" | "poor" id: metric.id, delta: metric.delta, })); } onLCP(send); onINP(send, { reportAllChanges: true }); onCLS(send, { reportAllChanges: true }); ``` `rating` maps the raw value against good/poor thresholds automatically — alert on regressions without hardcoding numbers. ## Diagram ```mermaid flowchart LR subgraph Metrics LCP["LCP — largest paint ≤ 2.5 s"] INP["INP — interaction paint ≤ 200 ms"] CLS["CLS — layout shift ≤ 0.1"] end subgraph Sources Lab["Lab (Lighthouse) CI regression check"] Field["Field (CrUX / RUM) Ranking signal"] end Lab -->|diagnose| LCP & INP & CLS Field -->|measure| LCP & INP & CLS ``` Lab tools diagnose regressions; field data (CrUX) is the actual ranking signal — they serve different purposes. ## Trade-offs of the CWV framework **Pros** - Ties user perception to measurable numbers, not vague 'feels fast' reports. - CrUX field data gives a 28-day real-device baseline no synthetic tool can match. - INP catches slow interactions that FID missed — full input-to-paint delay. - Three metrics cover load, interactivity, and stability — orthogonal failure modes. **Cons** - CrUX requires enough real-user traffic; low-traffic pages get no field data. - Lab tools can't reproduce session-level INP across many interactions. - CLS is easily gamed by deferring all layout shifts to after user input. - LCP origin attribution is hard: hero image, TTFB, render-blocking CSS, or all three. ## Key terms - **LCP**: Time to render the largest above-the-fold image or text block; good ≤2.5 s. - **INP**: 98th-percentile input-to-paint delay across all interactions; good ≤200 ms. - **CLS**: Sum of impact × distance shift scores for unexpected layout shifts; good ≤0.1. - **CrUX**: Chrome UX Report — 28-day rolling field data from real Chrome users, used for ranking. - **LoAF**: Long Animation Frame — browser entry identifying frames that exceed 50 ms. ## Related topics - [Render Performance](https://fearchitect.com/topics/render-performance-patterns.md): Skip renders, defer slow work, and virtualize long lists. - [Image & Asset Strategy](https://fearchitect.com/topics/image-and-asset-strategy.md): Serve the smallest correct image; preload the LCP one. - [Network Performance](https://fearchitect.com/topics/network-performance.md): Hint, prioritize, and pre-navigate to cut request latency. - [Streaming SSR](https://fearchitect.com/topics/streaming-ssr.md): Flush the HTML shell immediately, then stream the rest as Suspense resolves. - [CI/CD for Frontend](https://fearchitect.com/topics/ci-cd-frontend.md): Automated pipeline from commit to production with quality gates. - [Event Loop & scheduler.yield()](https://fearchitect.com/topics/event-loop-scheduler-yield.md): Break long tasks to keep the main thread responsive and hit INP. ## Further reading - [web.dev — Core Web Vitals](https://web.dev/articles/vitals) - [web.dev — INP (Interaction to Next Paint)](https://web.dev/articles/inp) - [web.dev — Optimize LCP](https://web.dev/articles/optimize-lcp) - [web.dev — Optimize CLS](https://web.dev/articles/optimize-cls) - [web-vitals npm library](https://github.com/GoogleChrome/web-vitals)