React — React Compiler
The React Compiler (formerly React Forget) is an ahead-of-time compiler that automatically optimizes React components. It analyzes your code and inserts memoization where beneficial, eliminating the need for manual useMemo, useCallback, and React.memo calls.
The compiler works at build time, transforming your source code into optimized versions that skip unnecessary re-renders. It is a Babel/SWC plugin that runs during compilation — no runtime changes are needed.
| 1 | # Install the React Compiler (React 19+) |
| 2 | npm install babel-plugin-react-compiler |
| 3 | |
| 4 | # Or with the official React Compiler package |
| 5 | npm install react-compiler |
| 6 | |
| 7 | # For Vite projects — add to vite.config.ts |
| 8 | npm install babel-plugin-react-compiler vite-plugin-react-compiler |
| 9 | |
| 10 | # For Next.js — add to next.config.js |
| 11 | npm install babel-plugin-react-compiler |
| 1 | { |
| 2 | "compilerOptions": { |
| 3 | "target": "ES2022", |
| 4 | "jsx": "react-jsx", |
| 5 | "module": "NodeNext", |
| 6 | "moduleResolution": "NodeNext" |
| 7 | } |
| 8 | } |
| 1 | // Vite configuration — vite.config.ts |
| 2 | import { defineConfig } from "vite"; |
| 3 | import react from "@vitejs/plugin-react"; |
| 4 | |
| 5 | export default defineConfig({ |
| 6 | plugins: [ |
| 7 | react({ |
| 8 | babel: { |
| 9 | plugins: [ |
| 10 | ["babel-plugin-react-compiler", { |
| 11 | target: "19", // React 19 |
| 12 | }], |
| 13 | ], |
| 14 | }, |
| 15 | }), |
| 16 | ], |
| 17 | }); |
| 18 | |
| 19 | // Next.js configuration — next.config.js |
| 20 | /** @type {import('next').NextConfig} */ |
| 21 | const nextConfig = { |
| 22 | experimental: { |
| 23 | reactCompiler: true, |
| 24 | }, |
| 25 | }; |
| 26 | |
| 27 | module.exports = nextConfig; |
info
| 1 | // BEFORE — manual memoization (React 18 style) |
| 2 | import { useMemo, useCallback, memo } from "react"; |
| 3 | |
| 4 | function ProductList({ products, onSelect }: Props) { |
| 5 | const sortedProducts = useMemo( |
| 6 | () => [...products].sort((a, b) => a.price - b.price), |
| 7 | [products] |
| 8 | ); |
| 9 | |
| 10 | const handleClick = useCallback( |
| 11 | (id: string) => { |
| 12 | onSelect(id); |
| 13 | }, |
| 14 | [onSelect] |
| 15 | ); |
| 16 | |
| 17 | return ( |
| 18 | <ul> |
| 19 | {sortedProducts.map((product) => ( |
| 20 | <ProductItem |
| 21 | key={product.id} |
| 22 | product={product} |
| 23 | onClick={handleClick} |
| 24 | /> |
| 25 | ))} |
| 26 | </ul> |
| 27 | ); |
| 28 | } |
| 29 | |
| 30 | const ProductItem = memo(function ProductItem({ product, onClick }: Props) { |
| 31 | return ( |
| 32 | <li onClick={() => onClick(product.id)}> |
| 33 | {product.name} — ${product.price} |
| 34 | </li> |
| 35 | ); |
| 36 | }); |
| 37 | |
| 38 | // AFTER — React Compiler handles it automatically |
| 39 | function ProductList({ products, onSelect }: Props) { |
| 40 | // Compiler automatically memoizes this computation |
| 41 | const sortedProducts = [...products].sort((a, b) => a.price - b.price); |
| 42 | |
| 43 | // Compiler automatically memoizes this callback |
| 44 | const handleClick = (id: string) => { |
| 45 | onSelect(id); |
| 46 | }; |
| 47 | |
| 48 | return ( |
| 49 | <ul> |
| 50 | {sortedProducts.map((product) => ( |
| 51 | <ProductItem |
| 52 | key={product.id} |
| 53 | product={product} |
| 54 | onClick={handleClick} |
| 55 | /> |
| 56 | ))} |
| 57 | </ul> |
| 58 | ); |
| 59 | } |
| 60 | |
| 61 | // No memo() needed — the compiler tracks which props change |
| 62 | function ProductItem({ product, onClick }: Props) { |
| 63 | return ( |
| 64 | <li onClick={() => onClick(product.id)}> |
| 65 | {product.name} — ${product.price} |
| 66 | </li> |
| 67 | ); |
| 68 | } |
best practice
| 1 | // The compiler statically analyzes your code to determine dependencies |
| 2 | |
| 3 | // BEFORE — manual dependency array (error-prone) |
| 4 | function UserProfile({ userId }: { userId: string }) { |
| 5 | const [user, setUser] = useState(null); |
| 6 | |
| 7 | useEffect(() => { |
| 8 | fetchUser(userId).then(setUser); |
| 9 | }, [userId]); // Must remember to include userId |
| 10 | |
| 11 | const fullName = useMemo( |
| 12 | () => user ? `${user.firstName} ${user.lastName}` : "", |
| 13 | [user] // Must remember to include user |
| 14 | ); |
| 15 | |
| 16 | const handleUpdate = useCallback( |
| 17 | (data: Partial<User>) => { |
| 18 | setUser((prev) => ({ ...prev, ...data })); |
| 19 | }, |
| 20 | [] // Empty — no dependencies needed |
| 21 | ); |
| 22 | } |
| 23 | |
| 24 | // AFTER — compiler tracks dependencies automatically |
| 25 | function UserProfile({ userId }: { userId: string }) { |
| 26 | const [user, setUser] = useState(null); |
| 27 | |
| 28 | useEffect(() => { |
| 29 | fetchUser(userId).then(setUser); |
| 30 | }, [userId]); // Compiler verifies this is correct |
| 31 | |
| 32 | const fullName = user |
| 33 | ? `${user.firstName} ${user.lastName}` |
| 34 | : ""; |
| 35 | // Compiler memoizes based on actual user access |
| 36 | |
| 37 | const handleUpdate = (data: Partial<User>) => { |
| 38 | setUser((prev) => ({ ...prev, ...data })); |
| 39 | }; |
| 40 | // Compiler detects no external dependencies — runs once |
| 41 | } |
| 42 | |
| 43 | // The compiler understands: |
| 44 | // - Variable reads (local and prop references) |
| 45 | // - Object property access |
| 46 | // - Array methods |
| 47 | // - Closures |
| 48 | // - React hooks and their dependency rules |
| 49 | |
| 50 | // Example: compiler tracks which properties are accessed |
| 51 | function ProductCard({ product }: { product: Product }) { |
| 52 | // Only depends on product.name and product.price |
| 53 | // If product.description changes, this doesn't re-render |
| 54 | return ( |
| 55 | <div> |
| 56 | <h3>{product.name}</h3> |
| 57 | <p>${product.price}</p> |
| 58 | </div> |
| 59 | ); |
| 60 | } |
| 1 | // The compiler provides directives for fine-grained control |
| 2 | |
| 3 | // "use no memo" — opt out of compiler optimization for a component |
| 4 | function ExperimentalComponent() { |
| 5 | "use no memo"; |
| 6 | |
| 7 | // This component will NOT be optimized by the compiler |
| 8 | // Useful for components with intentional re-render patterns |
| 9 | return <div>Not memoized</div>; |
| 10 | } |
| 11 | |
| 12 | // "use no memo" on a function expression |
| 13 | const ManualComponent = function () { |
| 14 | "use no memo"; |
| 15 | |
| 16 | const [count, setCount] = useState(0); |
| 17 | return <button onClick={() => setCount((c) => c + 1)}>{count}</button>; |
| 18 | }; |
| 19 | |
| 20 | // Compiler handles these patterns automatically: |
| 21 | // 1. Inline object literals — memoized when passed as props |
| 22 | function Parent() { |
| 23 | // This object is memoized — doesn't cause child re-renders |
| 24 | return <Child style={{ color: "red", fontSize: 16 }} />; |
| 25 | } |
| 26 | |
| 27 | // 2. Inline function expressions — memoized for callbacks |
| 28 | function Parent() { |
| 29 | // This callback is memoized — stable reference |
| 30 | return <Child onClick={() => console.log("clicked")} />; |
| 31 | } |
| 32 | |
| 33 | // 3. Conditional hooks — the compiler validates hook rules |
| 34 | function Component({ showDetails }: { showDetails: boolean }) { |
| 35 | const [name, setName] = useState(""); |
| 36 | // The compiler ensures hooks are always called in the same order |
| 37 | |
| 38 | if (showDetails) { |
| 39 | const [details, setDetails] = useState(null); |
| 40 | return <Details data={details} />; |
| 41 | } |
| 42 | |
| 43 | return <p>{name}</p>; |
| 44 | } |
| 45 | |
| 46 | // Disabling compiler for specific files |
| 47 | // Add // @react-compiler-ignore at the top of a file |
| 48 | // Or in your compiler config: |
| 49 | // { "sources": { "path/to/file.tsx": { "compilationMode": "skip" } } } |
warning
| 1 | // The compiler is fully backward compatible |
| 2 | |
| 3 | // All these patterns continue to work: |
| 4 | import { useState, useEffect, useMemo, useCallback, memo } from "react"; |
| 5 | |
| 6 | // Manual memoization — still works, but unnecessary |
| 7 | const MemoizedComponent = memo(function MyComponent({ data }: Props) { |
| 8 | const processed = useMemo(() => data.map(transform), [data]); |
| 9 | const handleClick = useCallback(() => {}, []); |
| 10 | return <div onClick={handleClick}>{processed}</div>; |
| 11 | }); |
| 12 | |
| 13 | // Migration steps: |
| 14 | // 1. Add the compiler plugin to your build config |
| 15 | // 2. Run your test suite — everything should pass |
| 16 | // 3. Optionally remove manual memoization calls |
| 17 | // 4. Verify bundle size decreased |
| 18 | |
| 19 | // What the compiler handles: |
| 20 | // ✅ useState, useEffect, useRef, useContext — standard hooks |
| 21 | // ✅ useMemo, useCallback — can be removed after enabling |
| 22 | // ✅ React.memo() — can be removed after enabling |
| 23 | // ✅ Custom hooks — compiler analyzes hook internals |
| 24 | // ✅ Conditional rendering — tracks dependencies per branch |
| 25 | // ✅ List rendering — memoizes individual list items |
| 26 | // ✅ Context consumers — avoids unnecessary re-renders |
| 27 | |
| 28 | // What the compiler does NOT do: |
| 29 | // ❌ Optimize effects (useEffect, useLayoutEffect) |
| 30 | // ❌ Change runtime behavior — same React semantics |
| 31 | // ❌ Optimize external library calls |
| 32 | // ❌ Fix broken code — you must follow React rules |
| 33 | |
| 34 | // Framework support: |
| 35 | // - Next.js (experimental.reactCompiler: true in next.config.js) |
| 36 | // - Vite (via babel-plugin-react-compiler) |
| 37 | // - Parcel (via babel-plugin-react-compiler) |
| 38 | // - Webpack (via babel-loader with the plugin) |
| 39 | // - Expo (via babel config) |
1. Enable the compiler first, then progressively remove manual useMemo, useCallback, and memo() calls.
2. Keep following the Rules of Hooks — the compiler optimizes within these rules, it does not replace them.
3. Use "use no memo" only for components with intentional re-render patterns (like animations or real-time data).
4. Test your application after enabling the compiler — the optimization should be transparent but always verify.
5. The compiler works best with React 19+ and modern bundlers (Vite, Next.js, etc.).
6. Don't rely on compiler internals — write clean React code and let the compiler optimize it.