Before (Manual) After (Compiler)
────────────────────────── ──────────────────────────
useMemo everywhere Automatic memoization
useCallback for handlers Compiler handles it
React.memo for components Not needed
Easy to forget, bugs Always optimized
Complex dependency arrays No dependency arrays
npm install -D babel-plugin-react-compiler
# For Next.js
npm install -D babel-plugin-react-compiler// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
reactCompiler: true,
},
}
export default nextConfig// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
// Options
}],
],
}// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
],
})// Without compiler - lots of manual work
function TodoList({ todos, filter }) {
// Manual memoization of filtered list
const filteredTodos = useMemo(
() => todos.filter(todo => todo.status === filter),
[todos, filter]
)
// Manual callback memoization
const handleToggle = useCallback(
(id: string) => {
// toggle logic
},
[]
)
// Manual callback with dependency
const handleDelete = useCallback(
(id: string) => {
deleteTodo(id, filter)
},
[filter]
)
return (
<ul>
{filteredTodos.map(todo => (
<MemoizedTodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
)
}
// Manual React.memo
const MemoizedTodoItem = React.memo(function TodoItem({
todo,
onToggle,
onDelete
}) {
return (
<li>
<span onClick={() => onToggle(todo.id)}>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
)
})// With compiler - write naturally, compiler optimizes
function TodoList({ todos, filter }) {
// Compiler automatically memoizes this
const filteredTodos = todos.filter(todo => todo.status === filter)
// Compiler handles function identity
const handleToggle = (id: string) => {
// toggle logic
}
const handleDelete = (id: string) => {
deleteTodo(id, filter)
}
return (
<ul>
{filteredTodos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
)
}
// No React.memo needed
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li>
<span onClick={() => onToggle(todo.id)}>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
)
}// Your code
function Component({ items }) {
const doubled = items.map(x => x * 2)
return <List data={doubled} />
}
// Compiler transforms to (conceptually)
function Component({ items }) {
const doubled = useMemo(
() => items.map(x => x * 2),
[items]
)
return <List data={doubled} />
}The compiler enforces and relies on React's rules:
// Components must be pure
function Component({ count }) {
// Don't mutate props
count.value = 5
// Don't read/write external mutable values
globalCounter++
// Don't call hooks conditionally
if (count > 0) {
const [state] = useState(0) // Error!
}
}
// Hooks must follow rules
function useCustomHook(value) {
// Dependencies must be correct
useEffect(() => {
console.log(value) // Compiler tracks this
}, []) // Compiler may fix or warn
}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',
},
},
]// Disable compiler for specific component
function LegacyComponent() {
'use no memo'
// Component won't be optimized
// Use for gradual migration
}// Check if compiler is working
// React DevTools shows "Memo ✓" badge on optimized components
// Or check build output
// Compiled components have _c variables// External subscriptions still need cleanup
function Component() {
useEffect(() => {
const sub = eventEmitter.subscribe(handler)
return () => sub.unsubscribe() // Still needed
}, [])
}
// Refs still work the same
function Component() {
const ref = useRef(null)
// ref.current access is fine
}
// Context is still context
function Component() {
const theme = useContext(ThemeContext)
// Re-renders when context changes
}// 1. Enable ESLint plugin first
// 2. Fix all violations
// 3. Enable compiler in development
// 4. Test thoroughly
// 5. Enable in production
// Gradual opt-in
const nextConfig = {
experimental: {
reactCompiler: {
compilationMode: 'annotation', // Only annotated components
},
},
}
// Then annotate components to opt-in
function MyComponent() {
'use memo'
// This component is compiled
}Before Compiler:
- Manual memo: Developer must remember
- Missed optimizations: Common
- Dependency bugs: Frequent
- Code verbosity: High
After Compiler:
- Auto memoization: 100% coverage
- Optimal updates: Guaranteed
- No dependency bugs: Compiler tracks
- Clean code: Just write React
Learned: December 20, 2025 Tags: React, Compiler, Performance, Optimization, Memoization