PerformanceGENERALBROWSER-SPECIFICSPEC-EVOLVINGDEVICE-SPECIFIC

Measure Before Optimising

Lab profiles and field data are two instruments answering two different questions. Which browser signal to reach for, and why a local recording is a hypothesis rather than evidence.

The intent, the obvious build, and why it breaks

Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.

The question

Before I change any code, which signal tells me what is actually slow for the people using this?

The user intent

Someone said the app feels slow. They cannot say which part, when, or on what — only that it does not respond the way they expect it to.

The obvious build

Open devtools, record the page, find the widest bar in the flame chart, and make that function faster. It is the biggest thing on the screen, so it must be the problem.

Why it breaks

The widest bar is often module evaluation on first load, which runs once and is invisible to a user who has already been in the app for ten minutes complaining that typing is slow.

How it breaks in a real browser
  • The widest bar is often module evaluation on first load, which runs once and is invisible to a user who has already been in the app for ten minutes complaining that typing is slow.
  • The recording was made on a development machine with a warm cache, no third-party tags, a seeded database of forty rows and a network with no latency. None of those four conditions exist for the person who filed the report.
  • The complaint was about one interaction. A page-level recording averages that interaction into everything else, and the one slow tap that made them file the ticket is a few pixels wide.
  • Making the widest bar faster and shipping it produces no measurable change, because the widest bar was never on the path the user was waiting on (The Critical Path Is the Only Path That Pays in Observability).
  • There is no baseline, so after the change nobody can say whether it helped, hurt, or coincided with a third-party script that got slower on the same day.
IntentEventStateUI LogicDOM WorkNetworkLayout / PaintPixelsFeedback

What is actually happening

In the browser, not in the framework.

  • Lab measurement is a synthetic run you control: one device, one network profile, one scripted sequence, repeatable. It answers *why* — it can show you the call stack, the layout invalidation, the retained object.
  • Field measurement is telemetry from real sessions: many devices, many networks, many user behaviours, arriving as a distribution rather than a number. It answers *whether* and *for whom* — it cannot show you a stack trace.
  • The two disagree routinely, and the disagreement is information. A regression visible in the field and invisible in the lab usually means a device class, a network condition or a data volume you do not have locally.
  • Field data is summarised by percentile because the shape matters. A distribution with a fast middle and a long tail and a distribution that is uniformly mediocre have the same mean and completely different causes (Percentiles: Which One, and How Many Users Is That?, The Average Was Fine and Users Were Not in Observability).
  • The browser exposes the raw material through performance entries: navigation and resource timing, long-task and long-animation-frame records, element timing, layout-shift records, and event timing for interactions. A RUM library is a wrapper over those, not a separate source of truth (Real User Monitoring).
  • Devtools panels are different instruments over the same run, not different levels of detail. The Network panel knows about dependency structure and knows nothing about the main thread; the Performance panel knows the opposite (A Mental Model of the Devtools).

What this makes the browser do

And which of it is avoidable.

  • Recording a profile is itself work: the sampler runs on the profiled thread, and a recording with memory and screenshots enabled distorts what it is recording. Long recordings distort more than short ones.
  • Collecting field data is work the user pays for: observers stay registered for the whole session, and a beacon has to be sent before the page goes away.
  • CPU throttling in devtools slows the main thread by a multiplier. It is a useful approximation of a slower device and it is not the same thing — it does not model a smaller cache, a slower GPU, thermal throttling or less memory (The First Ten Seconds Lie in Computer Architecture).
  • Extensions inject script and stylesheets into the page you are profiling. A recording made in a normal browser profile is measuring your extensions as much as your application.

Two instruments, two questions

Lab and field are not "detailed" and "rough". They are instruments with different domains. The lab can tell you exactly which function ran and what it invalidated, for one device you chose. The field can tell you what fraction of real sessions were slow and on what, and can never tell you why.

The productive loop runs in one direction: the field narrows the search, the lab explains the finding, the field confirms the fix. Running it the other way — find something interesting locally, ship it, hope — is how teams end up with a quarter of performance work and a flat graph.

  • Lab answers why. Field answers whether, for whom and how often.
  • Neither answers what should I do about it — that is the rest of this module.
