Skip to content

React Compiler and Automatic Memoization in Practice

The React Compiler auto-inserts memoization at build time. How to install it, verify it works, and understand the cases it skips.

· · 9 min read

Updated: July 29, 2026

Code editor showing React component optimization with memoization patterns

Quick Take

The React Compiler auto-inserts useMemo and useCallback at build time, eliminating manual memoization for most components. It silently skips components that violate Rules of Hooks, learn how to verify it's actually optimizing your code before shipping.

Quick take: The React Compiler (babel-plugin-react-compiler) statically analyzes your components at build time and inserts memoization automatically, the equivalent of correctly written React.memo, useMemo, and useCallback with accurate dependency arrays, across your entire codebase. This auto-memoization skips components that violate React's rules and tells you why. Works on React 17+. React DevTools shows exactly which components it covered, with practical examples.

The React Compiler is a build-time Babel plugin that reads your component code, figures out which values can be safely cached, and inserts the memoization for you. No component rewrites. No manual dependency arrays. You configure it once and it runs on every build.

On a typical production Next.js app with around 200 components, teams report the compiler optimizing roughly 65% of them automatically. Dozens of manual useCallback and useMemo calls then turn out to be removable, not because the compiler requires it, but because they're redundant once its coverage is verified through DevTools. That's the right rollout pace: enable, verify, clean up incrementally.

What Does the React Compiler Actually Do?

It's a static analysis pass. The compiler reads your component code, determines which computations and returned functions can be memoized without changing behavior, and rewrites the output to include those memo calls with correct dependency arrays.

// What you write
function ProductCard({ product, onAddToCart }) {
  const price = formatPrice(product.price, product.currency);
  const handleClick = () => onAddToCart(product.id);

  return (
    <div onClick={handleClick}>
      <h3>{product.name}</h3>
      <p>{price}</p>
    </div>
  );
}

// What the compiler generates (roughly)
function ProductCard({ product, onAddToCart }) {
  const price = useMemo(
    () => formatPrice(product.price, product.currency),
    [product.price, product.currency]
  );
  const handleClick = useCallback(
    () => onAddToCart(product.id),
    [onAddToCart, product.id]
  );

  return (
    <div onClick={handleClick}>
      <h3>{product.name}</h3>
      <p>{price}</p>
    </div>
  );
}

The dependency arrays are the part that's easy to get wrong by hand, and wrong dep arrays are where subtle stale-closure bugs live. The compiler gets them right because it has full visibility into what each function captures.

A close-up of interlocking brass clockwork gears
Photo by Curated Lifestyle on Unsplash

How Do You Install the React Compiler?

npm install -D babel-plugin-react-compiler eslint-plugin-react-compiler

Next.js 15:

// next.config.ts
const nextConfig = {
  experimental: {
    reactCompiler: true,
  },
};

export default nextConfig;

Vite:

// vite.config.ts
import react from '@vitejs/plugin-react';

export default {
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-react-compiler'],
      },
    }),
  ],
};

Plain Babel:

{
  "plugins": ["babel-plugin-react-compiler"]
}

Add the ESLint plugin immediately, it catches patterns the compiler will refuse to optimize before you hit a runtime warning. Static analysis is the technique the compiler and its ESLint companion both use, examining your source code without running it to determine which patterns are safe to memoize. Here's the order that avoids surprises:

  1. Install babel-plugin-react-compiler and eslint-plugin-react-compiler together
  2. Add the ESLint rule as an error, not a warning, so violations block CI
  3. Fix every violation the linter reports before enabling the compiler in your build
  4. Enable the compiler in your framework config and rebuild
  5. Check React DevTools for Memo badges to confirm actual coverage
{
  "plugins": ["eslint-plugin-react-compiler"],
  "rules": {
    "react-compiler/react-compiler": "error"
  }
}

Don't skip the ESLint step. It surfaces the components you'll need to clean up before the compiler can touch them.

How Do You Verify It's Working?

React DevTools 5.x shows a "Memo" badge next to components the compiler has optimized. Open the Components panel after a development build, you'll see coverage immediately without any extra tooling. According to the React Compiler documentation, this badge is the recommended way to confirm coverage rather than reading generated build output, since the compiled code itself is intentionally hard to eyeball once minified. On a mid-size app, expect the first coverage pass to land somewhere between 60 and 80 percent, with the remainder split across components the ESLint rule already flagged and a handful the linter missed because the violation only shows up at runtime.

You can also enable verbose output during development:

{
  "plugins": [
    ["babel-plugin-react-compiler", {
      "logger": "console"
    }]
  ]
}

This logs a line per optimized component on startup. Noisy, but useful for a first-pass audit of what's covered. The React Compiler Playground lets you paste a component and see the compiled output before enabling it in your project, useful for understanding what the compiler will do to a specific pattern.

A black-and-white close-up of a large gear meshing with a chain drive
Photo by Mike Hindle on Unsplash

