On October 21, 2025, Vercel shipped Next.js 16. The headline was Turbopack going stable as the default bundler. Buried four sections down was a smaller, stranger announcement: the reactCompiler flag had been promoted from experimental to stable - and left off by default.
That gap is the story. Vercel does not usually leave performance wins sitting behind a flag. Turbopack went default the same day. So did the rewritten prefetch cache. The React Compiler - a tool Meta has run in production since 2024, now at v1.0 - did not.
The reason is in the docs, stated plainly:
It is not enabled by default as we continue gathering build performance data across different application types. Expect compile times in development and during builds to be higher when enabling this option as the React Compiler relies on Babel.
Translation: the runtime is ready. The toolchain is not. If you are deciding whether to flip reactCompiler: true in your next.config.ts this week, that sentence is the only one that matters.
What v1.0 actually does
The React Compiler is an automatic memoizer. You write components the way React's docs always told you to - no useMemo, no useCallback, no manual React.memo - and the compiler rewrites them so they only re-render when their inputs actually change.
It is not a bundler or a transpiler. It is a Babel plugin that runs over your component source and emits memoized output that looks roughly like this:
// You write:
function ProductList({ products, onSelect }) {
const sorted = products.sort((a, b) => a.price - b.price);
return sorted.map(p => (
<Card key={p.id} product={p} onClick={() => onSelect(p)} />
));
}
// The compiler emits something equivalent to:
function ProductList({ products, onSelect }) {
const $ = useMemoCache(3);
const sorted = $[0] !== products
? ($[0] = products, $[1] = products.sort((a, b) => a.price - b.price))
: $[1];
// ...stable onClick handlers per row, cached against onSelect and p.id
}Meta has shipped this on the Quest Store and reports up to a 12% improvement on initial loads, certain interactions running 2.5x faster, and - importantly - neutral memory usage. That last number is the one to read twice. Aggressive memoization usually trades RAM for CPU. The compiler appears not to.
Why Vercel is being cautious
The compiler runs as a Babel plugin. Next.js 16's default bundler is Turbopack, a Rust-based system that does not run Babel for any project that does not opt in. Turning on reactCompiler forces Babel back into the pipeline for every file that contains JSX or React Hooks.
Next.js mitigates this with a custom SWC pass that pre-filters which files Babel even sees. The docs describe it as analyzing the project and applying the compiler only to relevant files. That is a real optimization. It is not a free one.
For a small app, the build-time delta is noise. For a monorepo with three hundred routes, it is not. Vercel runs the largest Next.js builds in the world on their own infrastructure, and the team has visibility into how that distribution looks at the 99th percentile. They are not flipping the default until those numbers stop looking ugly.
Contrast this with Expo SDK 54+, where the compiler is on by default. Expo's build cost profile is different. Their users are mostly not running enterprise monorepos. Defaults reflect the median user, not the worst-case one.
Should you turn it on this week?
Three questions decide it.
1. Do you currently feel the cost of missing memoization?
If your app re-renders heavily on every state change and you have already lost an afternoon to useMemo archaeology, the compiler is a clean win. The case where it helps least is the case where you already memoized everything by hand - in that situation you get roughly the same runtime profile and a slower build. Audit git log -S 'useMemo' before you decide.
2. Can your dev loop absorb a Babel pass?
Even with the SWC pre-filter, expect Fast Refresh to slow somewhere between 10% and 40% depending on how JSX-dense your codebase is. If your dev server already takes 800ms to recompile a route, you will feel it. If it takes 80ms, you probably won't.
There is a softer option: annotation mode.
// next.config.ts
const nextConfig: NextConfig = {
reactCompiler: {
compilationMode: 'annotation',
},
};In annotation mode, the compiler only touches components and hooks that opt in with the "use memo" directive. You enable it per-file as you migrate, and the build cost only applies to files you have explicitly marked.
export default function ProductList() {
"use memo";
// compiler runs here
return <Grid />;
}This is the right default for a large existing codebase. Turn the flag on globally only after annotation mode has been running cleanly for a release cycle.
3. Are you on React 19, and do you trust your test suite?
The compiler works with React 17+, but the version it most cleanly composes with is React 19, which Next.js 16 ships against. If you are still pinned to React 18, fix that first.
The compiler also depends on your components following the Rules of React - no mutation of props or state during render, no setState during render, hooks called in stable order. Most apps mostly follow these rules. Almost no app follows them perfectly. The team's explicit recommendation if your test coverage is thin: pin the exact version.
{
"devDependencies": {
"babel-plugin-react-compiler": "1.0.0"
}
}Not ^1.0.0. The literal version. Memoization bugs are some of the most subtle bugs in a React app, and you want to control exactly when you pick up a new release.
The eslint rule is the part everyone should adopt
Even if you are not ready for the compiler itself, the linting it powers is worth adopting today. eslint-plugin-react-hooks has absorbed the compiler's static analysis and ships three rules that catch bugs the runtime never warned you about:
set-state-in-render- flags setState patterns that cause render loopsset-state-in-effect- flags expensive work inside effects that should be event handlersrefs- prevents unsafe ref access during render
The linter is decoupled from the compiler. You can install the new rules without enabling reactCompiler anywhere. There is no Babel cost. There is no opt-in flag in your bundler. There is no reason not to.
What to watch
The compiler being stable-but-off in Next.js 16 is a temporary state, not a permanent one. The cleanest signal that Vercel has finished gathering data will be a Next.js minor release that promotes reactCompiler: true to the default - probably with an opt-out for projects that explicitly do not want Babel in their build.
The other thing to watch for is a Rust port of the compiler itself. Several people inside the React team have hinted that the Babel dependency is the friction, and that an SWC- or Oxc-based reimplementation would let the compiler run inside Turbopack and Rolldown without crossing language boundaries. If that lands, the default flips overnight.
Until then: turn on the lint rules everywhere, turn on annotation mode in the hot files where you have been wishing for useMemo for the past year, and leave the global flag alone until you have measured a build you care about. Stable is not the same as default. The team that wrote both words knows the difference.
