Part III β The Speed Β· Lesson 5.3
Performance Monitoring
In one lineLab tests tell you what changed; field data tells you what users actually got.
Where you've seen itA green Lighthouse score in CI while support tickets say the app is unusable β both are true, on different devices.
Diagram
TWO KINDS OF MEASUREMENT, TWO DIFFERENT JOBS
SYNTHETIC (lab) RUM (field)
ββββββββββββββββββββββββ ββββββββββββββββββββββββ
β same device β β every device β
β same network β β every network β
β same time β β all the time β
β before merge β β after deploy β
ββββββββββββββββββββββββ ββββββββββββββββββββββββ
β β
βΌ βΌ
"did THIS change "what are users
make it slower?" actually getting?"
β β
βββββ gate merges ββββ βββββ alert on regressions
βΌ βΌ
you need BOTHTHE DISTRIBUTION IS THE STORY
users (count)
β²
β ββββ
β ββββ ββ
β ββββ ββββ
β ββββ ββββ ββ ββ
β ββββ ββββ ββββ ββ ββ ββ ββ
βββββ¬βββββ¬βββββ¬βββββ¬βββββ¬βββββ¬ββββΊ LCP
1s 2s 3s 4s 6s 9s
β² β² β²
median p75 p95
1.9s 3.4s 8.2s
Reporting "average 2.4s" hides both the 3.4s and the 8.2s.RUM COLLECTION β a few lines, sent on page hide
web-vitals lib βββΊ onLCP/onINP/onCLS βββΊ beacon βββΊ your endpoint
β
navigator.sendBeacon survives page unload;
fetch() does not.How it works
- Instrument with the
web-vitalslibrary β it implements the exact definitions Chrome reports, including edge cases you'd get wrong by hand. - Send on
visibilitychangeβ hidden, usingsendBeacon, so metrics survive the user navigating away. - Attach dimensions: route, device class, connection type, country, release version. Aggregate numbers without dimensions are undebuggable.
- Alert on p75 by route, not sitewide β a regression on checkout drowns in a sitewide average.
- Correlate with deploys. The first question about any regression is "what shipped?"
import { onLCP, onINP, onCLS } from 'web-vitals';
const send = (metric) =>
navigator.sendBeacon('/rum', JSON.stringify({
name: metric.name, value: metric.value, route: location.pathname,
release: __BUILD_ID__, conn: navigator.connection?.effectiveType,
}));
[onLCP, onINP, onCLS].forEach((fn) => fn(send));Trade-offs
| Source | Strength | Limit |
|---|---|---|
| Lighthouse CI | Comparable, pre-merge | One synthetic profile |
| CrUX | Real Chrome users, free | 28-day rolling, coarse |
| Your own RUM | Your routes, your dimensions | You build and maintain it |
| Vendor RUM | Fast to adopt, rich UI | Cost, another third-party script |
Interview angle
"Lighthouse says 98. Users say it's slow. Who's right?"
- Both β Lighthouse measures one simulated device, field data measures the population.
- Look at p75 and p95 by device class and route; the regression is almost always concentrated somewhere specific.
- Keep lab for gating merges and field for alerting; using either alone leaves a blind spot.
Recap
- Synthetic gates changes; RUM tells the truth.
- Report p75 and p95 with dimensions attached β averages hide the users who leave.
sendBeaconon page hide, or you lose the metrics that matter most.