React DevTools Profiler: Reading Flame Graphs Correctly
AI generated
{ }
React · Performance · DevTools
React DevTools Profiler
reading flame graphs correctly instead of just staring at colored bars

The Profiler tab in React DevTools shows a flame graph for every commit, but the coloring alone does not say much. Combining commit duration, the ranked chart, and the why did this render panel correctly is what turns guessing into finding real bottlenecks.

14 min read React DevTools Profiler Flame Graph

1. What the Profiler tab actually records

The Profiler tab in React DevTools records every commit, meaning every moment React actually applies a computed render tree to the DOM. A commit is not the same as a single state update call: React can batch several state updates into a single commit, so a commit in the profiler often represents the result of multiple setState calls within one event cycle. Anyone expecting exactly one commit per click will often be surprised by reality.

For every commit, the profiler stores a timeline at the top with one bar per commit whose height roughly reflects render duration, plus the actual flame graph below it for the currently selected commit. Recording has to be started manually before the interaction under investigation runs, and in older React versions production builds without a profiling flag do not provide meaningful timing data, which is why a profiling capable build is needed for meaningful measurements.

2. Reading the flame graph itself

In the flame graph, each row represents a level in the component tree, each bar represents a single component, and the width of the bar shows how much time was spent rendering that component and its children, relative to the other components in the same commit. A component colored gray instead of a color from the scale was not re-rendered at all in this commit, React simply skipped it, which is already a good first hint about where in the tree the actual work happened.

The coloring itself follows a scale from yellow through orange to a rich blue or green: yellowish and orange tones mark components with above average render time within the commit, while green or blue tones mark fast components. Important detail: the color is calculated relative to the currently viewed commit, not absolute. A component can appear yellow in a fast commit even though its absolute render time is lower than the same component appearing green in an overall slower commit.

3. Understanding commit duration and the timeline correctly

At the top of the Profiler tab, the commit overview shows one bar per recorded commit, where the height represents the relative render duration compared to the other recorded commits. A particularly tall bar is worth checking first, but the absolute number in milliseconds shown on hover is the actually relevant piece of information, because a two millisecond commit remains mostly irrelevant even as the tallest bar in the overview on fast devices.

A common mistake is looking only at the total duration of a commit while ignoring the distribution within it. Two commits with an identical total duration can have completely different causes: one time a single expensive component takes up most of the time, another time dozens of small components all re-render unnecessarily and add up to the same total. Only looking into the flame graph itself distinguishes these two cases, which each require a completely different fix.

4. Why did this component render

Clicking a single component in the flame graph shows a 'Why did this render' section in the right hand sidebar of DevTools that names the concrete cause of the render: changed props, changed state, a context update, or a render of the parent without actual necessity. This panel is the most direct way to distinguish between a justified render, because relevant data actually changed, and an unnecessary render, because the parent simply re-renders all children every time.

For changed props, the panel even lists which specific prop names changed, which is especially helpful for components with many props to narrow down the exact trigger instead of guessing. In current React versions this feature is enabled by default, in older versions the option 'Record why each component rendered while profiling' had to be manually enabled in the DevTools settings dialog before recording, since it introduces a small additional overhead.

5. The ranked chart as an alternative to the flame graph

Besides the flame graph, the profiler offers a second view, the ranked chart, which lists all components of a commit sorted by their own render duration, from slowest to fastest, independent of their position in the component tree. While the flame graph shows well how time is distributed hierarchically through the tree, the ranked chart is the faster view for directly identifying the single most expensive component in a commit, without clicking through multiple nested levels.

An important difference in interpretation: the ranked chart shows the 'self time' of each component, meaning the time spent exclusively on the component itself, without the time its children took. A parent component can therefore look huge in the flame graph because it wraps many expensive children, while it sits near the bottom of the ranked chart because its own render logic is trivial. Using both views together gives a more complete picture than either one alone.

6. Profiler settings for more meaningful measurements

The gear icon in the Profiler tab hides several options that noticeably improve the quality of a measurement: 'Highlight updates when components render' briefly colors components during normal use, outside of an active recording, whenever they re-render, which is useful for a quick visual check before even starting a formal profiler session. 'Hide commits below X ms' filters out irrelevant short commits from the commit overview, so the timeline focuses on the actually relevant outliers.

For production oriented measurements it is additionally crucial to test with a profiling capable production build instead of the development build, since development builds are noticeably slower due to extra warnings, PropTypes checks, and consistency checks, which skews the absolute numbers in the profiler even if it does not make them completely useless. React offers a dedicated 'profiling' build target for this, which keeps the production optimizations but additionally includes the timing markers the profiler needs.

7. A concrete debugging workflow

A typical workflow starts by starting the recording, performing the interaction under investigation, for example typing into a search field, and then stopping the recording. Next, identify the most noticeable bars in the commit overview, switch between flame graph and ranked chart for each one to see both the hierarchical distribution and the single most expensive component, and then click specifically on the suspicious components to confirm the exact cause via why did this render.

If it turns out, for example, that a list component fully re-renders on every keystroke even though only the search field itself changes, why did this render usually points to a changed prop that should actually be stable, such as an inline defined callback function or an inline created options object. The fix is then typically to stabilize the affected function with useCallback or wrap the list itself with memo, so an unchanged props object actually prevents the re-render.


// Before: SearchResults re-renders on every keystroke,
// because onSelect is re-created on every SearchPage render
function SearchPage() {
  const [query, setQuery] = useState('');

  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <SearchResults
        query={query}
        onSelect={(item) => console.log(item)} // new reference per render
      />
    </>
  );
}

