One of the recurring challenges in React development is managing unnecessary re-renders and avoiding expensive calculations during component updates.
For years, developers have relied on tools such as useMemo, useCallback, and React.memo to control rendering behavior and improve performance.
Although these tools are useful, manual memoization introduces additional complexity. Developers must understand dependency arrays, preserve stable references, and decide which components or calculations actually need optimization.
React Compiler introduces a different approach: instead of requiring developers to manually identify every optimization opportunity, it analyzes React code at build time and automatically applies memoization where it can.
React Compiler reached its stable 1.0 release in October 2025. It is now suitable for production adoption, provided that applications follow React's rules and are properly tested.
In this article, we'll explore what React Compiler is, how automatic memoization works, whether traditional memoization Hooks are still necessary, and how to enable it in modern React applications.
React Compiler is a build-time optimization tool that analyzes React components and Hooks and automatically applies memoization to code that can be safely optimized.
In simple terms, it attempts to avoid unnecessary recalculations and re-renders without requiring developers to manually wrap every component, function, or computed value in memoization APIs.
Consider the following example:
import { useMemo, useCallback, memo } from "react";
const ProductList = memo(function ProductList({
products,
onSelect,
}: {
products: Product[];
onSelect: (id: string) => void;
}) {
const sortedProducts = useMemo(
() => products.toSorted((a, b) => a.price - b.price),
[products]
);
const handleSelect = useCallback(
(id: string) => {
onSelect(id);
},
[onSelect]
);
return (
<ul>
{sortedProducts.map((product) => (
<li key={product.id}>
<button onClick={() => handleSelect(product.id)}>
{product.name}
</button>
</li>
))}
</ul>
);
});
This component uses three memoization mechanisms to avoid unnecessary work.
With React Compiler enabled, in many cases, we can write the same component more naturally:
function ProductList({
products,
onSelect,
}: {
products: Product[];
onSelect: (id: string) => void;
}) {
const sortedProducts = products.toSorted(
(a, b) => a.price - b.price
);
const handleSelect = (id: string) => {
onSelect(id);
};
return (
<ul>
{sortedProducts.map((product) => (
<li key={product.id}>
<button onClick={() => handleSelect(product.id)}>
{product.name}
</button>
</li>
))}
</ul>
);
}
The Compiler analyzes this code and can generate the appropriate memoization logic during the build process.
The goal is not to change how developers think about React components. It is to reduce the amount of manual performance-related code they need to maintain.
Before understanding React Compiler, it helps to understand memoization.
Memoization is an optimization technique that caches the result of a calculation or preserves a reference so that unchanged inputs do not require unnecessary recomputation.
React Compiler performs this optimization at build time.
At a high level, the compiler analyzes your source code, builds an internal representation, tracks data flow and mutability, and generates memoization logic for expressions and components that can be optimized.
This can include:
- Skipping unnecessary cascading re-renders.
- Caching expensive calculations.
- Preserving function and value references when appropriate.
- Analyzing actual dependencies in a component.
- Optimizing individual expressions rather than treating an entire component as one unit.
One important difference from manual memoization is that the compiler can optimize smaller sections of a function, including conditional sections, with more precise dependency analysis.
| Feature | Manual Memoization | React Compiler |
|---|
| Optimization decisions | Written by developers | Generated during build |
| Hook requirements | Usually required | Often unnecessary |
| Dependency management | Developer-managed | Compiler analysis |
| Code maintenance | Can become complex | Usually simpler |
| Fine-grained control | Direct and explicit | Automatic, with opt-in controls |
This does not mean the compiler is universally better in every situation. The two approaches provide different levels of control.
The short answer: not in many common cases, but these APIs are not deprecated.
React Compiler reduces the need for manual memoization. It does not remove the existing APIs or make them useless.
useMemo caches the result of a calculation between renders.
Without relying on compiler optimization, we might write:
const filteredUsers = useMemo(() => {
return users.filter((user) => user.active);
}, [users]);
With React Compiler, we can often write:
const filteredUsers = users.filter((user) => user.active);
The compiler can analyze the calculation and determine when its result needs to be recomputed.
useCallback preserves a function reference between renders.
Traditional implementation:
const handleSubmit = useCallback(() => {
submitForm(formData);
}, [formData]);
With React Compiler:
const handleSubmit = () => {
submitForm(formData);
};
In many cases, the compiler can handle function memoization automatically.
React.memo prevents a component from re-rendering when its props have not changed.
const UserCard = memo(function UserCard({ user }: Props) {
return <div>{user.name}</div>;
});
React Compiler can automatically apply equivalent optimizations to components that it can safely analyze.
There are situations where manual memoization remains valuable:
- When you need precise control over a cached value or function.
- When reference stability matters for an Effect dependency.
- When your application does not have React Compiler enabled.
- When maintaining an existing codebase where removing memoization could change compilation output or runtime behavior.
The official React guidance is straightforward: for new code, rely on the compiler by default and use useMemo or useCallback when you need more precise control.
For existing codebases, do not remove all existing memoization blindly. Test the behavior and performance before making changes.
React Compiler is designed to work best with React 19, but it also supports React 17 and 18 with the appropriate compiler runtime and configuration.
Start by installing the compiler as a development dependency:
npm install -D babel-plugin-react-compiler
Or with pnpm:
pnpm add -D babel-plugin-react-compiler
For newer versions of @vitejs/plugin-react, the official React documentation provides a setup using reactCompilerPreset and @rolldown/plugin-babel.
First, install the additional package:
npm install -D @rolldown/plugin-babel
Then configure vite.config.ts:
import { defineConfig } from "vite";
import react, {
reactCompilerPreset,
} from "@vitejs/plugin-react";
import babel from "@rolldown/plugin-babel";
export default defineConfig({
plugins: [
react(),
babel({
presets: [reactCompilerPreset()],
}),
],
});
This setup targets newer versions of the Vite React plugin. Older versions may use a different Babel configuration, so check your installed package versions before applying the configuration.
React Compiler support is stable in Next.js 16 and no longer requires an experimental configuration flag.
Install the compiler:
npm install -D babel-plugin-react-compiler
Then enable it in next.config.ts:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactCompiler: true,
};
export default nextConfig;
That's it. Next.js will enable React Compiler as part of its build pipeline.
Earlier Next.js versions have different support and configuration details. The official documentation also describes support starting with Next.js 15.3.1.
Installing the package does not automatically guarantee that the compiler is enabled correctly.
One of the easiest ways to verify its activity is through React Developer Tools.
- Install the React Developer Tools browser extension.
- Run your application in development mode.
- Open the Components panel.
- Inspect the components that have been compiled.
Optimized components may display a Memo ✨ badge.
You can also inspect the generated build output. Compiled code may contain imports from react/compiler-runtime and generated caching logic.
If you are working on a large production application, enabling the compiler across the entire codebase without testing is not the best starting point.
React Compiler supports incremental adoption, allowing teams to introduce it gradually.
For example, you can begin with newly developed features or specific directories, validate the results, and expand compiler coverage over time.
One available approach is compilationMode: "annotation".
In this mode, only components and Hooks explicitly marked with the "use memo" directive are compiled.
function ProductList({ products }: Props) {
"use memo";
const sortedProducts = products.toSorted(
(a, b) => a.price - b.price
);
return (
<ul>
{sortedProducts.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
You can also temporarily exclude a component from compilation:
function LegacyComponent() {
"use no memo";
// This component will be skipped by the compiler.
return <div>Legacy component</div>;
}
These options are particularly useful when migrating large applications or introducing the compiler into an established codebase.
React Compiler offers meaningful benefits, but it should not be treated as a universal solution to every performance problem.
If a component is too large, state is placed at the wrong level, or unnecessary data flows through the component tree, the compiler cannot automatically fix every architectural problem.
The compiler relies on React's programming model to analyze components safely.
Patterns such as mutating props or state, incorrectly using Hooks, or performing side effects during rendering can prevent parts of your code from being optimized.
Because the compiler analyzes and transforms source code, enabling it may increase development and production build times.
This overhead should be evaluated alongside the performance improvements experienced by users.
React Compiler focuses on render-related optimization and calculations within React components.
It does not automatically solve issues such as:
- Network bottlenecks.
- Large JavaScript bundles.
- Expensive API requests.
- Unoptimized images.
- Poor data-fetching strategies.
- Long-running main-thread tasks.
- Inefficient DOM operations.
You should continue using tools such as React Profiler and Chrome Performance Panel to identify actual bottlenecks.
No. The actual improvement depends on the application's architecture, rendering patterns, expensive calculations, and existing optimization strategies.
If an application is already well optimized, you may not notice a significant difference.
On the other hand, applications with excessive manual memoization or unnecessary cascading re-renders may benefit considerably.
The only reliable way to evaluate the impact is to measure performance before and after enabling the compiler under comparable conditions.
React Compiler represents an important step forward in the React developer experience.
By automating much of the memoization process, it allows developers to write simpler and more maintainable code without manually optimizing every component and function.
However, it does not replace a solid understanding of React, good component architecture, proper state management, or performance profiling.
My recommendations:
- Consider enabling React Compiler in new React 19 applications.
- Use incremental adoption when introducing it into existing projects.
- Do not remove existing memoization without testing.
- Measure real-world results with profiling tools.
- Focus on application architecture rather than simply reducing the number of memoization Hooks.
Ultimately, the goal is not to eliminate every useMemo and useCallback call. The goal is to build faster applications with less unnecessary complexity.
Written for frontend developers interested in modern React performance optimization and building maintainable applications.