Confirmed directly against the real, currently-installed web-vitals v5.3.0 library: there is no onFID export anywhere in its API — FID was fully replaced by INP as a Core Web Vital, and any material still teaching FID as one of 'the three' is testing stale knowledge. This topic covers what each of the three CURRENT vitals actually measures, their confirmed good/poor thresholds, and the exact page behaviors that move each one.
core-web-vitalslcpclsinpperformance-metrics
Knowledge Check
20 questions · pass at 70%
0/20
easymcq
1. Confirmed via the currently-installed web-vitals v5.3.0 library's own exports, what are the three CURRENT Core Web Vitals?
Interview Questions
2 questions
Cheatsheet
Download-ready reference
Cheatsheet
CONFIRMED (web-vitals v5.3.0's actual exports): current Core Web
Vitals = LCP, CLS, INP. NO onFID export — FID was REPLACED by INP
in March 2024. Citing "LCP/FID/CLS" as current = stale knowledge.
LCP (Largest Contentful Paint) — CONFIRMED thresholds [2500, 4000]:
good ≤2500ms / needs-improvement 2500-4000ms / poor >4000ms
Measures the LARGEST element's paint time — NOT the first (that's
FCP, a supporting metric, NOT itself a Core Web Vital).
CLS (Cumulative Layout Shift) — CONFIRMED thresholds [0.1, 0.25]:
good ≤0.1 / needs-improvement 0.1-0.25 / poor >0.25
UNITLESS score. CUMULATIVE across the ENTIRE visit, not just
initial load — a later user-triggered shift still counts.
INP (Interaction to Next Paint) — CONFIRMED thresholds [200, 500]:
good ≤200ms / needs-improvement 200-500ms / poor >500ms
Full interaction→paint duration (incl. handler execution), across
the WORST interaction of the ENTIRE visit — not just the first
(that limitation is exactly why FID got replaced).
Metric object (confirmed real shape): { name, value, rating
('good'|'needs-improvement'|'poor' — PRE-COMPUTED for you), delta, id }
FIELD data (web-vitals, real users) vs LAB data (Lighthouse,
simulated/controlled) — COMPLEMENTARY, not redundant. A lab/field
gap is informative (real device/network diversity), not a bug.
Cheapest fix: reserve layout space for async content → near-free CLS
win. LCP fix = usually resource DISCOVERY/PRIORITY timing, not
raw bandwidth (see network-optimization's preload technique).
The beginner framing: "the page feels slow" is subjective and useless for tracking whether things are getting better or worse — Core Web Vitals are Google's attempt to turn "feels slow" into a small set of specific, measurable numbers that correlate with real user-perceived experience, used both for internal performance tracking and as an actual Google Search ranking signal.
The three current vitals — confirmed via the installed library's real API#
import { onLCP, onCLS, onINP } from "web-vitals";// Confirmed: this is the ENTIRE current Core Web Vitals API surface — no onFID export exists.
Confirmed directly against this app's own installed web-vitals v5.3.0: the library exports onCLS, onLCP, onINP (plus the supporting, non-Vital metrics onFCP and onTTFB) — there is no onFID export anywhere in the package. This directly confirms a real, important version-currency fact: FID (First Input Delay) was fully replaced by INP as the third Core Web Vital in March 2024 — any material still describing "the three Core Web Vitals" as LCP/FID/CLS is testing outdated knowledge.
Measures: when the LARGEST visible element (often a hero image, a big heading, or a large text block) finishes rendering.Confirmed thresholds (web-vitals v5.3.0 LCPThresholds export): [2500, 4000] good: ≤ 2500ms needs improvement: 2500ms – 4000ms poor: > 4000ms
LCP is NOT "when the page first shows something" (that's FCP, a related but distinct, non-Vital metric) — it specifically tracks the LARGEST element, which is usually the thing a user actually came to see. A page can paint text almost instantly (fast FCP) while its LCP element (a large hero image) doesn't finish loading until much later — these are genuinely different moments, and optimizing one doesn't automatically fix the other.
Step through all three vitals plotted against real page-load milestones:
LCP / CLS / INP plotted against real page-load milestones — confirmed thresholds (web-vitals v5.3.0)
step 1 / 5
// Navigation starts — the browser begins fetching the document
0ms900ms1800ms2700ms3600ms4500ms
Navigation
0ms
LCP good (≤2500ms)needs improvementpoor (>4000ms)
The timeline starts at navigation. The background bands are the CONFIRMED real thresholds from web-vitals v5.3.0's own exported constants — LCP is 'good' at or under 2500ms, 'poor' past 4000ms.
Measures: the TOTAL, cumulative amount of UNEXPECTED layout movement during the page's lifetime (not just initial load — the WHOLE session).Confirmed thresholds (CLSThresholds export): [0.1, 0.25] good: ≤ 0.1 needs improvement: 0.1–0.25 poor: > 0.25
CLS is a unitless score, not a time — it's computed from how much visible content moves multiplied by how far it moves, summed across every unexpected shift for the ENTIRE time a user is on the page, not just during initial load. A late-loading ad that pushes text down after a user has already started reading is the textbook cause — the shift is "unexpected" specifically because nothing signaled space should be reserved for it in advance.
Measures: from a user interaction (click, tap, key press) to the browser's NEXT PAINT reflecting the result — the WORST (or near-worst) such interaction across the entire page visit.Confirmed thresholds (INPThresholds export): [200, 500] good: ≤ 200ms needs improvement: 200-500ms poor: > 500ms
INP replaced FID specifically because FID only measured the delay before an event handler STARTED running for the FIRST interaction — it said nothing about how long the handler itself took, and nothing about interactions after the first one. INP measures the full interaction-to-paint duration (including the handler's own execution time and any rendering work it triggers) and considers interactions throughout the ENTIRE visit, not just the first — a page that's snappy on first click but janky on the fifth now correctly gets penalized.
import { onLCP } from "web-vitals";onLCP((metric) => { console.log(metric.name, metric.value, metric.rating);});// Suppose this logs: "LCP" 3200 ???
Given the confirmed threshold values, what rating does a 3200ms LCP receive?
Solution
needs-improvement — confirmed directly against LCPThresholds ([2500, 4000]): 3200ms is above the 2500ms "good" ceiling but below the 4000ms "poor" floor, landing in the middle band. This is exactly the rating field the real Metric object exposes (confirmed via the library's own TypeScript types: rating: 'good' | 'needs-improvement' | 'poor') — the library computes this classification for you; you don't need to hand-roll threshold comparisons against the raw value, though understanding the actual boundary numbers (not just the labels) is what lets you reason about how close to "good" a "needs-improvement" score actually is.
Build a minimal Web Vitals collector — the actual mechanism behind sending real-user-measurement data to an analytics endpoint:
import { onLCP, onCLS, onINP } from "web-vitals";function sendToAnalytics(metric) { const body = JSON.stringify({ name: metric.name, value: Math.round(metric.value), rating: metric.rating, id: metric.id, // unique per metric instance — dedupes if reported more than once }); // navigator.sendBeacon is preferred here: it reliably delivers the request // even if the page is being unloaded right after this call, unlike a normal fetch() if (navigator.sendBeacon) { navigator.
This is the actual mechanism real-user-monitoring (RUM) tools use: subscribe to each metric's callback, and report REAL, field-measured values from REAL users' actual devices and networks — this is fundamentally different from and complementary to lab-based tools like Lighthouse (covered in the next topic), which simulate a single, consistent set of conditions rather than capturing the full diversity of real visitors.
LCP and CLS are both direct consequences of the browser's paint/layout pipeline covered in Browser Internals & Critical Rendering Path — LCP is literally "when did the paint step finish for the largest element," and CLS is "how much did the layout step move things unexpectedly." Chrome's own DevTools, covered in Browser DevTools Mastery, expose a live Performance panel that visualizes these exact same underlying browser events — the web-vitals library's numbers and what you'd see stepping through a DevTools trace by hand are measuring the identical underlying events, just one is automated and the other is manual inspection.
1. Still citing FID as one of the three Core Web Vitals#
"The three Core Web Vitals are LCP, FID, and CLS" // ❌ outdated — confirmed FID has no export in current web-vitals
FID was replaced by INP in March 2024 — confirmed directly against the currently-installed library, which has zero onFID export. Citing FID as current signals stale knowledge, exactly the kind of gap this course exists to close.
"Our FCP is 800ms, so our Core Web Vitals must be great." // ❌ FCP isn't even a Core Web Vital
FCP measures the FIRST paint of anything; LCP measures the LARGEST element specifically, and is the actual Core Web Vital. A fast FCP says nothing about LCP — the hero image driving LCP can still load far later.
// "We fixed all our layout shifts during page load, CLS should be 0 now" // ❌ CLS accumulates for the WHOLE visit
CLS is cumulative across the entire page lifetime a user is present for — a shift caused by a late user-triggered UI change (an accordion, a dynamically-loaded comment section) still counts, not just shifts during the initial load sequence.
Cite the CURRENT three vitals (LCP, CLS, INP) — verify this is still accurate before repeating it, the same discipline this course applied to catch the OWASP 2021-vs-2025 staleness gap elsewhere.
Measure BOTH lab data (Lighthouse, simulated) and field data (real-user web-vitals reporting) — they answer different questions ("how does this perform under controlled conditions" vs. "how do REAL users actually experience it"), covered together with Lighthouse in the next topic.
Reserve space for content that loads asynchronously (images with explicit width/height, ad slots with a fixed min-height) — the single most common, most fixable cause of CLS.
Identify the LCP element explicitly (DevTools' Performance panel labels it) and ensure ITS specific resource is prioritized — a fast page can still have a slow LCP if the largest element's own resource is discovered late.
Treat INP as measuring the WORST interaction across a visit, not just the first — a single janky action deep in a session (not just the initial page load) can tank this metric.
LCP improvements are almost always about resource DISCOVERY and PRIORITY timing (when does the browser learn about and start fetching the LCP resource), not raw download speed — the network-optimization topic covers preload as the direct mechanism for pulling this earlier.
CLS fixes are nearly free performance-wise (reserving layout space costs nothing at runtime) — this is one of the few "vitals" improvements that's pure upside with essentially no tradeoff against other metrics.