Home / Articles

Reading React Performance Tracks in Chrome DevTools to Find Slow Renders

Learn how the Scheduler, Components and Server tracks added in React 19.2 show where a slow interaction spends its time, and a record-fix-record workflow to act on it.

Publisher: REACTAPP.TOP5 min read1081 words

This article is published in English.

Sources: Stop Guessing Why React Is Slow. Use the Performance Timeline.

When a React interaction feels sluggish, the reflex is to add useMemo, useCallback and React.memo and hope. Often the page stays slow, because nobody measured what was eating the time. React 19.2 added Performance Tracks, which write React activity into the browser's Performance timeline; here is what each track shows and how to turn a recording into a targeted fix.

Why the plain timeline was hard to read

Record a slow interaction in the Chrome DevTools Performance panel on an app without these tracks, and the main thread usually looks like this:

JavaScript
████████████████████
██████████
███
████████████

JavaScript is clearly busy, but which component was responsible, which update started the render, was the page waiting on the network, did an Effect dominate, and did React render just as the user was waiting for immediate feedback? The generic view leaves you to piece that together from function names. The React tracks supply the missing context.

What the React tracks add

React contributes dedicated tracks for three areas:

Scheduler
Components
Server

They sit next to the browser's usual lanes, so a single trace can be read roughly like this:

USER CLICK
    │
    ▼
┌─────────────────────────────┐
│ React Scheduler             │
│ Blocking → Render → Commit  │
├─────────────────────────────┤
│ React Components            │
│ Dashboard                   │
│ ├─ Header                   │
│ ├─ DataTable  ← expensive   │
│ └─ Chart                    │
├─────────────────────────────┤
│ Network / JavaScript        │
└─────────────────────────────┘

The value lies in correlation: React's activity and the browser's activity appear on one shared time axis.

Scheduler: why did this work run now?

React does not handle every update the same way. The Scheduler track separates work by priority, with lanes such as Blocking, Transition, Suspense and Idle, and it labels the phases of each update: Update, Render, Commit, and any remaining Effects.

This turns a question like "why does typing into this field lag?" answerable. The trace might show that a keystroke kicked off a heavy synchronous render in the blocking lane. Or it might show that the expensive work was already correctly wrapped in a transition, which points you elsewhere. Either way, you are working from evidence instead of intuition. If transitions and priorities are new to you, the model behind these lanes is covered in Understanding React Lanes.

Components: who did the work?

For day-to-day debugging this is usually the most useful track. It plots how long each component spent rendering and running Effects in a flamegraph, and in development it can also highlight changed props and cascading updates. A result like this is typical:

ProductPage        310ms
├── Header           12ms
├── Filters          18ms
├── DataTable       245ms  ←
└── Footer            8ms

Instead of the vague complaint that React is slow, you now have a precise lead: DataTable took the bulk of this render. From there, check whether it renders too many rows, recomputes data each pass, or receives new props from its parent every time.

Server: is the page even waiting on React?

In apps that use React Server Components, development builds additionally surface server requests and server component work on the timeline. This exposes a common misdiagnosis:

Slow page
   │
   ▼
Assume React render
   │
   X
   │
   ▼
Actually waiting on:
getProducts()
      │
      ▼
Database / API

If the trace shows the page idling while getProducts() waits on a database or API, the fix belongs in data fetching, such as caching, parallel requests or a leaner query, and wrapping components in React.memo will not help. This is exactly why you want the whole execution timeline in one view.

A repeatable debugging workflow

When an interaction feels slow, follow the same sequence every time:

1. Reproduce the problem
          ↓
2. Open Performance
          ↓
3. Record the interaction
          ↓
4. Find the slow React work
          ↓
5. Inspect the component / phase
          ↓
6. Check network and browser work
          ↓
7. Fix the actual bottleneck
          ↓
8. Record again

Do not skip the final step. Record again under the same conditions and compare. An optimization that was never re-measured is only a hypothesis, and occasionally the second trace shows the change moved the cost somewhere else rather than removing it.

Know the limits of a local trace

Performance Tracks are a development and profiling aid. React documents that the instrumentation adds overhead and is turned off in production builds by default, and the server tracks are limited to development builds. Development builds also run slower overall, so trust proportions more than absolute numbers.

That means a clean trace on your machine does not prove that real users get a fast experience. Production performance still calls for field data and real-user monitoring. The tracks answer a narrower but very practical question: what is the application doing, step by step, while you reproduce this specific problem?

Key takeaways

The most costly performance habit is not choosing the wrong hook; it is optimizing before understanding the problem. A sturdier approach looks like this:

Slow interaction
       │
       ▼
    Measure
       │
       ▼
 Find the bottleneck
       │
 ┌─────┼──────┐
 ▼     ▼      ▼
React Network Browser
 │      │      │
 ▼      ▼      ▼
Fix    Fix    Fix
  • Measure first: the tracks make recording and locating the bottleneck the easy part.
  • Use the Scheduler track to understand priority and phase, and the Components track to find the expensive subtree.
  • Check the Server track and network lanes before assuming the render is at fault.
  • Re-record after every change to confirm the fix.
  • Treat local traces as diagnosis, and rely on field data for what users experience.

Framed this way, the conversation shifts from whether to add useMemo to what actually consumed those 200 milliseconds, and that question is the one that produces an actual fix.