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.
What the React Compiler actually does
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
],
};Before and after: the memoization you stop writing
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.
Why it leans on the Rules of React
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.
Wiring it into your build and linting
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 hook- Install babel-plugin-react-compiler as an exact dev dependency, plus eslint-plugin-react-hooks at latest for the compiler lint rules.
- In Next.js, set reactCompiler to true in next.config, or use annotation mode plus a use memo directive for gradual opt-in.
- In Vite 6 and up, add the react compiler preset through the Babel plugin; older Vite passes babel-plugin-react-compiler to the react plugin directly.
- For React 17 and 18, add the react-compiler-runtime dependency and set the target version in the compiler config.
Manual memoization vs the React Compiler
The 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.

