React.js, part 19: the React Compiler in production — the eslint plugin, bailouts, and measuring the win
Part 19from the React.js series · 20 parts in all
Turning on an optimizing compiler is the kind of change that either does nothing or does something surprising. Part 19 is the production checklist for the React Compiler: enabling it without breaking behavior, finding the code it silently skipped, and proving the result.
Enable it, then read the linter
npm i -D babel-plugin-react-compiler eslint-plugin-react-compiler
// eslint.config.js
import reactCompiler from 'eslint-plugin-react-compiler';
export default [
{ plugins: { 'react-compiler': reactCompiler },
rules: { 'react-compiler/react-compiler': 'error' } },
];
The compiler only optimizes code it can prove pure, so the eslint rule is the real instrument: every warning is a function the compiler is going to bail out of, left running exactly as it does today. Fixing those warnings is what turns the compiler from "on" into "effective".
The bailouts that show up most
- Mutating props or state.
props.items.push(x)makes identity comparisons meaningless; the compiler cannot cache around it. - Reading refs during render.
ref.currentin the render body is unprovable by design. - Conditional hooks. The compiler needs a stable hook graph; a hook inside
an
ifis both a React error and a compiler stop. - Time or randomness in render.
Date.now()orMath.random()as a value the compiler might cache is a correctness hazard it correctly refuses.
Measure, do not assume
The compiler reduces wasted renders; it does not reduce the work of the renders that happen. So measure both:
// 1. Render counts, per component, in a real interaction.
<Profiler id="Cart" onRender={(id, phase, actualDuration) =>
series.push({ id, phase, ms: actualDuration })}>
<Cart />
</Profiler>
// 2. Whether memoized props are now stable (the compiler's actual win):
// a child wrapped in React.memo that used to re-render on every parent
// render should now re-render only when its inputs change.
Roll it out behind a flag if you can, compare interaction timings on a slow device rather
than a fast dev machine, and keep the useMemo calls that express a real cache
boundary even after the compiler handles the incidental ones. Next: where React is going —
the Activity component and rendering trees that are not on screen.