Imagine that on your spaceship, instead of manually configuring every optimization system, you have an intelligent autopilot that automatically detects and optimizes all redundant operations. That is exactly what the React Compiler (also known as React Forget) does -- it automatically analyzes your code and adds memoization where it is needed.
Until now, performance optimization in React required manual use of
React.memo, useMemo, and useCallback. It is like manually configuring every system on a spaceship -- time-consuming, error-prone, and easy to overlook:1// BEFORE React Compiler - manual memoization
2import { useState, useMemo, useCallback, memo } from 'react';
3
4const MissionCard = memo(function MissionCard({ mission, onSelect }) {
5 return (
6 <div onClick={() => onSelect(mission.id)}>
7 <h3>{mission.name}</h3>
8 <p>Status: {mission.status}</p>
9 </div>
10 );
11});
12
13function MissionDashboard({ missions }) {
14 const [filter, setFilter] = useState('all');
15 const [selectedId, setSelectedId] = useState(null);
16
17 // You must remember to use useMemo...
18 const filteredMissions = useMemo(
19 () => missions.filter(m => filter === 'all' || m.status === filter),
20 [missions, filter]
21 );
22
23 // ...and useCallback for every function passed as a prop
24 const handleSelect = useCallback((id) => {
25 setSelectedId(id);
26 }, []);
27
28 return (
29 <div>
30 {filteredMissions.map(m => (
31 <MissionCard key={m.id} mission={m} onSelect={handleSelect} />
32 ))}
33 </div>
34 );
35}Problems with manual memoization:
useCallback and React.memo won't workuseMemo/useCallback = bugReact Compiler is a build-time tool that automatically analyzes React components and adds appropriate memoization. It works at compilation time -- your source code remains clean:
1// AFTER React Compiler - write plain, clean code
2function MissionDashboard({ missions }) {
3 const [filter, setFilter] = useState('all');
4 const [selectedId, setSelectedId] = useState(null);
5
6 // Compiler AUTOMATICALLY memoizes this computation
7 const filteredMissions = missions.filter(
8 m => filter === 'all' || m.status === filter
9 );
10
11 // Compiler AUTOMATICALLY stabilizes these functions
12 const handleSelect = (id) => {
13 setSelectedId(id);
14 };
15
16 return (
17 <div>
18 {filteredMissions.map(m => (
19 <MissionCard key={m.id} mission={m} onSelect={handleSelect} />
20 ))}
21 </div>
22 );
23}
24
25// You don't need React.memo - Compiler handles it!
26function MissionCard({ mission, onSelect }) {
27 return (
28 <div onClick={() => onSelect(mission.id)}>
29 <h3>{mission.name}</h3>
30 <p>Status: {mission.status}</p>
31 </div>
32 );
33}The Compiler analyzes your code during the build (e.g., with a Babel plugin) and generates an optimized version:
1// Your source code (what you write):
2function StarList({ stars, highlight }) {
3 const visible = stars.filter(s => s.magnitude < 6);
4 const style = { color: highlight ? '#ffd700' : '#ccc' };
5
6 return (
7 <ul style={style}>
8 {visible.map(s => <li key={s.id}>{s.name}</li>)}
9 </ul>
10 );
11}
12
13// What the Compiler generates (simplified example):
14function StarList_compiled({ stars, highlight }) {
15 // Compiler adds cache for filtering
16 const visible = useMemo(
17 () => stars.filter(s => s.magnitude < 6),
18 [stars]
19 );
20
21 // Compiler caches the style object
22 const style = useMemo(
23 () => ({ color: highlight ? '#ffd700' : '#ccc' }),
24 [highlight]
25 );
26
27 return (
28 <ul style={style}>
29 {visible.map(s => <li key={s.id}>{s.name}</li>)}
30 </ul>
31 );
32}The Compiler does this intelligently -- it does not memoize everything, only those parts that can actually repeat with the same data.
1// babel.config.js
2module.exports = {
3 plugins: [
4 ['babel-plugin-react-compiler', {
5 compilationMode: 'infer',
6 panicThreshold: 'NONE',
7 }],
8 ],
9};1// next.config.js
2module.exports = {
3 experimental: {
4 reactCompiler: true,
5 },
6};infer (default) - Compiler decides which components to optimizeannotation - optimizes only components marked with the 'use memo' directiveall - optimizes all components (most aggressive)React Compiler works correctly ONLY if your code follows the "Rules of React":
1// GOOD - pure function, no side effects in render
2function GoodComponent({ data }) {
3 const sorted = [...data].sort((a, b) => a.name.localeCompare(b.name));
4 return <ul>{sorted.map(d => <li key={d.id}>{d.name}</li>)}</ul>;
5}
6
7// BAD - mutating props (Compiler may generate incorrect code!)
8function BadComponent({ data }) {
9 data.sort((a, b) => a.name.localeCompare(b.name)); // Mutation!
10 return <ul>{data.map(d => <li key={d.id}>{d.name}</li>)}</ul>;
11}
12
13// BAD - side effect in render
14function BadComponent2({ userId }) {
15 // Compiler will assume this is pure and may skip it!
16 analytics.track('render', userId);
17 return <div>User: {userId}</div>;
18}Key rules:
useEffect, event handlers, or callbacksReact Compiler requires following the rules. The ESLint plugin helps enforce them:
1// .eslintrc.js
2module.exports = {
3 plugins: ['react-compiler'],
4 rules: {
5 'react-compiler/react-compiler': 'error',
6 },
7};If you have an existing project with manual memoization, you can gradually migrate. Existing
React.memo and useMemo will not conflict with the Compiler -- they simply become an additional, unnecessary layer.React Compiler is the future of React optimization:
useMemo, useCallback, React.memo for youRemember: React Compiler is like an autopilot on a spaceship -- it does its job perfectly, but requires the ship to be properly built.