Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
# Install the compiler and its Babel plugin
npm install -D babel-plugin-react-compiler
npm install -D react-compiler-healthcheck # optional: check compatibility// babel.config.js
module.exports = {
plugins: [
["babel-plugin-react-compiler", {
// target defaults to "19" for React 19
}],
],
};// next.config.js (Next.js 15+)
module.exports = {
experimental: {
reactCompiler: true,
},
};When to reach for this: Enable the React Compiler for any React 19 project to automatically optimize re-renders. It replaces the need for useMemo, useCallback, and React.memo in the vast majority of cases.
// Before: Manual memoization everywhere
import { useMemo, useCallback, memo } from "react";
type Todo = { id: string; text: string; completed: boolean };
const TodoItem = memo(function TodoItem({
todo,
onToggle,
}: {
todo: Todo;
onToggle: (id: string) => void;
}) {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
{todo.text}
</li>
);
});
function TodoList({ todos }: { todos: Todo[] }) {
const [filter, setFilter] = useState<"all" | "active" | "completed">("all");
const filteredTodos = useMemo(() => {
switch (filter) {
case "active": return todos.filter((t) => !t.completed);
case "completed": return todos.filter((t) => t.completed);
default: return todos;
}
}, [todos, filter]);
const handleToggle = useCallback((id: string) => {
// toggle logic
}, []);
const stats = useMemo(() => ({
total: todos.length,
active: todos.filter((t) => !t.completed).length,
completed: todos.filter((t) => t.completed).length,
}), [todos]);
return (
<div>
<p>{stats.active} items left</p>
<ul>
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
))}
</ul>
</div>
);
}// After: With React Compiler -- no manual memoization needed
import { useState } from "react";
type Todo = { id: string; text: string; completed: boolean };
function TodoItem({
todo,
onToggle,
}: {
todo: Todo;
onToggle: (id: string) => void;
}) {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
{todo.text}
</li>
);
}
function TodoList({ todos }: { todos: Todo[] }) {
const [filter, setFilter] = useState<"all" | "active" | "completed">("all");
const filteredTodos = (() => {
switch (filter) {
case "active": return todos.filter((t) => !t.completed);
case "completed": return todos.filter((t) => t.completed);
default: return todos;
}
})();
function handleToggle(id: string) {
// toggle logic
}
const stats = {
total: todos.length,
active: todos.filter((t) => !t.completed).length,
completed: todos.filter((t) => t.completed).length,
};
return (
<div>
<p>{stats.active} items left</p>
<ul>
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
))}
</ul>
</div>
);
}What this demonstrates:
useMemo, useCallback, and React.memo to avoid unnecessary re-rendersuseMemo, useCallback, and React.memo where appropriate.target: "17" or target: "18" and install the react-compiler-runtime package."use no memo" directive at the top of a function to opt out a specific component or hook from compiler optimization.Gradual rollout with opt-in mode:
// babel.config.js -- only compile files with "use memo" directive
module.exports = {
plugins: [
["babel-plugin-react-compiler", {
compilationMode: "annotation", // only compile "use memo" functions
}],
],
};// Only this component is compiled
function OptimizedList({ items }: { items: string[] }) {
"use memo";
return (
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
);
}Opting out a specific component:
function LegacyComponent({ data }: { data: any }) {
"use no memo";
// This component will not be optimized by the compiler
// Useful for code that intentionally breaks React rules
externalMutableStore.value = data;
return <div>{data.label}</div>;
}Health check before adopting:
# Run the health check to see how compatible your codebase is
npx react-compiler-healthcheck --verboseESLint plugin for rule validation:
npm install -D eslint-plugin-react-compiler// eslint.config.js
import reactCompiler from "eslint-plugin-react-compiler";
export default [
{
plugins: { "react-compiler": reactCompiler },
rules: {
"react-compiler/react-compiler": "error",
},
},
];"use memo" and "use no memo" are string literal directives (like "use strict") and do not need type declarations.console.log(Date.now()) or modifying external variables during render may produce stale results when the compiler caches the output. Fix: Move side effects into useEffect or event handlers.useSyncExternalStore for external mutable state.eslint-plugin-react-compiler to catch issues and check the compiler's build output for skipped components.compilationMode: "annotation" option to compile only opted-in components during migration.| Approach | When to choose |
|---|---|
| React Compiler | Default for React 19 projects -- automatic, zero-effort optimization |
Manual useMemo / useCallback | React 18 or when you need explicit control over what is memoized |
React.memo | React 18 or for class components the compiler does not handle |
| Million.js | Alternative compiler focused on virtual DOM diffing optimization |
| Manual component splitting | Isolating expensive subtrees when compiler or memoization is insufficient |
"use no memo" directive | Opting out specific components from compiler optimization |
useMemo, useCallback, and React.memo in most cases// next.config.js
module.exports = {
experimental: {
reactCompiler: true,
},
};For non-Next.js projects, install babel-plugin-react-compiler and add it to your Babel config.
eslint-plugin-react-compiler to detect which components are being skipped and whyfunction LegacyComponent({ data }) {
"use no memo";
// This component will not be optimized
externalStore.value = data;
return <div>{data.label}</div>;
}The "use no memo" directive is placed at the top of the function body.
// babel.config.js
module.exports = {
plugins: [
["babel-plugin-react-compiler", {
compilationMode: "annotation",
}],
],
};Then add "use memo" at the top of functions you want to compile. Only annotated functions are optimized.
npx react-compiler-healthcheck --verboseThis scans your codebase and reports which components follow React's rules and which may cause issues.
target: "17" or target: "18" in the plugin configreact-compiler-runtime packageconsole.log(Date.now()) during render may produce stale resultsuseEffect or event handlers where they belonguseSyncExternalStore for external mutable state so React can track changes"use memo" and "use no memo" are string literal directives and do not need type declarationscompilationMode: "annotation" to compile only opted-in components during an incremental migrationReviewed by Chris St. John·Last updated Jul 7, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