The cheapest instrument you own is a counter in a component body and performance.now() around a render. flushSync makes the timing honest: it forces React 7,897 to finish the render and the commit before it returns, so the number covers the whole pass, not the call that scheduled it.
<div id="root"></div><p id="out" style="font:14px system-ui"></p>
<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",
"react-dom": "https://esm.sh/react-dom@19.3.0",
"react-dom/client": "https://esm.sh/react-dom@19.3.0/client" } }</script>
<script type="text/babel" data-type="module">
import React from 'react';
import { createRoot } from 'react-dom/client'; import { flushSync } from 'react-dom';
let renders = 0; // the cheapest instrument there is
const Row = ({ n }) => { renders++;
let s = 0; for (let k = 0; k < 2e4; k++) s += Math.sqrt(k + n);
return <li>row {n}: {s.toFixed(0)}</li>; };
const List = ({ q }) => <ul style={{ height: 22, overflow: 'hidden', margin: 0 }}>
{Array.from({ length: 500 }, (_, i) => <Row key={i} n={i + q} />)}</ul>;
const root = createRoot(document.getElementById('root'));
const time = (el) => { const t = performance.now(); flushSync(() => root.render(el));
return (performance.now() - t).toFixed(1); };
const mount = time(<List q={0} />), update = time(<List q={1} />);
out.innerHTML = `mount <b>${mount} ms</b>, update <b>${update} ms</b>, ` +
`${renders} Row renders`;
</script>
Three facts fall out of one screenshot. The update is cheaper than the mount because React reuses the existing DOM nodes; all 500 rows re-rendered on both passes; and an update of this size has already blown past the 16.7 ms a 60 Hz frame allows. A counter like renders answers the question memoization turns on — is this component re-rendering more often than its inputs change? — in any build.
For the load story the instrument is the browser: Lighthouse 30,824 for Largest Contentful Paint, the Performance panel for Interaction to Next Paint, React DevTools 7,897 ' Profiler tab for ranking components by time in a commit. Run all three against a production build — a development build exaggerates render cost and hides bundle wins.