Question you actually haveLab profileField dataThe trap
Is this slow for real people?Cannot say — one device, one runYes, as a distribution per route and device classTreating a local number as a population number
Which function is slow?Yes — stacks, durations, invalidationNo — no stacks, only aggregatesDemanding a stack trace from telemetry, and instrumenting until the page is slower
Did my change help?Only under identical conditionsYes, once enough sessions have accumulatedDeclaring victory from a single re-run on the same laptop
Who is it worst for?Cannot sayYes — segment by device, network, geography, routeAveraging the segments together until the affected group disappears
Why is it slow on this one phone?Yes, if you have the phoneNo — it only tells you the phone class existsEmulating instead of borrowing a real device
Is it getting worse?Only with a stored trace to compare againstYes, that is what the graph is forHaving no stored baseline, so every regression becomes a debate

Which panel answers which question

The most common wasted hour in frontend performance is a Performance recording opened for a question the Network panel would have answered in ten seconds. The panels are not tiers of detail; they observe different subsystems, and picking wrong means reading a lot of true information that is irrelevant to your problem.

Match the panel to the shape of the complaint. "It takes ages before anything appears" is about arrival and blocking. "It appears fast then does nothing when I click" is about the main thread. "It gets slower the longer I use it" is about retention. Three complaints, three subsystems, three tools.

The complaint decides the instrument

Someone says the app is slow. Where do you look first?

Field dashboard, segmented

when Always first. You need to know which route, which device class and which percentile before a local recording means anything.

cost No stack traces, and it needs enough traffic to be stable. Segmenting too finely produces confident noise.

Network panel

when "Nothing appears for ages", a first visit, or anything that smells like arrival and blocking. Read the staircase, not the longest bar.

cost Tells you nothing about main-thread work; a page that downloads instantly and then blocks on script looks perfect here (Reading a Network Waterfall).

Performance panel, main thread

when "It freezes", "typing lags", "the click does nothing for a moment". Long tasks, handler duration and rendering work all live here (Long Tasks).

cost The recording distorts what it records, and a first-load trace is dominated by one-time work that no returning user pays.

Performance panel, interactions

when A specific interaction is slow and you need the split between waiting, handling and painting (Interaction Responsiveness).

cost Chromium-centric tooling; the equivalent decomposition elsewhere has to be assembled by hand from event timing entries.

Memory panel, heap snapshots

when "It is fine at first and terrible after an hour", or a tab whose memory grows without bound (Memory Leaks).

cost Snapshots are slow, large, and require a disciplined comparison method to mean anything. Guessing from the heap graph alone is unreliable (Leak or Unbounded Cache? The Question That Picks the Fix in Observability).

Rendering and layer overlays

when Jank during scroll or animation, unexplained repaints, or a suspicion about layer count (Layer Explosion).

cost Shows symptoms vividly and causes poorly; it is a way to confirm a hypothesis, not to form one.

A local recording is a hypothesis

Everything about a development environment is unrepresentative in the same direction: faster CPU, warmer cache, shorter distance, smaller data, fewer tags, no other tabs, no extensions in a clean profile and the wrong extensions in a normal one. Each of those biases the result toward "fine".

That does not make local profiling useless — it makes it a hypothesis generator. The discipline is to write down what you think the local recording proves, then check whether the field agrees, before spending a sprint on it.