// After: onSelect stabilized, SearchResults memoized
const SearchResults = memo(function SearchResults({ query, onSelect }) {
  // ...
});

function SearchPage() {
  const [query, setQuery] = useState('');
  const handleSelect = useCallback((item) => console.log(item), []);

  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <SearchResults query={query} onSelect={handleSelect} />
    </>
  );
}

8. Common interpretation pitfalls

A common pitfall is misreading the color scale as an absolute statement about performance: since it is calculated relative to each commit, a commit that is entirely colored orange is not necessarily a problem if the absolute total duration is still in the low single digit millisecond range. Anyone who only watches color instead of the actual millisecond values quickly ends up chasing phantom problems that would never be noticeable in the real user experience.

A second pitfall involves testing in development mode: some effects, especially the double renders caused by React Strict Mode during development, show up in the profiler as extra commits that do not exist in production at all. Anyone recording a profiler session under Strict Mode should consciously discount these duplicate commits and rely on a profiling build without the Strict Mode doubling for the final assessment, to avoid drawing wrong conclusions about actual production performance.

9. The most important profiler tools at a glance

React DevTools offer four complementary tools with the flame graph, the ranked chart, the commit overview, and why did this render, each answering a different question: how does time distribute through the tree, which single component is the most expensive, which commit stands out overall, and why exactly did a specific component re-render in the first place. Combining all four instead of relying on just one leads to finding performance problems systematically rather than by chance.

The table below summarizes which tool fits which question best and what to watch out for when interpreting it, so the color scale and bar heights do not lead to wrong conclusions.

Tool Answers Watch out for Typical entry point
Commit overview Which commit stands out overall Relative bar height, check absolute ms Click the tallest bar
Flame graph How time distributes hierarchically through the tree Color is relative to the commit, not absolute Wide bars at the top level
Ranked chart Which single component is the most expensive Shows self time without children's time Top row of the list
Why did this render Why exactly did a re-render happen May need to be enabled beforehand Click a suspicious component
Highlight updates Which components render outside a recording Visual only, no numbers Watch during normal usage

Mironsoft

React architecture, performance, and Magento frontend integration

React frontends that stay fast instead of slowing down with every feature?

We review existing React applications for unnecessary re-renders, bloated bundles, and fragile state management, then build a frontend that stays performant and connects cleanly to Magento or other backends.

Performance Audit

Systematically measuring and fixing re-renders, bundle size, and load times.

State Architecture

Cleanly separating context, client state, and server state instead of mixing everything.

Magento Integration

Building robust, type-safe GraphQL or REST integration with Magento.

10. Summary

React Profiler Flame Graphs: The Essentials at a Glance

Color is relative

Flame graph coloring always refers to the currently selected commit, not an absolute threshold.

Use the ranked chart

For the fastest identification of the most expensive single component, the ranked chart is often more direct than the flame graph.

Why did this render

Shows the concrete cause of a render: changed props, state, context, or a parent render.

Use a profiling build

Development builds skew absolute numbers, a dedicated profiling build gives production accurate values.

11. FAQ: React Profiler Flame Graphs: The Essentials at a Glance

1What exactly does the color of a bar in the flame graph mean?
The color shows the relative render duration of a component compared to all other components in the same commit, from green or blue for fast to yellow or orange for slow. It is not an absolute statement, an orange bar in an overall very fast commit can still be in the low millisecond range.
2Why is a component colored gray in the flame graph?
Gray components were not re-rendered in this commit, React skipped them because neither their props nor their state changed, or because they are protected by memo. They still appear in the tree so the structure stays understandable.
3What is the difference between the flame graph and the ranked chart?
The flame graph shows the hierarchical distribution of render time along the component tree, the ranked chart lists all components of a commit flatly sorted by their own render time, independent of their position in the tree. The ranked chart is usually faster for finding the single most expensive component.
4How do I enable why did this render in DevTools?
In current React versions the panel is usually available by default as soon as you click a component in the flame graph. In older versions you first need to enable Record why each component rendered while profiling in the gear icon of the Profiler tab before starting a new recording.
5Why do my measurements differ between development and production?
Development builds contain extra warnings, consistency checks, and sometimes doubled renders from Strict Mode, which noticeably increases the absolute times. For realistic measurements, a dedicated profiling production build should be used, which keeps the production optimizations but still includes the profiler timing markers.
6What does self time mean in the ranked chart?
Self time is the time a component spent exclusively on its own render work, without the time its child components took. A parent component with trivial own logic can therefore sit near the bottom of the ranked chart even though it looks large in the flame graph because of its expensive children.
7Do I have to manually start recording before every profiler session?
Yes, the profiler only records while a recording is active, and the interaction under investigation must happen within that time window. After stopping the recording, all recorded commits can be clicked through as often as needed afterwards.
8Why do I see twice as many commits under Strict Mode?
React Strict Mode intentionally runs certain functions, including some renders, twice during development to surface side effects in impure components. These extra commits only exist in the development environment and should not be counted when assessing real production performance.
9Can I apply Hide commits below X ms retroactively to an existing recording?
Yes, the setting filters the display of already recorded commits in the overview, it does not need to be set before recording. It is especially helpful for long sessions with many short, irrelevant commits that would otherwise clutter the timeline.
10Is the profiler worth using on small applications without noticeable performance problems?
Yes, precisely because unnecessary re-renders often go unnoticed while an application is still small, an occasional look into the profiler early in a project pays off. Patterns like inline created callback functions or missing memoization can be spotted this way before they turn into noticeable problems as data volume grows.