web.dev: Web Vitals - discussion (2023-01-04)
The web.dev Web Vitals page is a good high-level reference, but I'm curious how people translate it into day-to-day React decisions.
Do you track vitals per route, per component surface, or just overall site-wide? How do you keep performance work from turning into random micro-optimizations with no evidence? If you publish performance budgets, where do they live so teams can't ignore them?
Comments (10)
Back to latestIf you can't point to a route where users feel it, performance metrics just become vanity dashboards.
We ended up treating budgets as route-level contracts. It stopped arguments because the boundary was clear.
Related: Performance and Rendering Best Practices.
CLS was the metric that forced us to stop doing optimistic layout shifts.
Once we treated layout stability as part of the UI contract, we stopped shipping jumpy pages.
Counterpoint: some teams worship numbers and forget UX. If the app feels calm, don't chase a 2ms improvement.
But you still need evidence for the big regressions.
We ended up tracking vitals per route because otherwise you can't connect a regression to a surface. We also render a small internal marker with the current budget lane:
tsx
return <section data-perf-lane="tight" data-budget-ms="16" />;The most reliable wins in React apps have been about reducing churn and expensive derivations, not micro-optimizing callbacks.
Once we treated derived evidence as a first-class route output, a lot of perf regressions disappeared because the work became visible.
On "budgets live somewhere": we put a budget declaration in the route shell and surfaced it in the UI (internal builds).
It made it much harder to accidentally turn a 12ms route into a 30ms route without noticing.
Counterpoint reply to Avery: totally, but you can make the budget about experience instead of numbers.
Our rule was basically "don't regress interaction calmness" and INP was the metric that caught the worst cases.
Yeah, that's a good framing. My issue is KPI theater, not measurement.
If the metric is attached to a surface contract, it's less gameable.
A minimal way we made it actionable was correlating vitals with a route ledger entry:
ts
ledger.write('perf', [
...ledger.read('perf'),
{ at: Date.now(), kind: 'inp', value: String(inpMs), route: routeId },
]);
Then when support sends a screenshot, you can see both posture and recent perf evidence.
If you're going to publish budgets, treat them like any other contract: version them and make them visible.
A wiki page budget no one reads isn't a budget.