React Compiler: Automatic Memoization Explained
The React Compiler automatically memoizes components and values at build time, cutting most manual useMemo, useCallback, and React.memo calls.
The React Compiler is a build-time tool that automatically memoizes React components and values, so that re-renders skip work that hasn’t actually changed — without the developer writing a single useMemo, useCallback, or React.memo call. It reads your component source, figures out which values depend on which props and state, and inserts the equivalent of manual memoization for you during the build.
The problem manual memoization was solving
React re-renders a component whenever its parent re-renders, or its own props or state change. Most of that re-rendering is wasted: the resulting JSX tree comes out identical to last time, but React still has to call the function, build the tree, and diff it. The traditional fix was manual memoization — wrapping expensive calculations in useMemo, wrapping callback functions in useCallback so child components don’t see a “new” function on every render, and wrapping whole components in React.memo so they skip re-rendering when their props are shallow-equal to last time.
This works, but it’s tedious and easy to get wrong. Forget a dependency in a useMemo array and you get stale values. Add a dependency that changes every render and the memoization does nothing. Wrap a component in React.memo but pass it a new object literal as a prop on every render, and the memoization is silently defeated. Codebases end up with memoization scattered everywhere, some of it load-bearing and some of it dead weight nobody wants to remove because nobody’s sure what it’s protecting against.
What the compiler actually does
The React Compiler runs as a build step (typically as a Babel plugin) and performs static analysis on your components and hooks. For each function, it determines which values are derived from which inputs, and automatically wraps the appropriate pieces in memoization — conceptually similar to what a careful engineer would write by hand with useMemo and useCallback, except generated correctly and consistently every time.
| Manual memoization | React Compiler | |
|---|---|---|
| Who decides what to memoize | The developer, case by case | Static analysis of the whole component |
| Correctness risk | Stale closures, wrong dependency arrays | Handled by the compiler’s dependency tracking |
| Code you write | useMemo, useCallback, React.memo calls | Plain component code |
| Coverage | Only what someone remembered to wrap | Applied consistently across the codebase |
| Debuggability | Explicit, visible in source | Implicit, requires compiler output inspection |
The practical effect is that a component tree that previously needed careful, hand-tuned memoization to avoid unnecessary re-renders now gets comparable behavior automatically. You still write idiomatic component code — the compiler is doing optimization work that used to be a manual, error-prone chore layered on top of it.
It depends on the Rules of React
The compiler’s static analysis only works if your components follow the Rules of React — the constraints React has always had, like not mutating props or state during render, and keeping render functions pure. These are the same rules that make components safe to run as React Server Components or to render twice under strict mode. If a component mutates a prop object in place, or reads from a mutable ref during render and expects that to trigger updates, the compiler either bails out and leaves that component unoptimized or, in rarer edge cases, memoizes in a way that assumes purity you didn’t actually maintain. This is one reason ESLint’s react-hooks rules matter more, not less, with the compiler in the picture: they’re the mechanical check that your code is compiler-safe.
Existing manual memoization doesn’t need to be ripped out immediately. useMemo and useCallback calls in an already-compiled component are mostly redundant rather than harmful, and teams generally migrate by removing them incrementally rather than in one pass.
Where it doesn’t help
The compiler optimizes re-render cost within components you’ve written — it doesn’t change your state management architecture. If your app re-renders large swaths of the tree because of how state is structured (a single global store update triggering every subscriber, for instance), the compiler won’t fix that on its own; the fix is still choosing the right state shape, the same conversation covered when comparing Zustand and Redux or evaluating fine-grained reactive state with signals as an alternative to React’s render-and-diff model entirely. The compiler makes React’s existing re-render model cheaper; it doesn’t replace it with something structurally different.
It also isn’t a substitute for optimistic UI updates or other techniques for perceived performance — those are about making updates feel instant to the user, while the compiler is about not doing unnecessary work once an update is triggered. The two solve adjacent but separate problems.
Should you turn it on
For most React codebases that already follow the Rules of React — which the linter has been nudging developers toward for years — enabling the compiler is close to a free performance win: less manual memoization to write and maintain, and re-render behavior that’s at least as good as a careful team achieves by hand, applied uniformly instead of unevenly. The main cost is trust: teams used to reasoning about performance by reading explicit useMemo calls need to get comfortable reasoning about it through compiler output instead, which is a real workflow change even if the runtime outcome is better.
The takeaway
The React Compiler moves memoization from a manual, error-prone habit into an automatic build-time step, analyzing components to decide what to cache the way a careful engineer would by hand — just applied consistently. It relies on components following the Rules of React to work correctly, doesn’t replace the need for good state architecture, and is best adopted by removing hand-written useMemo and useCallback calls gradually rather than all at once.
Tagged
Keep reading
Takina · · 5 min read TanStack Query vs SWR: Choosing a Data-Fetching Library
TanStack Query and SWR both cache and revalidate server data in React, but differ in scope, mutation handling, and how much they manage for you.
Takina · · 4 min read useMemo vs useCallback: When to Use Each in React
useMemo caches a computed value; useCallback caches a function reference. Both skip work on re-render, but they memoize different things.
Takina · · 4 min read Next.js App Router vs Pages Router: What's the Difference
The App Router uses React Server Components and nested layouts; the Pages Router is Next.js's original file-based model. How they differ and when to pick each.