How a local recording misleads, and what to do about it
TriggerSymptomCauseResponse
Profiling on the development machineEverything is fast; the ticket is not reproducibleDevelopment hardware sits near the top of the device distribution, and CPU-bound work scales with itThrottle the CPU, and keep one genuinely mid-range phone on the desk for the cases throttling cannot model (The Real Cost of JavaScript).
Warm cache and a warm connectionFirst visit is slow in the wild and instant locallyNo DNS, no connection setup, no cold cache, no third-party origins on the critical pathRecord with an empty cache and applied network throttling, and read the first-visit waterfall separately from the repeat visit (Browser HTTP Caching).
Development data volumesFine in staging, unusable for the customer with the large accountRendering, reconciliation and layout costs scale with node count, and seed data does notSeed with a realistic worst case, and treat the largest customer as a supported device class (List Virtualization).
Third-party tags absent locallyMain thread is busy in production for reasons your code does not explainTag managers, consent banners, analytics and chat widgets all execute on your main threadProfile a production build on the production origin, and attribute long tasks by script URL (Third-Party Scripts and the Supply Chain).
One recording, no baselineNobody can agree whether the change helpedRun-to-run variance is large, and there is nothing stored to compare againstSave the trace, repeat the run several times, and confirm in the field before closing the ticket (Regression or Tuesday? Telling a Real Change from Noise in Observability).
Measuring only the happy pathThe reported slowness is on an error or empty state nobody profiledError and empty paths often re-render more, and they are excluded from scripted runsInclude failure and empty states in the scripted flow; they are real user states (Loading, Error, Empty — The States You Did Not Render).

How to build it

Most important first.

  • Start from the complaint, not from the tooling. "Saving a row takes a beat" is a question about interaction responsiveness; "the page is blank for ages on my phone" is a question about loading. They lead to different panels (Interaction Responsiveness, Loading: Why Content Arrives Late).
  • Get a field baseline before touching anything, even a crude one. Without a before, no after is interpretable, and performance work without a baseline reliably turns into refactoring with a performance justification.
  • Reproduce in the lab only after the field has told you what to reproduce: which page, which interaction, which device class, which percentile. That makes the local recording a test of a hypothesis rather than a fishing trip.
  • Profile with CPU throttling and an empty cache in a clean browser profile. The default configuration measures the most favourable machine in your user population (The Real Cost of JavaScript).
  • Change one thing, measure the same way, and keep the recording. A saved trace from before the change is the only artefact that survives the argument about whether it worked (Regression or Tuesday? Telling a Real Change from Noise in Observability).
  • Instrument the interactions that matter as explicit measurements — a mark when the user commits, a measure when the result is on screen — so the signal survives a refactor of the code that produced it (Analytics Events That Answer a Question).

Keyboard, focus, semantics, announcement

A required field on every lesson in this domain, not a section added when there is room.

  • Almost all profiling is done with a mouse, so keyboard-driven paths are systematically unmeasured. Tab, Enter and Escape go through the same handlers as a click and often through more of them — focus management, live-region updates, roving tabindex (Keyboard Operability).
  • A screen reader changes the timing of what you are measuring. Assistive technology consumes the accessibility tree, and a page that rebuilds large parts of that tree on every update does work in the field that never appears in your recording (The Accessibility Tree).
  • A long task is worse with assistive technology, not merely equal: announcements queue behind it and the user has no visual cue that anything is happening, so the failure mode is silence rather than a spinner (Long Tasks).
  • If you can only measure one accessibility-relevant thing, measure whether focus lands somewhere sensible after a navigation or when a dialog opens. It is cheap, it is frequently broken, and no performance metric will ever tell you about it (Focus Management).

What can go wrong

Failure modes
  • Optimising the median while the tail gets worse. Caching, batching and prefetching often trade the slow case for the common case, and the slow case is where the complaints come from (Tail Latency: Why p50 Being Fine Does Not Help in Observability).
  • A field metric that improves because the slow sessions stopped being recorded. Users who abandon before the beacon fires are silently removed from your distribution, which makes an abandonment problem look like a performance win.
  • Sampling that is too aggressive to see the thing you are hunting. A rare, severe interaction problem disappears entirely at a low sample rate, and raising the rate raises the cost the user pays.
  • A lab run that is not deterministic — a third-party script, an experiment assignment, a cold connection — so the comparison between two runs measures the variance rather than the change.
  • Measuring in a way that is itself the bottleneck: an observer that reads layout on every entry, or a logging call inside the hot handler you are timing (Layout Thrashing).
What can arrive out of order
  • Field beacons race the page unload. A metric finalised at page hide can be lost when the tab is discarded, which biases your data toward sessions that ended politely.
  • A user can interact before your instrumentation has registered its observers, so the first and often slowest interaction of a session is the one most likely to be missing.
  • Two recordings of "the same" flow can interleave a third-party script differently, so a difference between them may be scheduling rather than your change.
