Frontendcore web vitalslcpinpclsfield dataweb

Core Web Vitals as Signals, Not Scores

LCP asks "did anything useful appear?", INP asks "did the page answer when I touched it?", CLS asks "did it move while I was reading?". They are proxies for three different user frustrations — and they are web-platform definitions that have already changed once.

Follow the diagnosis

Frame the diagnosis

Performance work starts from a symptom and a signal — never from a resource dashboard.

Diagnostic question
Which user frustration does each vital actually measure, and why does the score in my lab run disagree with what my users experience?
Symptom
A dashboard shows a vitals score that is either reassuring or alarming, and nobody can say which specific user experience it corresponds to or what would move it.
Signal
The p75 of each vital in field data, per route and device class. The misleading signal is a single composite score from one lab run, which compresses three unrelated problems into one number you can optimise without helping anyone.
SymptomSignalMeasurementHypothesisEvidenceRoot CauseChangeValidationRegression Check

Three metrics, three unrelated complaints

Web-specific · Core Web Vitals are web-platform metric definitions maintained by the Chrome team; the metric set and thresholds have changed over time (INP replaced FID as a Core vital in 2024)

The vitals are not three views of one problem. Each is a proxy for a distinct thing a user complains about, with a distinct cause and a distinct fix. Treating them as a single "performance score" is how teams end up optimising the one that is easiest to move rather than the one their users are suffering from.

Because they are platform definitions rather than physical quantities, they change. INP replaced FID as a Core Web Vital in 2024 precisely because FID measured only the delay before the *first* interaction was processed, which was easy to look good on and poor at describing real responsiveness. Write the metric definitions down with a date, and re-check them: a threshold you memorised two years ago may no longer be current.

The threshold values are also chosen by the platform, not derived from your users. "Good" is a useful shared vocabulary and a poor substitute for knowing what your own funnel does at 2s versus 4s. Use the thresholds to communicate; use your own data to decide.

What each vital is a proxy for
VitalUser complaint it proxiesUsually caused byFixed by
LCP — Largest Contentful Paint"I stared at a blank page"Late-discovered hero resource, render-blocking CSS/JS, slow TTFBResource priority and discoverability, less blocking work
INP — Interaction to Next Paint"I tapped and nothing happened"Long tasks occupying the main thread, heavy event handlersBreaking up work, yielding, moving computation off the main thread
CLS — Cumulative Layout Shift"It moved and I clicked the wrong thing"Images without dimensions, late-injected banners, web fonts swappingReserving space up front for anything that arrives late

Why your lab number and your field number disagree

Web-specific · Synthetic browser testing versus real-user monitoring

A lab run measures one device, one connection, a cold cache, no extensions, no third-party consent banner, and an interaction pattern chosen by the tool. Field data measures every device your users own, warm and cold caches, real network variance, and interactions people actually perform.

INP is the sharpest example. A lab tool has to *simulate* interaction, so it typically reports responsiveness under conditions no user reproduces; the field value comes from actual taps, including the one a user made while a third-party script was executing. It is completely normal for a lab run to look excellent and field INP to be poor, and that gap is information rather than an error.

The reverse gap also happens: CLS in the lab can look terrible because the tool loads with an empty cache and no consent state, while returning users with a cached font and a dismissed banner see almost no shift. Neither number is wrong; they are answers to different questions, exactly as in The Half of the Budget You Cannot See From the Server.

Same route, two sourcesILLUSTRATIVE
SignalValueWhat it tells youVerdict
LCP — lab, desktop profile1.1sFast machine, fast link, cold cache. A regression baseline, nothing morenormal
LCP — field p75, mobile4.2sThe population your users are actually insmoking gun
INP — labnot reported / simulatedLab tools cannot observe interactions users did not makesuspect
INP — field p75, mobile380msReal taps landing on a busy main threadsmoking gun
CLS — lab0.24Cold cache, no consent state, font swap on every loadsuspect
CLS — field p750.03Repeat visitors with cached fonts barely see itnormal

Moving the metric versus fixing the experience

Web-specific · Core Web Vitals measurement windows and attribution are browser-platform behaviours

Every one of these metrics can be gamed, and the gaming is usually easier than the fix. That makes them dangerous as targets: the moment a number becomes a goal, someone will find the shortcut, and the shortcut is almost always invisible to the metric and obvious to the user.

The honest test is whether the change would still be worth shipping if the metric did not exist. Reserving space for an ad slot genuinely stops content jumping under the reader's thumb; hiding the shifting element until it settles makes CLS report a lovely number while the user still waits. One is a fix, the other is a report.

This is why the vitals belong on a dashboard next to a business metric and a qualitative signal, not alone. If completion rate did not move and users still say "it feels laggy", the number improved and the product did not — the same discipline Measure Before You Optimize applies to any optimisation.

Moves the metric, not the experience
1# CLS: hide the late element until it settles
2.promo-banner { visibility: hidden }
3# ...reveal after layout stabilises
4
5# LCP: mark a tiny always-present element as the "largest"
6# so LCP fires early while the real hero is still loading
7
8# INP: defer the handler past the measurement window
9# so the interaction "responds" before doing the work
10
11# Result: green dashboard. User still waits, still mis-taps.
Fixes the cause the metric was pointing at
1# CLS: reserve the space the element will occupy
2.promo-slot { min-height: 90px } /* known at render time */
3<img src=hero.avif width=1200 height=600> /* aspect ratio reserved */
4
5# LCP: make the real hero discoverable and prioritised
6<link rel=preload as=image href=hero.avif fetchpriority=high>
7
8# INP: break the 300ms handler into yielding chunks
9# so the next paint is not blocked by one long task
10
11# Result: green dashboard AND the page stops jumping.

