React was one of the first major front-end frameworks to not only name, but tout its rendering performance. React’s virtual dom is famous for efficiently rendering components—but what happens when those components suddenly don’t feel fast anymore? Where do you look? How do you fix it?
In this walk-through, we’ll show how to find and fix slow React component code using two complementary tools: the React DevTools Profiler (purpose-built for React component renders) and Chrome DevTools (which shows the full browser timeline, including React 19.2’s new Performance Tracks).
While this article is focused on React, you’ll learn concepts that can be applied to React, Angular, Vue or just about anything written in JavaScript. To follow along, you’ll need:
- Use React 18 or newer in development mode (React 19.2+ unlocks Chrome DevTools’ new Performance Tracks)
- Install React Developer Tools for Chrome or Firefox
- Use Chromium-based devtools (Chrome, Edge, or Brave)
- Export JS with source-maps (optional, but highly recommended)
Using the React DevTools Profiler#
React DevTools knows about your components; Chrome DevTools knows about the browser. Use the React Profiler to find which component is slow. Use the Chrome profiler to find why. They’re complementary, and you’ll move between them often.
The React Profiler only works when your app is running in development mode — production builds strip out the profiling instrumentation by default.
Record a profiling session#
With React DevTools installed, open Chrome DevTools and switch to the React tab, then the Profiler sub-tab. Press the record button, perform the interaction you want to investigate (a route change, a list filter, a button click), and press stop.
You’ll see one bar per commit — every time React applies updates to the DOM. The commit picker along the top of the panel lets you scrub through them.
Read the flame chart and ranked chart#
The default flame chart shows the component tree for the selected commit, with each component sized by how long it took to render. Yellow components are slow; grey ones rendered, but quickly.
Switch to the ranked chart to sort every rendered component by render duration descending. It’s the fastest way to spot the worst offender in a busy commit.
Find out why a component rendered#
The single most useful debugging tweak: open the React DevTools settings (the gear icon), enable “Record why each component rendered while profiling”, then record again. Click any component in the flame chart and React tells you exactly what changed: props, state, hooks, or a parent re-render. React also flags which one of those triggered the work.
Most of the time, that answer is enough to know what to fix. For everything else, switch to Chrome DevTools.
Setting up your audit#
For deeper visibility into JavaScript execution, painting, and React’s internal scheduling, switch to Chrome DevTools.
Firstly, open Chrome's devtools. You’re going to need some room to breathe, so undock devtools and maximise it to be as large as your screen allows.
When doing performance audits it’s essential that you’re working towards realistic real world device targets. After all, not everyone has a powerful developer computer or current model phone.
Thankfully, Chrome devtools can synthetically throttle JavaScript execution. Throttling JavaScript execution in this manner makes performance issues far more apparent, so it’s a good idea to always debug performance using slowed performance.
Remember: Any improvements that you can make for slow hardware will also mean that fast devices get an even better experience. Everyone wins!
Recording and viewing a performance trace#
In development mode, as React renders, it creates ‘mark and measure’ events for each component. Those marks and measures can be viewed within Devtools.
To record a performance profiler trace, navigate to the page that you’d like to test (on localhost) and press the ‘Start profiling and reload page’ button.
This will record a performance trace for the current page. Chrome automatically stops recording the trace after the page has settled, though you can end it earlier by pressing the stop button.
Once you’ve got a trace, your window will look something like this:

Two items worth highlighting, that may not be immediately obvious to someone who is new to the Performance tab.
This red bar shows significant long tasks — runs of JavaScript over 50ms that block the main thread and degrade Interaction to Next Paint (INP). That’s where you want to look.
The colours used in the graph at the top of the performance window correspond to different types of activity. Each category has various causes, fixes, and analysis required.
In this article, we’re focusing on “Scripting” (JavaScript runtime performance).
React 19.2 added Performance Tracks to Chrome DevTools. Look for the Scheduler track in the Performance panel — it shows React’s work split across four subtracks: Blocking (urgent updates like clicks), Transition (updates wrapped in startTransition), Suspense, and Idle. The Scheduler track tells you what priority React was working at, so you can see whether a slow interaction was blocked by a high-priority render or held up by something else entirely.
Now we’re going to want to investigate that red CPU-burn area. We can see that the page rendered elements on the page during that period of the trace.
Zooming immediately displays user-timing information and a component labelled Pulse (that takes 500ms to render).
Beneath the Pulse component, there appears to be child components rendering, although the size of those items indicates that they aren’t costing a lot of execution time.
Discovering slow functions#

