Chargement...
Chargement...
Notes from running the React Compiler on the BetterDocs admin (a 4-year-old codebase with ~80 components and useMemo scattered everywhere) and starting clean with it on xCloud v1. What broke, what did not, and where I still reach for manual memoization.
Halfway through re-architecting the BetterDocs admin panel, I caught myself doing something silly — wrapping a calculation in useMemo, then a few weeks later removing the useMemo because the dependency check turned out to cost more than the calculation it was guarding. That kind of guesswork is what the React Compiler is meant to end. It reads your components, figures out what depends on what, and inserts memoization at build time.
I have used it on two pretty different codebases now: BetterDocs admin (4 years old, ~80 components, useMemo grown organically across the surface) and xCloud v1 (greenfield, compiler on from day one). What follows is a mix of how the compiler works and what actually happened when I turned it on.
Manual memoization has two failure modes. Over-memoize and you pay for equality checks that never save work — I have watched the React Profiler in Chrome literally show useMemo calls eating more frame time than the calculations they were guarding. Under-memoize and you ship re-renders nobody notices until a designer says the panel feels sluggish during typing. Both mistakes are invisible without profiling, and almost nobody profiles.
// Before the compiler — manual memoization everywhere
const UserList = memo(({ users, onSelect }) => {
const sortedUsers = useMemo(
() => [...users].sort((a, b) => a.name.localeCompare(b.name)),
[users],
);
const handleClick = useCallback(
(user) => onSelect(user.id),
[onSelect],
);
return sortedUsers.map((u) => (
<UserRow key={u.id} user={u} onClick={handleClick} />
));
});
// After the compiler — identical runtime behavior
function UserList({ users, onSelect }) {
const sortedUsers = [...users].sort((a, b) => a.name.localeCompare(b.name));
return sortedUsers.map((u) => (
<UserRow key={u.id} user={u} onClick={() => onSelect(u.id)} />
));
}Info
The compiler emits memoization instructions that React interprets at runtime. Your output bundle is slightly larger than raw source, but meaningfully smaller than hand-memoized code.
The compiler performs static analysis on your components to build a dependency graph. It identifies pure computations, stable references, and values derived from props or state. Anything that can be cached safely is cached. Anything ambiguous — side effects, non-deterministic calls, mutations — is left alone.
The compiler only works on code that follows the Rules of React. If you mutate props, read refs during render, or break the rules of hooks, the compiler bails out on that component — sometimes silently. The accompanying ESLint plugin (eslint-plugin-react-compiler) flags violations before they become bugs.
The compiler ships as a Babel plugin. For Next.js, Vite, or any build tool that wraps Babel/SWC, enabling it is a configuration change.
// next.config.ts
export default {
experimental: {
reactCompiler: true,
},
};
// Or opt in incrementally per directory
export default {
experimental: {
reactCompiler: {
compilationMode: 'annotation', // only components with "use memo"
},
},
};Astuce
On an older codebase, start with annotation mode. Add 'use memo' directives to your hot paths first (in our case the BetterDocs analytics dashboard, which renders ApexCharts on every state change), measure the win, then flip to full compilation once the rest of the codebase is clean.
On a fresh build with the compiler enabled, around 12 of our ~80 BetterDocs admin components bailed out — the compiler refused to process them. The eslint-plugin-react-compiler linter had been quietly flagging those for weeks; the bailouts were mostly conditional hook calls and a few places where we were mutating data received from React Query (an old pattern from before we knew better). About half a day to fix, and the bailout count dropped to two — both intentional, around third-party library boundaries. xCloud v1 had zero bailouts on day one, which is the upside of starting clean.
With the compiler on, there are still a few cases where I reach for explicit useMemo. ApexCharts instances on the BetterDocs analytics dashboard re-render heavily on prop identity, so the chart factory has to be memoized at the component boundary. IntersectionObservers in infinite-scroll lists need stable callbacks to avoid teardown-and-reattach cycles. And a couple of older third-party form libraries on the Templately admin compare props by reference — explicit memoization stays in those spots.
React DevTools shows a compiled badge on components the compiler has processed. If a component is mysteriously absent, either it violated the Rules of React or the compiler could not statically analyze it. The react-compiler-healthcheck CLI reports which files compiled successfully across your project — useful for tracking bailout count down over time.
On new projects (xCloud v1 is the first), the compiler is on day one and I do not write useMemo or useCallback at all. On older codebases like BetterDocs, the migration is iterative — fix the lint violations, switch to annotation mode, profile the hot paths, then flip to full compilation. Either way, the era of guessing about memoization is over for me. That is the more important shift, more than any specific bundle-size win.
Plus dans React
On the sapan.dev contact form I rewrote the React Hook Form + manual pending/error setup as a single useActionState call. The component dropped from ~80 lines to ~30, and useOptimistic gave the submit button a snappier feel. Notes on what Actions actually replace.
sapan.dev is built server-component-first across all 16 locales. Notes from designing it that way: the bundle savings, the patterns I keep reaching for, and the moments I had to pull back from "everything server" because the UX needed it.
Templately runs on Redux Toolkit. TubeOnAI is React Query for server state and a small Zustand store for UI. sapan.dev gets by on Redux Toolkit + URL state. Three projects, three different state strategies, and the pattern that emerged for picking between them.