Security
  • Field telemetry is user data. URLs carry identifiers, referrers carry context, and interaction targets carry the labels you gave your own UI — all of which leave the origin the moment you send a beacon (Session Replay and the Privacy It Costs).
  • Session replay is the extreme case: it is a recording of a real person using your product, and it needs masking, consent and retention rules decided before it is turned on rather than after an incident.
  • A monitoring SDK is a third-party script with full page authority. It can read the DOM, wrap fetch, and observe every input; adding one extends your trust boundary (Third-Party Scripts and the Supply Chain).
  • Resource timing for cross-origin responses is deliberately coarse unless the other origin opts in with Timing-Allow-Origin, because precise cross-origin timing is a side channel. That is why parts of your waterfall are opaque.
Misreads
  • "Devtools said it took 40 ms, so it is fine." Devtools said it took 40 ms on that machine, on that network, with that data, once. None of those four are properties of your users.
  • "Field data has no stack traces, so it is useless." It is the only thing that can tell you which of the twenty possible problems is the one people are actually hitting. It narrows; the lab explains.
  • "The synthetic score went up, so users are faster." A synthetic score is a lab result with an opinion attached. It is a useful checklist and a poor proxy for the distribution of real sessions.
  • "We should measure everything." Every measurement costs the user something and costs you a retention decision. Choose the few signals that change a decision.
  • "The average improved." Averages hide the shape. A change that helps the fast half and hurts the slow tail improves the average and increases complaints (The Average Was Fine and Users Were Not in Observability).

Measuring it, and what changes in the field

How you would see this
Slow device, slow network, large data, old tab
  • On a slow device the ranking of costs changes, not just their magnitudes: script parse, compile and execute grow faster than network time, so a profile that was network-bound on your laptop can be CPU-bound on a mid-range phone (The Real Cost of JavaScript).
  • On a slow or high-latency network, dependency structure dominates and the number of sequential hops matters more than total bytes (Reading a Network Waterfall).
  • With production data volumes, rendering and reconciliation costs that were invisible at forty rows become the whole profile at four thousand (List Virtualization).
  • In a tab that has been open for hours, retention and accumulated listeners change the answer completely, and a fresh-load profile cannot see any of it (Long-Lived Clients and Version Skew).
What this costs
  • Field measurement costs the user bytes, CPU and battery, and costs you a privacy obligation. A thorough RUM setup is not free, and the honest version of "measure everything" is "sample deliberately and know what you dropped".
  • Waiting for a baseline before changing anything is slower than just fixing the obvious thing, and sometimes the obvious thing really is the problem. The discipline pays back on the second and third attempt, when it was not.
  • A local recording with throttling is a rough proxy. It is much better than an unthrottled one and much worse than a real device from your users' population; keeping one cheap phone on the desk beats any amount of emulation.

Where this applies

Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.

  • GENERALThe lab-versus-field distinction, and the rule that a single local recording is a hypothesis, hold for every browser and every framework, because they follow from the fact that you do not control the device population rather than from any implementation detail.
  • BROWSER-SPECIFICPanel names and capabilities differ: Chromium groups interaction work under an Interactions track and exposes long-animation-frame data, Firefox presents the same main thread as a stack chart with a different marker vocabulary, and Safari records a different subset again in its Timelines tab. The signal exists everywhere; the place to click does not.
  • SPEC-EVOLVINGThe field metrics named across this module, and the boundaries used to rate them, are published by the web vitals working group. Metrics have been retired and replaced, and definitions have been revised without a rename. Treat the concern as stable and the metric as a moving reference; read the current definition rather than a number remembered from a conference talk.
  • DEVICE-SPECIFICWhich signal matters most depends on the device class: on a fast laptop most pages are network-bound and the waterfall is the interesting artefact, while on a mid-range phone the same page is CPU-bound and the main-thread track is. The same recording ranks the costs differently.

Where the depth lives

This domain teaches the browser-side mechanism and hands the rest off.

Domains that do not exist yet
  • Testing & Reliability Engineering — how a performance budget becomes a gate in continuous integration without turning into a flaky check that everyone learns to re-run.