React Compiler: Automatic Memoization Without useMemo

Photo by Markus Spiske on Pexels
The React Compiler is a build-time optimizing compiler, formerly called React Forget, that automatically memoizes React components and hook values. It inserts the equivalent of useMemo, useCallback, and React.memo during the build, so you rarely need to write them by hand. It reached stable version 1.0 on October 7, 2025.
In most cases, no. The compiler infers dependencies from your data flow and memoizes automatically, often more precisely than hand-written code. You can still use useMemo and useCallback as deliberate escape hatches for explicit control, and the React team suggests keeping existing memoization until tests prove it is redundant.
Install babel-plugin-react-compiler as a dev dependency, then set reactCompiler to true in next.config. Next.js runs an SWC pass that only feeds files containing JSX or Hooks to the Babel plugin, keeping builds fast. You can also use annotation mode with a use memo directive to opt in gradually.
Automatic memoization is only safe when components are pure and predictable. The compiler assumes components and hooks are idempotent during render, that you never mutate props or state, and that hooks are called only at the top level. The compiler-powered lint rules in eslint-plugin-react-hooks catch violations before they cause problems.
Yes. React Compiler 1.0 is stable and has run in production across Meta's largest apps. The main caveat is that if your code breaks the Rules of React in ways the linter cannot detect, memoization changes could affect behavior, so pin the exact version and keep end-to-end tests when adopting it.

Photo by Markus Spiske on Pexels
Key Takeaway
The React Compiler is React's build-time optimizing compiler that automatically memoizes components and hook values, so developers rarely need useMemo, useCallback, or React.memo. It reached stable version 1.0 in October 2025, relies on the Rules of React for safety, and integrates with Babel, Vite, and Next.js.
For a decade, keeping a React app fast meant scattering useMemo, useCallback, and React.memo across the tree to stop values and components from being recreated on every render. It worked, but it was manual, easy to get wrong, and noisy. Forget a dependency and you cached stale data; add one too many and the memo did nothing but cost memory.
The React Compiler, formerly known internally as React Forget, replaces most of that hand work. It is a build-time compiler that analyzes your components and hooks and inserts the memoization automatically. I have been following it since the experimental days, and with the stable 1.0 release it is finally something I reach for on new projects by default rather than as a science experiment.
The compiler runs as part of your build, not in the browser. It reads each component, works out which values depend on which inputs, and emits equivalent code that reuses previously computed values when the inputs have not changed. The output behaves as if you had written perfect useMemo and useCallback calls by hand, but you did not write any of them.
It also does things manual memoization struggles with. Because the compiler understands control flow, it can memoize values that appear after an early return or inside a conditional, which the Rules of Hooks forbid you from doing yourself. React Compiler 1.0 was published on October 7, 2025, works with React 17 and up, and has been running in production across Meta's largest apps for a long time before it went stable.
# Install the compiler's Babel plugin (pin the exact version)
npm install --save-dev --save-exact babel-plugin-react-compiler@latest
# Compiler-powered lint rules ship inside eslint-plugin-react-hooks
npm install --save-dev eslint-plugin-react-hooks@latest
# babel.config.js — the compiler must run FIRST in the plugin pipeline
module.exports = {
plugins: [
"babel-plugin-react-compiler",
// ...other plugins
],
};The clearest way to feel the difference is to look at a component both ways. On the left is the defensive style most teams write today; on the right is the same logic once the compiler is enabled. The runtime behavior is the same, but the second version is just the plain description of what the component does.
// BEFORE — hand-written memoization to stop needless re-renders
function ProductList({ products, query }) {
const filtered = useMemo(
() => products.filter((p) => p.name.includes(query)),
[products, query]
);
const onSelect = useCallback((id) => track(id), []);
return <List items={filtered} onSelect={onSelect} />;
}
// AFTER — with the React Compiler enabled, you write plain code.
// The compiler inserts equivalent memoization at build time.
function ProductList({ products, query }) {
const filtered = products.filter((p) => p.name.includes(query));
const onSelect = (id) => track(id);
return <List items={filtered} onSelect={onSelect} />;
}The compiler is not all-or-nothing. It emits a use memo directive on the code it optimizes, and you can run it in annotation mode so only components you explicitly opt in get compiled. That makes it safe to roll out one directory at a time on a large codebase.
Automatic memoization is only safe if your components are predictable. The compiler assumes your code follows the Rules of React: components and hooks must be pure and idempotent during render, you must not mutate props or state, and hooks are only called at the top level. When those hold, caching a result and reusing it is guaranteed to produce the same output.
This is why the linting story matters. The compiler-powered rules now ship inside eslint-plugin-react-hooks, in its recommended preset, so you no longer install a separate plugin. Rules like set-state-in-render, set-state-in-effect, and unsafe ref access catch the exact patterns that would make automatic memoization unsafe, before the compiler ever touches your code.
If your code breaks the Rules of React in a way the linter cannot detect, the compiler may still change how often effects run or values recompute. That is the real risk. Pin the exact compiler version, keep end-to-end tests, and read the incremental adoption guide before removing existing memoization from critical paths.
The core package is babel-plugin-react-compiler, and it must run first in your Babel pipeline so it sees your source before other transforms rewrite it. Framework integrations wrap this for you. In Next.js you flip a single reactCompiler flag, and Next runs an SWC pass that only feeds files containing JSX or Hooks to the Babel plugin, which keeps builds fast. Vite uses the react compiler preset, and Expo SDK 54 and up enables it by default.
// next.config.ts — Next.js runs the compiler through Babel,
// but uses an SWC pass so only files with JSX or Hooks are compiled.
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactCompiler: true,
};
export default nextConfig;
// Opt-in mode: compile only components/hooks you mark with a directive
const optInConfig: NextConfig = {
reactCompiler: { compilationMode: "annotation" },
};
// then add "use memo" at the top of a chosen component or hookThe compiler does not make manual memoization illegal; it makes it mostly unnecessary. Here is how the two approaches compare on the things that actually matter day to day.
| Aspect | Manual memoization | React Compiler |
|---|---|---|
| Who writes it | You, by hand, on every hot path | The build, automatically, everywhere |
| Dependency arrays | Yours to maintain and easy to get wrong | Inferred by the compiler from data flow |
| Conditional and post-return values | Cannot memoize; Rules of Hooks forbid it | Memoized correctly across branches |
| Failure mode | Stale data or wasted memory from bad deps | Skips code that breaks the Rules of React |
My rule of thumb after shipping with it: enable the compiler and the recommended lint rules on new code, let it own memoization, and keep any existing useMemo you rely on until tests prove it is redundant. Treat useMemo and useCallback as deliberate escape hatches, not the default posture, and your components get noticeably easier to read.