Frontend Performance
Aug 20, 20268 min

Measuring page metrics and interaction lag with the Chrome Performance panel

A practical walkthrough of the Chrome Performance panel — recording traces, reading the flame chart and using Core Web Vitals like INP to track down real interaction lag instead of guessing at it.

Most performance conversations stall at the metric. A Lighthouse score drops, a Core Web Vital turns red, and the instinct is to guess — maybe it's the bundle size, maybe it's a slow API, maybe it's just "React being React." The Performance panel in Chrome DevTools exists to end the guessing. It shows exactly what the main thread was doing, millisecond by millisecond, during the interaction that felt slow.

Start from the metric, not the code

Before opening the Performance panel, it's worth being specific about what "slow" actually means. Largest Contentful Paint measures how long the biggest visible element takes to render. Interaction to Next Paint measures the delay between a user's click, tap or keypress and the next frame the browser paints in response. Cumulative Layout Shift measures unexpected movement of content. These are different problems with different causes, and profiling the wrong one wastes a session. A slow LCP is usually a loading problem — render-blocking resources, an unoptimized hero image, a font that blocks text. A slow INP is almost always a main-thread problem — something is running long enough during or after the interaction that the browser can't paint the response in time.

Recording a trace worth reading

Open DevTools, switch to the Performance panel, and use the record button rather than "Record and reload" unless the problem is on initial load. Perform the exact interaction that feels slow — a click, a filter, a modal opening — then stop the recording. A trace with unrelated activity before and after the interaction just adds noise to the flame chart. It also helps to throttle CPU (4x or 6x slowdown) before recording, since most development machines are faster than the mid-range devices real users are on; an interaction that feels instant on a laptop can be genuinely laggy on the hardware most of your traffic runs on.

Reading the flame chart

The Main track in the flame chart shows every function call on the main thread as a stacked block, with width representing duration. Wide blocks are where time actually went. Chrome highlights blocks wider than 50ms in red — these are "long tasks," and they're the direct cause of interaction lag, because the browser can't respond to input or paint a frame while the main thread is occupied running one.

  • Click a wide block to see its call stack in the Summary panel below — that's where the actual function responsible for the delay lives.
  • The Bottom-Up view aggregates time by function across the whole trace, which is the fastest way to find what's actually expensive versus what merely called something expensive.
  • Yellow blocks are scripting, purple is rendering and layout, green is painting — a wide purple block usually means layout thrashing, not slow JavaScript.
  • The Interactions track at the top of the trace marks exactly when a click or keypress happened, and how long until the next paint — this is INP made visible.

Where interaction lag actually comes from

In practice, the same handful of causes show up over and over. A large state update triggers a re-render of far more of the tree than the interaction actually changed. An event handler does synchronous work — a big array sort, a JSON parse, a DOM read immediately followed by a DOM write — that blocks the thread for the length of the frame budget and beyond. A third-party script attaches a listener to a common event and does something expensive on every keystroke or scroll. None of these show up by reading source code from the top. They show up as wide blocks in a flame chart, with a call stack that says exactly which function to look at.

Fixing what the trace shows you

  • Break up long synchronous work with requestIdleCallback, chunking, or moving it off the main thread with a Web Worker where the work doesn't need DOM access.
  • Batch state updates so a single interaction causes one re-render pass instead of several — most of the "long task" time in React apps is repeated reconciliation, not the interaction handler itself.
  • Read layout properties before writing them, not interleaved, to avoid forced synchronous layout — the purple blocks in the flame chart are usually this.
  • Debounce or throttle handlers bound to high-frequency events like scroll, resize and input, rather than letting every event run full logic.
  • Re-measure after each fix. A trace before and after a change is the only reliable way to confirm the long task actually shrank, rather than just feeling faster.

The Performance panel doesn't replace judgment about what to optimize — it replaces guessing about where the time went. Once a flame chart shows a 400ms task blocking the paint after a click, the fix usually becomes obvious. The hard part was never fixing it. It was finding it.