- Click the component you’d like to investigate, in this case,
Pulse. This scopes the lower portion of the window to focus only on thePulsecomponent. - Choose the “Bottom-Up” tab.
- Sort by total time descending. In some cases, you may want to sort by Self time, or group by something other than the URL. Experiment what works best for what you’re investigating.
- Expand functions until you’ve located a point in the code you’d like to investigate. In this case the
mapfunction appears suspect. It amounts to 90ms of execution time. - This is why you need sourcemaps: Clicking
MetricStore.js:59in the line number gutter will take you to that spot in the code. Let’s go!

Using this approach, I’ve managed to shave seconds from areas of code that I thought “well, it’s complicated, so I guess it takes a while”. Now I know exactly how and where to look for janky JavaScript performance.
Once you’ve identified a slow function, the React-specific fixes fall into a few buckets:
- Memoisation —
React.memo,useMemo,useCallbackto skip work you don’t need to redo. With the React Compiler, most of this is applied automatically at build time, so reach for manual memoisation only when you’ve measured a problem the compiler doesn’t solve. - Deprioritising work — wrap non-urgent updates in
useTransitionor read deferred values withuseDeferredValueso urgent input doesn’t wait on heavy renders. - Splitting the work itself — break long tasks below 50ms (a
setTimeout, ascheduler.yield()call, or moving the work to a web worker). This is also the headline fix for poor INP.
Visualising the impact of third party scripts#
Easily investigate which scripts or third parties are costing your customers time using Calibre’s Long Task Timeline explorer
Exporting metrics and instrumenting your own actions#
Instrument component renders with the Profiler component#
React ships a <Profiler> component you can wrap around any subtree to capture render timing programmatically. Unlike React DevTools, it works in production builds, which makes it a good fit for sending render timings to your RUM provider.
import { Profiler } from 'react'
function onRender(id, phase, actualDuration) {
// send to analytics, RUM, or your own collection endpoint
}
<Profiler id="Pulse" onRender={onRender}>
<Pulse />
</Profiler>The callback fires on every commit with the component id, the phase (mount or update), and the actual time React spent rendering. Drop a <Profiler> around any subtree you want to keep an eye on in production — checkout flows, dashboards, anything that complains in real-user monitoring.
Instrument your own user actions with the User Timing API#
For events that happen outside of a React render (webfonts loading, an ad SDK initialising, the time between landing on a checkout page and completing it), use the User Timing API:
- Webfonts loaded
- Advertisement initialised
- Add item to cart
- Registration page: Time to register
- Cart page: Time to checkout
All major browsers support performance.mark and performance.measure, so it makes sense to add instrumentation to critical user-actions and flows to gain deeper insight into the experiences people are having on your pages.
Timings can be delivered to Google Analytics or to a custom collection endpoint using navigator.sendBeacon().
Profile with React DevTools to find which component is slow, switch to Chrome DevTools to find out why, and reach for the <Profiler> API to keep tabs on those components in production. Being a successful performance-minded developer requires great focus and knowledge. The good news is you’re already half way there! Best of luck!
Further reading#
- Interaction to Next Paint: What is it and How to Improve It — the metric most React performance work now targets.
- Small Bundles, Fast Pages: What To Do With Too Much JavaScript — fewer bytes is the cheapest INP fix you’ll find.
- Next.js Performance: Making a Fast Framework Even Faster — framework-specific patterns for the most common React framework.