Skip to content

React Compiler — Practical Guide to Auto-Memoization

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

· · 6 min read
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.

I enabled it on a production Next.js app with around 200 components. The compiler optimized roughly 65% of them automatically. We removed about 40 manual useCallback and useMemo calls afterward, not because the compiler required it, but because they were now redundant and the compiler's coverage was 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. For a refresher on why dep arrays matter, see React hooks best practices. This matters even more when components drive animations: unnecessary re-renders caused by unstable function references can break CSS transition timing and cause GSAP timelines to restart mid-sequence, a problem the CSS vs GSAP animation guide on Art of Styleframe covers in the context of scroll-linked and staggered animations.

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:

{
  "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.

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

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.

My approach: 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. A team I consulted for ran the compiler on CI as an --emit-only check, used the DevTools report to track coverage over several sprints, and removed manual memoization as they went. That incremental approach worked better than a single large PR.

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.