React Performance Tracks

Since React 19.2 7,897 a development build writes its own timeline into the Chrome DevTools 3,948 performance panel. Record a profile and two track groups appear beside the browser's own: Scheduler ⚛, with one lane per priority (Blocking, Transition, Suspense, Idle) showing what React was working on and waiting for, and Components ⚛, showing the tree it rendered and how long each part took.

The entries are written with console.timeStamp(name, start, end, track, group), the API a page uses to add custom tracks to a Chrome 1 recording — which makes them interceptable, so the demo replaces console.timeStamp before React loads and prints what React would have drawn.

Intercepting what React writes to its performance tracksHTML
<div id="root"></div><pre id="out" style="font:13px Consolas"></pre>
<script>
  window.TS = []; const real = console.timeStamp.bind(console);
  console.timeStamp = (n, s, e, track, group) => {
    TS.push([group || track, track, n, e - s]); real(n, s, e, track, group); };
</script>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@8.0.5/babel.min.js"></script>
<script type="importmap">{ "imports": { "react": "https://esm.sh/react@19.3.0?dev",
  "react-dom": "https://esm.sh/react-dom@19.3.0?dev",
  "react-dom/client": "https://esm.sh/react-dom@19.3.0/client?dev" } }</script>
<script type="text/babel" data-type="module">
  import React from 'react';
  import { createRoot } from 'react-dom/client'; import { flushSync } from 'react-dom';
  const Row = ({ n }) => { let s = 0;
    for (let k = 0; k < 6e5; k++) s += Math.sqrt(k + n);
    return <li>row {n}: {s.toFixed(0)}</li>; };
  const Table = () => <ul style={{ height: 20, overflow: 'hidden', margin: 0 }}>
    {Array.from({ length: 12 }, (_, i) => <Row key={i} n={i} />)}</ul>;
  flushSync(() => createRoot(document.getElementById('root')).render(<Table />));
  setTimeout(() => { out.textContent = TS
    .filter((r) => r[0].startsWith('Scheduler') && r[3] > 0.05)
    .map((r) => `${r[0]} / ${r[1]} | ${r[2]} | ${r[3].toFixed(1)} ms`).join('\n') +
    `\n${TS.filter((r) => r[1].startsWith('Components')).length} Components entries`; }, 0);
</script>
Browser output of Listing 2.104
Browser output of 104

One mount produced Render, Commit and Remaining Effects entries on the Blocking lane — a fourth, Waiting for Paint, shows up whenever the browser cannot paint straight away — plus one Components entry per component. Render dominates, as it should for a tree doing arithmetic; had Commit dominated, the cost would be DOM mutations. That attribution is the value of the tracks.