For years, I've been the "performance guy" on my team. Every code review, I'd hunt for missing useMemo, unnecessary re-renders, and expensive recalculations. I'd write the same optimization patterns over and over: wrap this in useCallback, memoize that with useMemo, add React.memo here. Then React 19's compiler landed, and everything changed. I deleted 40% of my memoization code. The compiler handled it automatically—and did it better than I could manually. This isn't just a convenience feature. It's a fundamental shift in how React applications perform.
The React Compiler (formerly "React Forget") is React 19's answer to performance optimization. Instead of manually sprinkling useMemo and useCallback throughout your code, the compiler analyzes your components and automatically memoizes expensive computations. Let's explore how this revolutionary tool works and how to leverage it in production.
What is the React Compiler?
The React Compiler is a build-time optimization tool that automatically adds memoization to your React components. It analyzes your code, identifies expensive operations, and inserts the equivalent of useMemo, useCallback, and React.memo—without you writing a single optimization hook.
Before Compiler (Manual Optimization):
function ProductList({ products, onSort }: Props) {
// Manual memoization - you write this
const sortedProducts = useMemo(() => {
return products.sort((a, b) => a.price - b.price);
}, [products]);
// Manual callback memoization
const handleSort = useCallback((sortBy: string) => {
onSort(sortBy);
}, [onSort]);
return (
<div>
{sortedProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onSort={handleSort}
/>
))}
</div>
);
}
// Manual component memoization
export default React.memo(ProductList);
With Compiler (Automatic Optimization):
function ProductList({ products, onSort }: Props) {
// Compiler automatically memoizes this!
const sortedProducts = products.sort((a, b) => a.price - b.price);
// Compiler automatically memoizes this!
const handleSort = (sortBy: string) => {
onSort(sortBy);
};
return (
<div>
{sortedProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onSort={handleSort}
/>
))}
</div>
);
}
// No React.memo needed - compiler handles it!
export default ProductList;
Result: Same performance, 30% less code, zero manual optimization.
How the Compiler Works
Compilation Process
┌─────────────────────┐
│ Your Source Code │
│ (React component) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ React Compiler │
│ - Analyze deps │
│ - Identify pure │
│ - Insert memoize │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Optimized Code │
│ (with auto-memo) │
└─────────────────────┘
What the Compiler Does:
- Dependency Analysis: Tracks which variables depend on props/state
- Purity Detection: Identifies pure computations that can be memoized
- Automatic Memoization: Inserts memoization at optimal points
- Component Bailout: Skips re-renders when props haven't changed
Example Transformation:
// Your code:
function ExpensiveComponent({ data }) {
const processed = data.map(x => x * 2);
const filtered = processed.filter(x => x > 10);
return <div>{filtered.length}</div>;
}
// Compiler output (conceptual):
function ExpensiveComponent({ data }) {
const processed = useMemo(() =>
data.map(x => x * 2),
[data]
);
const filtered = useMemo(() =>
processed.filter(x => x > 10),
[processed]
);
return useMemo(() =>
<div>{filtered.length}</div>,
[filtered]
);
}
Installation & Setup
Step 1: Install Compiler
npm install babel-plugin-react-compiler
Step 2: Configure Babel
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
// Enable source maps for debugging
compilationMode: 'annotation', // or 'all', 'strict'
}],
],
};
Step 3: Configure Next.js (if applicable)
// next.config.js
const nextConfig = {
experimental: {
reactCompiler: true,
},
};
module.exports = nextConfig;
Step 4: Verify Installation
npm run build
Look for compiler output:
✓ Compiled successfully
✓ React Compiler: 142 components optimized
- 89 components fully memoized
- 53 components partially memoized
- 0 components skipped
Compilation Modes
1. Annotation Mode (Recommended)
Opt-in per component:
'use memo'; // Directive to enable compiler
function OptimizedComponent({ data }: Props) {
// Compiler optimizes this component
return <div>{data.value}</div>;
}
When to use: Gradual migration, testing performance impact.
2. All Mode
Optimize everything:
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
compilationMode: 'all', // Auto-optimize all components
}],
],
};
When to use: New projects, confidence in compiler stability.
3. Strict Mode
Maximum optimization with warnings:
compilationMode: 'strict'
Warns about patterns that prevent optimization (side effects, mutations, etc.).
What Gets Optimized
✅ Automatically Memoized:
1. Expensive Computations
function DataProcessor({ items }: Props) {
// Compiler memoizes this automatically
const processed = items
.filter(x => x.active)
.map(x => x.value * 2)
.reduce((sum, x) => sum + x, 0);
return <div>Total: {processed}</div>;
}
2. Event Handlers
function Form({ onSubmit }: Props) {
// Compiler memoizes this callback
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
onSubmit(new FormData(e.target));
};
return <form onSubmit={handleSubmit}>...</form>;
}
3. Derived State
function UserProfile({ user }: Props) {
// Compiler memoizes derived values
const fullName = `${user.firstName} ${user.lastName}`;
const displayName = fullName.toUpperCase();
const initials = `${user.firstName[0]}${user.lastName[0]}`;
return (
<div>
<h1>{displayName}</h1>
<Avatar initials={initials} />
</div>
);
}
4. JSX Elements
function Layout({ children }: Props) {
// Compiler memoizes static JSX
const header = <Header />;
const footer = <Footer />;
return (
<div>
{header}
<main>{children}</main>
{footer}
</div>
);
}
❌ Cannot Be Optimized:
1. Side Effects
function BadComponent({ id }: Props) {
// Cannot optimize - has side effect
fetch(`/api/data/${id}`); // ❌ Don't do this!
return <div>Loading...</div>;
}
// Fix: Move to useEffect
function GoodComponent({ id }: Props) {
useEffect(() => {
fetch(`/api/data/${id}`);
}, [id]);
return <div>Loading...</div>;
}
2. Direct Mutations
function MutableComponent({ items }: Props) {
// Cannot optimize - mutates input
items.sort(); // ❌ Don't mutate props!
return <List items={items} />;
}
// Fix: Create new array
function ImmutableComponent({ items }: Props) {
const sorted = [...items].sort(); // ✅ New array
return <List items={sorted} />;
}
3. External References
let externalState = 0; // Module-level state
function UnstableComponent() {
// Cannot optimize - depends on external state
const value = externalState++;
return <div>{value}</div>;
}
Migration Strategy
Step 1: Audit Existing Code
Find manual memoizations that can be removed:
# Search for manual memoization
git grep -n "useMemo\|useCallback\|React.memo"
Step 2: Remove Redundant Memoizations
// BEFORE: Manual optimization
function Component({ data }: Props) {
const processed = useMemo(() => {
return data.map(x => x * 2);
}, [data]);
const handleClick = useCallback(() => {
console.log(processed);
}, [processed]);
return <button onClick={handleClick}>{processed}</button>;
}
// AFTER: Let compiler handle it
'use memo';
function Component({ data }: Props) {
// Compiler handles memoization
const processed = data.map(x => x * 2);
const handleClick = () => {
console.log(processed);
};
return <button onClick={handleClick}>{processed}</button>;
}
Step 3: Measure Performance
Use React DevTools Profiler:
import { Profiler } from 'react';
function App() {
return (
<Profiler id="App" onRender={(id, phase, actualDuration) => {
console.log(`${id} (${phase}): ${actualDuration}ms`);
}}>
<YourApp />
</Profiler>
);
}
Advanced Patterns
Pattern 1: Selective Optimization
Disable compiler for specific components:
'use no memo'; // Opt-out directive
function AlwaysRerender({ timestamp }: Props) {
// This component intentionally re-renders often
return <div>{new Date(timestamp).toISOString()}</div>;
}
Pattern 2: Manual Override
Keep manual optimization where needed:
'use memo';
function HybridComponent({ data }: Props) {
// Compiler handles most optimizations
// But keep manual control for critical paths
const criticalComputation = useMemo(() => {
return heavyAlgorithm(data);
}, [data]);
return <div>{criticalComputation}</div>;
}
Pattern 3: Debugging Optimizations
Log what's being memoized:
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
compilationMode: 'all',
logOptimizations: true, // Log what's optimized
}],
],
};
Output:
[React Compiler] Optimized ProductList
- Memoized: sortedProducts (line 12)
- Memoized: handleSort (line 18)
- Memoized: JSX return (line 24)
Performance Comparison
Benchmark: Complex Dashboard
Manual Optimization:
const Dashboard = React.memo(({ data }) => {
const metrics = useMemo(() => calculateMetrics(data), [data]);
const charts = useMemo(() => generateCharts(data), [data]);
const summary = useMemo(() => createSummary(metrics), [metrics]);
const handleRefresh = useCallback(() => {
refreshData();
}, []);
return (
<div>
<Metrics data={metrics} />
<Charts data={charts} />
<Summary data={summary} />
<button onClick={handleRefresh}>Refresh</button>
</div>
);
});
With Compiler:
'use memo';
function Dashboard({ data }: Props) {
const metrics = calculateMetrics(data);
const charts = generateCharts(data);
const summary = createSummary(metrics);
const handleRefresh = () => {
refreshData();
};
return (
<div>
<Metrics data={metrics} />
<Charts data={charts} />
<Summary data={summary} />
<button onClick={handleRefresh}>Refresh</button>
</div>
);
}
Results:
- Code size: 40% smaller (30 lines → 18 lines)
- Re-render time: Same (15ms avg)
- Memory usage: Same (~2MB)
- Developer experience: Significantly better
Common Pitfalls
❌ Over-Relying on Compiler
'use memo';
function SlowComponent({ bigArray }: Props) {
// Compiler can't fix fundamentally slow algorithms
bigArray.forEach(item => {
for (let i = 0; i < 1000000; i++) {
// O(n²) - still slow!
heavyOperation(item, i);
}
});
return <div>Done</div>;
}
Fix: Optimize algorithm first, then let compiler optimize re-renders.
❌ Assuming All Code is Optimized
'use memo';
function Component({ data }: Props) {
// This is optimized
const filtered = data.filter(x => x.active);
// This is NOT optimized (side effect)
localStorage.setItem('cache', JSON.stringify(filtered));
return <div>{filtered.length}</div>;
}
❌ Ignoring Compiler Warnings
[React Compiler] Warning: Component "UserList" cannot be optimized
Reason: Mutates props directly (line 23)
Suggestion: Use [...props.users].sort() instead of props.users.sort()
Always fix compiler warnings - they prevent optimization.
Production Checklist
Before deploying with React Compiler:
- ✅ Run full test suite (unit + integration)
- ✅ Profile performance with React DevTools
- ✅ Check bundle size (shouldn't change significantly)
- ✅ Review compiler warnings and fix issues
- ✅ Test in production mode (
NODE_ENV=production) - ✅ Monitor re-render count in production
- ✅ Set up error tracking for runtime issues
- ✅ Gradually roll out (feature flag)
When to Keep Manual Memoization
Keep useMemo/useCallback when:
- Referential Identity Matters:
function Parent() {
// Keep useCallback if child uses this in useEffect deps
const callback = useCallback(() => {
heavyOperation();
}, []);
return <Child onLoad={callback} />;
}
function Child({ onLoad }: Props) {
useEffect(() => {
onLoad(); // Depends on stable reference
}, [onLoad]);
return <div>Child</div>;
}
- Debugging Performance:
function Component({ data }: Props) {
// Keep to measure specific optimization impact
const result = useMemo(() => {
console.time('expensive-computation');
const value = expensiveComputation(data);
console.timeEnd('expensive-computation');
return value;
}, [data]);
return <div>{result}</div>;
}
- Third-Party Library Requirements:
function Chart({ data }: Props) {
// Some libraries require stable references
const chartConfig = useMemo(() => ({
data,
options: { ... }
}), [data]);
return <ThirdPartyChart config={chartConfig} />;
}
Compiler vs Manual: Decision Tree
┌─────────────────────────────────┐
│ Do you need optimization? │
└──────────┬──────────────────────┘
│
Yes │ No → Don't optimize
▼
┌─────────────────────────────────┐
│ Is it a React component? │
└──────────┬──────────────────────┘
│
Yes │ No → Use useMemo manually
▼
┌─────────────────────────────────┐
│ Does it have side effects? │
└──────────┬──────────────────────┘
│
No │ Yes → Move to useEffect
▼
┌─────────────────────────────────┐
│ Use React Compiler! │
│ Add 'use memo' directive │
└─────────────────────────────────┘
Real-World Adoption
Case Study: E-Commerce Dashboard
Before Compiler:
- 437 manual
useMemocalls - 283 manual
useCallbackcalls - 156
React.memocomponents - Average re-render: 45ms
After Compiler:
- Removed 91% of manual memoization
- Kept 39 critical manual optimizations
- Average re-render: 42ms (3ms faster!)
- 2,100 lines of code removed
Developer Feedback:
- "Stopped thinking about memoization during development"
- "Code reviews focus on logic, not performance"
- "Onboarding faster - juniors don't need to learn memoization patterns"
Browser Support
The React Compiler generates standard JavaScript:
- ✅ All modern browsers (Chrome, Firefox, Safari, Edge)
- ✅ IE 11 (with polyfills)
- ✅ React Native
- ✅ Server-side rendering
Debugging Tips
1. Visualize Optimizations
'use memo';
function Component({ data }: Props) {
console.log('Component rendered'); // Track renders
const processed = data.map(x => x * 2);
console.log('Processed recalculated'); // Should not log if memoized
return <div>{processed}</div>;
}
2. Compare Builds
# Build without compiler
DISABLE_COMPILER=true npm run build
# Build with compiler
npm run build
# Compare bundle sizes
du -h .next/static/chunks/*.js
3. Check Compiler Output
# Enable verbose logging
REACT_COMPILER_LOG=verbose npm run build
Summary
The React Compiler represents a paradigm shift in React performance optimization:
Key Takeaways:
- Automatic memoization - No more manual
useMemo/useCallback - Build-time optimization - Zero runtime overhead
- Gradual adoption - Opt-in per component or globally
- Production-ready - Used by Meta in production
- Developer experience - Write cleaner, simpler code
Migration Strategy:
- Start with
annotationmode on new components - Measure performance impact
- Gradually remove manual memoizations
- Switch to
allmode when confident
When to Use:
- ✅ All new React 19 projects
- ✅ Existing projects with heavy memoization
- ✅ Teams without deep performance expertise
- ✅ Code that changes frequently
When to Skip:
- ❌ React < 19 (not supported)
- ❌ Projects with complex custom memoization logic
- ❌ Need for fine-grained control over every optimization
Frequently Asked Questions
Does the React Compiler work with React 18?
No, the React Compiler requires React 19+. However, you can prepare your codebase by removing unnecessary memoization and ensuring components follow React's rules (no mutations, side effects only in useEffect, etc.).
Will the compiler make my app slower?
No. The compiler only adds optimizations—it never makes code slower. In worst case, it simply won't optimize certain patterns, falling back to normal React behavior.
Can I use the compiler with class components?
The compiler is designed for functional components. Class components will continue to work but won't be auto-optimized. This is another reason to migrate to functional components.
How do I know if the compiler is working?
Check your build output for "React Compiler" messages showing how many components were optimized. You can also use React DevTools Profiler to verify reduced re-renders.
What's the difference between the compiler and React Forget?
They're the same thing! "React Forget" was the codename during development. The official name is now "React Compiler."
Do I still need to learn useMemo and useCallback?
Yes, for understanding how optimization works and for React 18 codebases. But in React 19+ with the compiler, you'll rarely need to write them manually.
Can the compiler optimize third-party libraries?
Only if they're compiled with the React Compiler. Libraries distributed as pre-built bundles won't be optimized. This is why library authors should adopt the compiler.
What happens to my existing useMemo calls?
They still work! The compiler detects manual memoization and leaves it alone. You can gradually remove manual optimizations as you verify the compiler handles them.
The React Compiler isn't just about performance—it's about letting you focus on building features instead of micro-managing optimizations. Enable it, trust it, and spend your time on what matters: creating great user experiences.