Begin with the complaint
"The page feels slow" needs a boundary. Is the first view late, is interaction blocked, is scrolling uneven, or is the network unreliable? Each symptom points to a different measurement.
I capture a baseline before changing code: navigation timing, long tasks, layout shifts, request waterfalls, and the cost of the most important interaction.
Change one pressure point
Removing unnecessary work is usually better than making necessary work clever. That might mean reducing a payload, moving a computation off the render path, or avoiding a subscription that updates an entire tree.
The final test is not a better profiler screenshot. It is a faster path for the user that can be reproduced on the same device and connection.
A repeatable investigation
I start with the slow interaction in a production-like build and a throttled device. First I check whether the delay is network-bound. If the response is already available, I inspect scripting and rendering. If the page moves while loading, I inspect layout shifts and image dimensions.
flowchart TD
A[User complaint] --> B[Reproduce on a known device]
B --> C{Network delay?}
C -->|Yes| D[Reduce payload or request waterfall]
C -->|No| E{Long main-thread task?}
E -->|Yes| F[Move, batch, or remove work]
E -->|No| G[Inspect layout and paint]
D --> H[Measure again]
F --> H
G --> H
Use budgets, not superstition
A page budget might include an initial JavaScript limit, a maximum image weight, a target interaction delay, and a threshold for layout shift. The exact values should reflect the product and device mix. A budget turns performance from a cleanup project into a constraint reviewed with each feature.
const performanceBudget = {
firstViewJavaScriptKb: 220,
interactionDelayMs: 200,
cumulativeLayoutShift: 0.1,
};
The important habit is preserving the baseline. Without a before-and-after measurement, an optimization can simply move work to a different part of the experience.