Both columns produce the same metric movement. Only one changes what the user experiences, which is why a vital is a signal to investigate rather than a target to hit.

Key points

  • LCP, INP and CLS proxy three unrelated frustrations — blank screen, unresponsive tap, shifting content — with three different causes and fixes.
  • They are platform definitions with a history of changing; INP replaced FID as a Core vital in 2024. Re-check the definitions rather than trusting memory.
  • Lab and field values for the same vital routinely disagree, and the disagreement is information about cache state, device and real interaction patterns.
  • Every vital can be gamed more cheaply than it can be fixed; the test is whether the change would be worth shipping without the metric.
  • Report vitals as p75 per route and device class, next to a business metric — never as a single composite score.

Follow the diagnosis

The causal chain, hop by hop — and the readings that invite the wrong conclusion.

  1. 1
    HTML → parser: the hero image is referenced deep in the document or injected by script, so the preload scanner never sees it early.
  2. 2
    Parser → network: script and stylesheet fetches occupy connection priority; the hero image queues behind them.
  3. 3
    Main thread → paint: a 400ms script task runs before layout can complete, so the largest element cannot paint even once its bytes have arrived.
  4. 4
    User → main thread: a tap arrives during that task; input delay is added to processing and presentation, and INP records the total.
  5. 5
    Late banner → layout: an element with no reserved space is inserted above the fold, content jumps, and CLS records the shift the user just mis-tapped through.
What this evidence makes people conclude — wrongly
  • "Our Lighthouse score is 98, vitals are fine." That is one synthetic device with a cold cache; field p75 on mobile is the number your users live in.
  • "CLS is 0, so layout is stable." Check whether shifts are simply happening outside the measurement window or being hidden rather than prevented.
  • "INP is bad, we need a faster server." INP is main-thread responsiveness on the device; server latency is largely irrelevant to it.
  • "LCP improved, so the page is faster." Confirm the LCP element is still the element users care about — LCP can improve because a smaller element became "largest".
  • "The vitals are the goal." They are proxies chosen by a platform. Your funnel is the goal.

Measure, fix, validate

An optimization is not finished until the metric that motivated it has moved.

How to measure it
  • • Field p75 of LCP, INP and CLS per route, segmented by device class and connection, from real-user telemetry.
  • • The LCP element identity per route — knowing *which* element is the largest paint is usually more actionable than the timing itself.
  • • Attribution data for INP: which interaction, which handler, and how much of the delay was input delay versus processing versus presentation.
  • • CLS sources: which elements shifted and what arrived late to cause it.
  • • Lab values for the same three metrics per commit, on a pinned profile, purely as a regression trend.
What actually fixes it
  • • Fix the vital with the worst field p75 in the worst segment first, and fix its named cause — not the metric.
  • • For LCP: make the largest element discoverable in the initial HTML, give it fetch priority, and remove blocking work ahead of it.
  • • For INP: break long tasks into yielding chunks and move computation off the main thread (see [[rendering-and-layout]] and [[event-loop-lag]]).
  • • For CLS: reserve space for every element whose size is known before it arrives — images with dimensions, slots with min-heights, fonts with matched metrics.
  • • Re-read the current metric definitions before acting on a threshold you remember from a previous project.
How you know it worked
  • • Field p75 for the specific vital, specific route and specific device segment, over a window long enough to be stable — 7 days before and after is a reasonable default.
  • • Attribution data confirming the named cause disappeared: the LCP element is now the intended one, the long task no longer appears in INP attribution, the shifting element no longer appears in CLS sources.
  • • The other two vitals did not regress — INP getting worse while LCP improves is a common trade when work is deferred rather than removed.
  • • A business metric over the same window, so a percentile improvement caused by losing impatient users is distinguishable from a real one.
What it costs
  • • Reserving layout space for late content means visible empty regions during load — a real design cost paid to stop the jump.
  • • Preloading and prioritising the hero competes for bandwidth with other resources; over-prioritising simply moves the queue.
  • • Yielding long tasks into chunks adds scheduling complexity and can make total work slightly slower while making the page far more responsive.
  • • Collecting attribution-level field data increases client telemetry volume and its associated privacy surface.
Stop it coming back
  • A CI budget on the lab values per route, failing on a meaningful regression rather than on noise.
  • A field alert on p75 per vital per device class, windowed so it does not fire on traffic-mix changes.
  • A review rule: anything inserted above the fold must declare its dimensions; anything added to the first-screen bundle must state its main-thread cost.
  • A dated note recording which metric definitions and thresholds the team is targeting, reviewed periodically because the platform changes them.

Accuracy

Performance numbers are conditional. These are the conditions.

What these numbers depend on
  • WEB-SPECIFICCore Web Vitals are web-platform definitions. The metric set, thresholds and measurement methodology are maintained by the platform and have changed — INP replaced FID as a Core vital in 2024. Verify current definitions rather than relying on memory.
  • ILLUSTRATIVEAll lab-versus-field numbers here are invented to show the characteristic shape of the disagreement, not measurements from any real site.
  • ENVIRONMENT-SPECIFICField values depend on your specific user device and network mix; two sites with identical code can report very different vitals.

Misconceptions

Claim
“The vitals are three ways of measuring page speed.”
Reality
They measure three unrelated frustrations with three different causes. A page can have excellent LCP and terrible INP, and the fixes have nothing in common.
Claim
“A good lab score means good field vitals.”
Reality
Lab runs use one device, one connection and a cold cache with no real interactions. Field INP in particular routinely looks much worse, because real taps land on a real main thread.
Claim
“CLS of 0 means the layout never shifts.”
Reality
It can also mean shifts happen outside the measured window, or that a shifting element was hidden until it settled. Check the attribution data, not just the score.

Apply it