Search across all documentation pages
// Step 1: Install the compiler
// npm install -D babel-plugin-react-compiler
// Step 2: Add to your Next.js config (next.config.ts)
const nextConfig = {
experimental: {
reactCompiler: true,
},
};
export default nextConfig;
// Step 3: Remove manual memoization — the compiler handles it
// BEFORE: Manual memoization
function ProductList({ products, category }: Props) {
const filtered = useMemo(
() => products.filter((p) => p.category === category),
[products, category]
);
const handleClick = useCallback((id: string) => {
selectProduct(id);
}, [selectProduct]);
return <MemoizedGrid items={filtered} onClick={handleClick} />;
}
const MemoizedGrid = memo(Grid);
// AFTER: Compiler auto-memoizes — cleaner code, same performance
function ProductList({ products, category }: Props) {
const filtered = products.filter((p) => p.category === category);
const handleClick = (id: string) => selectProduct(id);
return <Grid items={filtered} onClick={handleClick} />;
}When to reach for this: When starting a new React 19+ project or migrating an existing one. The compiler eliminates boilerplate from useMemo, useCallback, and memo while maintaining equivalent or better performance.
// ---- BEFORE: Manual memoization with 15 lines of boilerplate ----
import { memo, useMemo, useCallback, useState } from "react";
interface User {
id: string;
name: string;
role: "admin" | "user";
lastActive: Date;
}
What this demonstrates:
useMemo x2, useCallback x1, memo x2 wrappers) and 15 lines of boilerplateReact.memo.useMemo which caches at the granularity you specify, the compiler can cache individual JSX elements within a component, potentially achieving better granularity than hand-written optimization.Opting out with the "use no memo" directive:
// Opt a specific component out of compiler optimization
function DebugPanel({ data }: { data: unknown }) {
"use no memo";
// This component always re-renders — useful for debugging
console.log("DebugPanel rendered at", Date.now());
return <pre>{JSON.stringify
Per-file opt-in (gradual migration):
// next.config.ts — only compile specific directories
const nextConfig = {
experimental: {
reactCompiler: {
compilationMode: "annotation",
// Or target specific paths:
// include: ["src/components/**"],
},
},
};// Opt in at the component level with "use memo"
function ExpensiveComponent({ data }: Props) {
"use memo";
// Compiler optimizes this component
return <Chart data={data} />;
}Checking compiler output:
# See what the compiler transforms
npx react-compiler-healthcheck
# Or inspect the compiled output
npx babel --plugins babel-plugin-react-compiler src/Component.tsx"use no memo" directive is a string literal and does not need a type declaration.External mutable state breaks compiler assumptions — The compiler assumes pure rendering. If a component reads from a mutable global variable or a ref during render, the compiler may cache a stale result. Fix: Use useSyncExternalStore for external stores, or add the "use no memo" directive.
Side effects during render — Code that logs, mutates external objects, or triggers network requests during render will be cached by the compiler, meaning side effects may not run on every render as expected. Fix: Move side effects into useEffect or event handlers.
Non-idempotent computations — If a value computation relies on Date.now(), Math.random(), or other non-deterministic sources, the compiler may cache a stale result. Fix: Move non-deterministic values into state or effects.
Class components are not optimized — The compiler only works with function components and hooks. Class components are passed through unchanged. Fix: Migrate class components to function components.
Manual memo is still needed in rare cases — The compiler may not optimize cross-component boundaries in all cases, particularly with higher-order components or render props. Fix: Profile after enabling the compiler. Add manual memo only where profiling shows the compiler missed an optimization.
Keep existing manual memoization during migration — Removing useMemo/useCallback before enabling the compiler leaves your app unoptimized. Fix: Enable the compiler first, verify performance is maintained, then remove manual memoization.
| Approach | Trade-off |
|---|---|
Manual useMemo + useCallback + memo | Full control; verbose; error-prone dependency arrays |
| React Compiler | Zero boilerplate; requires React 19+; experimental |
| State colocation | Avoids the need for memoization entirely; may need restructuring |
| Zustand selectors | Fine-grained subscriptions; adds external dependency |
| Million.js | Alternative compiler for virtual DOM optimization; third-party |
| Signals (Preact, Solid) | Reactive model avoids re-render problem; different framework |
The React Compiler is a Babel plugin that analyzes component code at build time and automatically inserts memoization (equivalent to useMemo, useCallback, and memo). Unlike manual memoization, you do not write dependency arrays -- the compiler tracks dependencies statically.
// next.config.ts
const nextConfig = {
experimental: {
reactCompiler: true,
},
};
export default nextConfig;Also install the Babel plugin: npm install -D babel-plugin-react-compiler.
Yes. Add the "use no memo" directive at the top of the component body:
function DebugPanel({ data }: { data: unknown }) {
"use no memo";
console.log("Rendered at", Date.now());
return <pre>{JSON.stringify(data,
Violating these rules may produce incorrect cached output.
The compiler may cache a stale result because it assumes pure rendering. If a component reads from window.someGlobal or a mutable ref during render, the cached output will not reflect updates to that global.
Fix: Use useSyncExternalStore for external stores, or add "use no memo".
The compiler caches component output. If side effects (logging, network requests, mutations) run during render, they may be skipped when the cached result is reused.
Fix: Move side effects into useEffect or event handlers.
No. The compiler only optimizes function components and hooks. Class components are passed through unchanged. Migrate class components to function components to benefit.
No. Enable the compiler first, verify performance is maintained, then remove manual memoization. Removing hooks before enabling the compiler leaves your app unoptimized during the gap.
No. The compiler works with TypeScript out of the box. Type annotations are preserved -- the compiler only transforms runtime behavior. Generic components are supported without changes.
npx react-compiler-healthcheck
# Or inspect compiled output directly:
npx babel --plugins babel-plugin-react-compiler src/Component.tsxIn compilationMode: "annotation" mode, only components with the "use memo" directive are optimized by the compiler. This allows gradual migration by opting in per component.
Manual useMemo caches at the granularity you specify (one value per hook call). The compiler can cache individual JSX elements within a component, potentially achieving better granularity than hand-written optimization.