Core Web Vitals targets and how to measure them for a marketing site
Pass thresholds (LCP ≤ 2.5 s, INP ≤ 200 ms, CLS ≤ 0.1 at the 75th percentile, mobile and desktop separately), how to combine lab and field data, and page-weight budgets that keep a marketing site inside them.
Overview
What: the pass/fail performance rule Motif applies to public pages.
Why: Core Web Vitals are Google's user-centric metrics for loading (LCP), responsiveness (INP) and visual stability (CLS). web.dev defines "good" as LCP ≤ 2.5 s, INP ≤ 200 ms and CLS ≤ 0.1, assessed at the 75th percentile of page loads, segmented by mobile and desktop. Meeting them at p75 means most real visitors get a fast, stable page — marketing pages are often the first impression and the heaviest (video, fonts, tags).
Use: as a launch gate (lab), and as an ongoing monitor (field) once traffic exists.
Note: lab tools (Lighthouse) measure one synthetic load and cannot measure INP directly; Total Blocking Time is the lab proxy.
Implementation
- Targets (p75, per device class) — good / needs improvement / poor, as published 2026-06-30 (re-verify at each freshness review): LCP ≤ 2.5 s / 2.5–4.0 s / > 4.0 s · INP ≤ 200 ms / 200–500 ms / > 500 ms · CLS ≤ 0.1 / 0.1–0.25 / > 0.25. Motif launch lab targets are stricter to leave headroom: LCP ≤ 2.0 s and TBT ≤ 200 ms on Lighthouse mobile (simulated slow 4G, 4× CPU), CLS ≤ 0.05.
- Budgets for a landing page (mobile, compressed): the binding one is the critical path: HTML + render-blocking CSS + preloaded fonts + LCP image ≤ 250 KB (lab: Chromium, slow 4G; each extra 100 KB adds about 0.5 s of LCP). Within it: HTML ≤ 30 KB · critical CSS inlined (< 14 KB) and total CSS ≤ 60 KB · JS ≤ 150 KB (first-party + third-party), deferred past LCP · first-view fonts ≤ 2 files and ≤ 150 KB · images ≤ 1 MB total, mobile hero ≤ 100 KB target (150 KB hard ceiling) · requests ≤ 50 · third-party origins ≤ 5. A page built at the old ceilings (HTML 50 KB, hero 200 KB, three preloaded fonts) measured lab LCP 2.4–2.9 s, missing the 2.0 s target; the tight build measured 1.6–1.8 s.
- Lab measurement (pre-launch) — the regression guard, not the release gate: Lighthouse CI on every template page, 3 runs median, mobile and desktop presets; fail the build when bundle-size budgets or lab vitals regress, but gate releases on field P75 once traffic exists. Also a WebPageTest run on a mid-range Android profile for the home and pricing pages.
- Field measurement (post-launch) — and why lab is not enough: Google's ranking signal is CrUX field data at P75; the Lighthouse score is not used, and the Chrome team itself notes the score often does not correlate with field vitals. CrUX is a rolling 28-day window (it cannot validate today's deploy) and is Chrome-only — Safari/iOS visitors are absent — so first-party RUM is required for fast iteration and full coverage. Use the
web-vitalslibrary rather than hand-rolledPerformanceObservercode; it handles bfcache resets, background-tab exclusion and outlier trimming correctly:
import { onLCP, onINP, onCLS } from 'web-vitals/attribution';
const send = m => { const body = JSON.stringify({ name: m.name, value: m.value, id: m.id, nav: m.navigationType, attr: m.attribution });
(navigator.sendBeacon && navigator.sendBeacon('/vitals', body)) || fetch('/vitals', { body, method: 'POST', keepalive: true }); };
onLCP(send); onINP(send); onCLS(send);
Gate on consent where required; review p75 weekly by template and device.
- Attribute regressions: log the LCP element, the INP interaction target and the largest shift sources; tie each to a template.
- bfcache eligibility: never add an
unloadlistener (it disqualifies the page from bfcache in Chrome and Firefox — audit third-party tags too); usepagehidefor cleanup andpageshowto resume; addbeforeunloadonly while there are unsaved changes and remove it after save; noCache-Control: no-storeon public pages. - Record results in the acceptance evidence with date, tool version, device profile and URL.
Verification
- Lighthouse CI config committed with assertions: LCP ≤ 2.0 s (mobile lab), TBT ≤ 200 ms, CLS ≤ 0.05, and resource budgets including the ≤ 250 KB critical path. Lab checks never cite p75; a single lab run is judged only against the lab targets.
- All template pages pass lab targets on mobile and desktop presets (median of 3).
- Field collection wired (web-vitals) with p75 dashboard segmented by template and device, gated by consent where required.
- No
unloadlisteners; public HTML not served withno-store(DevTools Application › Back/forward cache test passes). - Evidence record includes tool versions and device profiles.
Sources:
- https://web.dev/articles/vitals
- https://web.dev/articles/optimize-lcp
- https://web.dev/articles/optimize-inp
- https://web.dev/articles/optimize-cls
- https://web.dev/articles/bfcache
- https://web.dev/articles/vitals-tools
- https://github.com/GoogleChrome/web-vitals
- https://vercel.com/blog/how-core-web-vitals-affect-seo
- https://web.dev/articles/lcp
- https://web.dev/articles/extract-critical-css
Limitations
- Lab and field often disagree; field p75 is the truth once you have enough traffic, lab is a regression guard.
- Values verified 2026-09-26: 17 checked against standards (web.dev metric definitions and thresholds), 8 measured in Chromium lab tests (Chromium only; slow 4G, 4× CPU, served locally, so real first loads add a few round trips) and 2 against published guidelines. Request and third-party-origin counts remain judgement defaults.
- Budgets are starting points for a content site; rich interactive demos need their own budgets and may justify lazy-loading after interaction.
- Thresholds can change (INP replaced FID in 2024); review at each freshness date.
- Consent requirements may limit field-data collection in some regions, biasing samples.