How Does Installation Differ by Framework?

FrameworkSetupConfig location
Next.js 15Native supportexperimental.reactCompiler in next.config.ts
ViteVia @vitejs/plugin-reactbabel.plugins array in vite.config.ts
Plain BabelDirect plugin.babelrc plugins array

What Will the Compiler Skip?

The compiler skips components that violate React's rules. Common patterns that block optimization:

Mutating props or state during render:

// Skipped, mutates the array during render
function BadList({ items }) {
  items.push({ id: 'temp' }); // compiler bails on the whole component
  return <ul>{items.map(item => <li key={item.id}>{item.text}</li>)}</ul>;
}

Conditional hook calls:

// Skipped, hook inside a condition
function ConditionalHook({ isLoggedIn, userId }) {
  if (isLoggedIn) {
    const data = useUserData(userId); // whole component is skipped
  }
}

Reading mutable external state without a subscription:

// Skipped, reads window.someGlobal which changes outside React's control
function GlobalReader() {
  return <div>{window.someGlobal}</div>;
}

The compiler doesn't crash on these, it emits a warning and moves on to the next component. The ESLint plugin catches most of them at lint time before they reach the compiler.

Should You Remove Existing useMemo and useCallback?

Not all at once, and not without looking first. The compiler recognizes correct manual memoization and doesn't double-wrap it. But manual memoization written with incorrect dep arrays can interfere with the compiler's analysis.

The sane rollout: enable the compiler, check DevTools for the "Memo" badges, then remove manual calls component by component where the compiler already has coverage. You'll occasionally find a manual dep array that was wrong, the compiler's correct version handles it, and you can see the difference in behavior.

For components the compiler skipped, fix the ESLint violations first. The fix is usually straightforward: move mutations out of render, un-conditionalize hook calls. Once the component is clean, the compiler handles the rest.

Don't try to fix everything at once. One pattern that keeps proving itself: run the compiler on CI as an --emit-only check, use the DevTools report to track coverage over several sprints, and remove manual memoization as you go. That incremental approach works better than a single large PR.

Where Does the Compiler Stand Now?

The React Compiler hit its 1.0 release in October 2025, so it's no longer the RC-flagged tool this guide originally described. That matters for anyone who held off, "stable" here isn't marketing language, it's the same build Meta runs across large parts of facebook.com and internally reports double-digit gains from. Public numbers floating around since the 1.0 announcement put initial page load and navigation improvements around 12% on optimized apps, with individual interactions in some cases over twice as fast, without any extra memory cost. Teams outside Meta keep reporting smaller but real versions of that, with interaction latency dropping noticeably once compiler coverage passes the 80% mark.

Practically, this changes the calculus for new projects. Eighteen months ago the sane advice was "try it on a low-risk app first." Now, for any React codebase started from scratch, skipping the compiler is closer to skipping TypeScript, technically fine, but you're leaving free correctness and performance on the table for no real reason. If you're still running an app without it, the migration guide above hasn't changed, install the plugin, run the ESLint rule, verify coverage in DevTools, clean up incrementally.

Summary

The React Compiler (babel-plugin-react-compiler) automates memoization at build time, correctly placed useMemo, useCallback, and React.memo across your codebase without manual dep arrays. Install it, add the ESLint plugin, enable it in your framework config, and check React DevTools to see coverage. It skips components that violate React's rules and warns you exactly which ones. Remove manual memoization incrementally after verifying DevTools coverage. The compiler works on React 17+, you don't need to be on React 19 first. For a full picture of what React 19 itself added, see our React 19 new features guide.

Frequently Asked Questions

Does the React Compiler ship with React 19?
No. The React Compiler is a separate Babel plugin, babel-plugin-react-compiler. You install it independently and configure it in your Babel or framework config. It works with React 17 and 18 as well, not just React 19.
Should I remove useMemo and useCallback after enabling the compiler?
Not all at once. The compiler leaves correct manual memoization in place and doesn't double-wrap it. Remove manual calls incrementally, component by component, after verifying through React DevTools that the compiler is already handling those components. Don't mass-delete, inspect first.
What code will the React Compiler not optimize?
The compiler skips components that violate React's rules: mutations of props or state during render, conditional hook calls, non-idempotent render functions, and reading mutable external state without a subscription. It warns you and skips those components rather than produce incorrect output.
How do I verify the React Compiler is working?
React DevTools 5.x shows a 'Memo' badge next to compiler-optimized components in the Components panel. You can also enable verbose logging by setting logger: console in babel-plugin-react-compiler options during development.
Does the React Compiler work with Next.js?
Yes. Next.js 15 supports it natively via the experimental.reactCompiler flag in next.config.ts. For Vite, use the babel option in @vitejs/plugin-react. For plain Babel, add babel-plugin-react-compiler directly to your .babelrc plugins array.