If you've spent any time tuning a React app for performance, you know the ritual: wrap callbacks in useCallback, memoize expensive calculations with useMemo, slap React.memo on child components that keep re-rendering for no good reason. It works — but it's also brittle, easy to get wrong, and clutters your components with optimization noise that has nothing to do with what the component is actually supposed to do.
React Compiler v1.0 shipped in October 2025, and it tackles this problem at the build step. Instead of you manually annotating what needs to be memoized, the compiler analyzes your component code, figures it out, and handles it automatically. Meta has been running it in production for a while now and reported a 20–30% reduction in render time across their apps. It's time to take it seriously.
What the Compiler Actually Does ​
React Compiler is a Babel (or SWC) plugin that runs at build time and transforms your component code. It classifies every expression in a component as either static — values that never change between renders — or dynamic — values that may change. From there it automatically injects the memoization equivalents of useMemo, useCallback, and React.memo exactly where they're needed, including in places where you can't manually memoize, like conditionally computed values.
The result is that components you write as plain, idiomatic React get compiled into components that behave as if a very careful engineer went through and added memoization in all the right places.
Here's a before/after to make it concrete. Before the compiler, you might write something like this to avoid unnecessary re-renders:
import { useCallback, useMemo, memo } from "react";
const ProductCard = memo(({ product, onAddToCart }) => {
const formattedPrice = useMemo(
() =>
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(product.price),
[product.price],
);
const handleClick = useCallback(() => {
onAddToCart(product.id);
}, [onAddToCart, product.id]);
return (
<div className="card">
<h2>{product.name}</h2>
<p>{formattedPrice}</p>
<button onClick={handleClick}>Add to Cart</button>
</div>
);
});
With React Compiler, you write the plain version and the compiler handles the memoization for you:
// No memo(), no useMemo, no useCallback — the compiler adds the equivalent automatically
const ProductCard = ({ product, onAddToCart }) => {
const formattedPrice = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(product.price);
const handleClick = () => {
onAddToCart(product.id);
};
return (
<div className="card">
<h2>{product.name}</h2>
<p>{formattedPrice}</p>
<button onClick={handleClick}>Add to Cart</button>
</div>
);
};
Same runtime behavior, significantly less ceremony.
Setting It Up ​
Getting the compiler running is straightforward for most setups. If you're on Next.js 15+, it's a single config flag:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactCompiler: true,
};
export default nextConfig;
For Vite projects, install the Babel plugin and add it to your Vite config:
npm install --save-dev babel-plugin-react-compiler
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [
react({
babel: {
plugins: ["babel-plugin-react-compiler"],
},
}),
],
});
One thing to know: the compiler requires your code to follow the Rules of React — things like not mutating state directly and keeping components pure. If a component violates these rules, the compiler skips it rather than producing broken output. You can also explicitly opt a component out with a "use no memo" directive at the top of the function body when you need manual control.
Caveats and What to Watch For ​
The compiler is stable and production-ready, but it's not a complete free pass on performance thinking. A few things worth keeping in mind:
It requires valid React code. The compiler performs static analysis, so components that mutate props, rely on side effects in render, or break referential equality in surprising ways won't always compile cleanly. Running eslint-plugin-react-compiler alongside the build plugin will flag these before they become silent issues.
useMemo and useCallback still have a role as escape hatches. When a memoized value is used as a useEffect dependency and you need precise control over when the effect fires, manually specifying that dependency is still valid and the compiler won't fight you on it.
Check your bundle output once. Tools like React DevTools show you whether a component has been compiled (it'll show "Memo ✓" in the component tree). A quick audit after enabling the compiler confirms it's actually optimizing the components you expect.
React Compiler doesn't change how you think about component design — it just removes the tax of manually annotating your performance constraints. For most React codebases, enabling it today is a straightforward win.

