React Compiler
The React Compiler is a build-time tool that automatically optimizes React components and hooks. It analyzes your code and inserts fine-grained memoization, the work developers used to do by hand with React.memo, useMemo, and useCallback. The goal: apps that re-render only what actually changed, without cluttering code with memoization hooks and dependency arrays.
The compiler reached a stable 1.0 release in 2025 after being used in production at Meta. It works with React 17, 18, and 19, and integrates with Babel-based toolchains, Vite, Next.js, and Expo. It relies on your code following the Rules of React; where it can't prove a component is safe, it simply skips optimizing it.
TL;DR
- The compiler auto-memoizes components, hooks, JSX, and computed values at build time.
- Most manual
useMemo,useCallback, andReact.memobecome unnecessary. - It requires code that follows the Rules of React: pure render, immutable props and state, and hooks called correctly.
- Enable it with
babel-plugin-react-compiler, or framework options (Next.jsreactCompiler, Expo, Vite via the React plugin's Babel config). - Adopt incrementally with
compilationMode: "annotation"and the"use memo"directive, or opt components out with"use no memo". - Use the ESLint plugin (
eslint-plugin-react-hookscompiler rules) to find code the compiler can't optimize.
Quick Example
Without the compiler, preventing wasted renders means manual memoization:
With the compiler, you write the plain version:
Enabling it in a Vite project:
And in Next.js:
Core Concepts
What the Compiler Does
The compiler transforms each component and hook into a version that caches intermediate results in slots keyed by their inputs. When a component re-renders:
- Values whose inputs haven't changed are reused.
- JSX elements whose props haven't changed are reused, so React skips re-rendering those children.
- Callbacks keep stable identities unless what they capture changes.
It memoizes at a finer granularity than typical hand-written code, including individual JSX subtrees and values inside conditionals, which is awkward or impossible to do manually because of the Rules of Hooks.
The Rules of React
The compiler's optimizations are only valid if components behave like pure functions of props, state, and context:
- Render is pure: no side effects during render (mutating external variables, logging with side effects, reading refs to decide output).
- Props and state are immutable: never mutate them; create new objects instead.
- Hooks follow the Rules of Hooks: top level, consistent order, only in components and hooks.
- Values passed to hooks or JSX aren't mutated afterwards.
When the compiler detects a violation it can't reason about, it bails out of optimizing that component and leaves it unchanged, rather than producing broken code. Code that breaks the rules subtly (mutations the compiler can't see) can still misbehave, which is why the lint rules matter.
Linting
The compiler's diagnostics ship as rules in eslint-plugin-react-hooks (the recommended config in recent versions). They flag rule violations such as mutations during render, reading ref.current during render, and setState in render, even if you haven't enabled the compiler yet. Fixing these makes code more correct regardless.
Incremental Adoption
compilationMode: "annotation"compiles only functions marked with the"use memo"directive. It's useful for piloting on a few components."use no memo"opts a specific component or hook out, which is a temporary escape hatch while you fix an issue.sourcesfilters restrict compilation to specific directories.
Many teams enable it for the whole app once lint errors are cleared, then use "use no memo" for rare exceptions.
What Happens to useMemo and useCallback?
- Existing manual memoization keeps working; the compiler composes with it.
- New code can usually omit it. Keep manual memoization where you need a guaranteed stable identity for correctness, for example a value used as an effect dependency where re-running the effect would be wrong, or integration with non-React libraries that compare identities.
React.memowrappers become largely redundant, since compiled parents already avoid re-creating unchanged child elements.
Remove manual memoization gradually and verify with the Profiler, rather than stripping it all at once. See React performance.
Best Practices
Fix Lint Errors First
Enable the compiler-related ESLint rules before turning on the compiler. They surface code patterns that would cause bailouts or bugs, and they're worth fixing anyway.
Verify With React DevTools
Components optimized by the compiler show a "Memo ✨" badge in React DevTools. Use it, together with the Profiler, to confirm important components are compiled and re-rendering less.
Keep Components Pure
Write components as pure functions of props and state, put side effects in effects or event handlers, and treat data as immutable. That's good React regardless, and it's what lets the compiler optimize everything.
Test Behavior, Not Render Counts
Since the compiler changes when components re-render, tests that assert exact render counts or rely on side effects during render can break. Test user-visible behavior with tools like React Testing Library.
Common Mistakes
Mutating Props or State
Reading Refs During Render
if (renderCount.current > 3) inside render makes output depend on a mutable ref the compiler doesn't track. Refs are for event handlers and effects; use state for values that affect rendering.
Assuming the Compiler Fixes Everything
The compiler reduces unnecessary re-renders. It doesn't virtualize long lists, split bundles, fix data-fetching waterfalls, or speed up genuinely expensive computations. Those still need targeted solutions.
FAQ
Do I need React 19 to use the compiler?
No. It targets React 19 by default and supports React 17 and 18 with the react-compiler-runtime package and the target option. React 19 includes the needed runtime natively.
Will the compiler break my app?
It's designed to be conservative: code it can't prove safe is left unoptimized. Problems usually come from code that already violates the Rules of React in ways the compiler couldn't detect. Enable the ESLint rules, adopt incrementally, and run your test suite.
Should I remove all my useMemo and useCallback calls?
Not immediately. They're harmless alongside the compiler. Remove them gradually when touching code, keeping any that exist for correctness (stable identity for effect dependencies or external libraries) rather than performance.
Does the compiler work with other frameworks' build tools?
Yes. It's a Babel plugin, with integrations for Vite, Next.js, Expo/React Native (Metro), webpack/Rspack, and others. SWC-based pipelines like Next.js enable it through config, using the Babel plugin where needed.
Related Topics
- React — The library overview
- React Performance — What the compiler does and doesn't solve
- React Hooks — useMemo, useCallback, and the Rules of Hooks
- Next.js — Built-in compiler support
- Vite — Configuring the Babel plugin
- Build Tools — Compilers and transforms in the frontend toolchain