React Compiler Not Memoizing Your Component? Fix It (2026)
July 13, 2026

React Compiler skips ("bails out on") a component when your code might violate the Rules of React, silently leaving it unmemoized rather than risk breaking your app. Check the "Memo ✨" badge in React DevTools, then use the Playground to find the exact cause.12
TL;DR
React Compiler 1.0 shipped stable on October 7, 2025, and Next.js 16 promoted reactCompiler from an experimental flag to a stable top-level config option — but it isn't on by default in either React or Next.js.34 The compiler doesn't throw an error when it skips ("bails out on") a component; it just doesn't optimize it, and unless you're watching for the "Memo ✨" badge in React DevTools, you won't notice.1 This post walks through exactly how to detect a bailout, what the official docs say actually causes one, how to isolate it with the "use no memo" directive and the React Compiler Playground, and how to configure the compiler correctly in Next.js 16 and Vite 6.
What you'll learn
- How to tell whether React Compiler actually optimized a specific component
- What a compiler "bailout" is and the official patterns that trigger one
- The exact step-by-step workflow React's own docs recommend for debugging a bailout
- Whether you still need
useMemo,useCallback, andReact.memoonce the compiler is enabled - How to migrate from the old
eslint-plugin-react-compilerpackage to the current setup - How to enable the compiler correctly in Next.js 16 and in Vite 6+
- How to use the
"use no memo"and"use memo"directives to control compilation per-component - Whether React Compiler is actually stable enough to ship to production in 2026
Why isn't React Compiler memoizing my component?
Because your component contains a pattern the compiler can't statically prove is safe to optimize, so it "bails out" and leaves that component exactly as you wrote it — no error, no warning in the console, just silently unmemoized code.2 React's own debugging guide is explicit that compiler bailouts are a safety feature, not a bug: "When it encounters code that might break these rules, it safely skips optimization rather than risk changing your app's behavior."2 The two broad categories of bailout-triggering code, per React's docs, are components that rely on memoization for correctness rather than performance — for example, effects that depend on an object or array keeping the exact same reference across renders — and components with incomplete manual memoization, where an existing useMemo or useCallback is missing a dependency the compiler can see but can't safely resolve on your behalf.25
How do I know if React Compiler actually compiled a component?
Look for the "Memo ✨" badge next to the component's name in React DevTools — if it's missing, the compiler skipped that component.1 The official verification steps are:
- Install the React Developer Tools browser extension and open your app in development mode.
- Open the Components tab and look for the ✨ emoji next to component names.
- If a component is compiled, you'll see a "Memo ✨" badge; if it's missing, that component was bailed out.1
You can confirm the same thing by reading your build output directly. A compiled component's output imports a cache helper from React's own runtime and wraps its return value in a memoization check:
import { c as _c } from "react/compiler-runtime";
export default function MyApp() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>Hello World</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
If your compiled output for a given component doesn't look like this — no _c() call, no compiler-runtime import — the compiler skipped it.1 For a faster feedback loop while you're actively debugging one component, paste it into the official React Compiler Playground — it shows the compiled output and any bailout reasoning immediately, without a full build.3
What causes a React Compiler bailout?
Most bailouts come down to one of two things: a Rules of React violation the ESLint plugin already flagged, or manual memoization the compiler can't safely extend because its dependency array is incomplete.25 The preserve-manual-memoization lint rule captures the second case directly — React Compiler will only compile a component if its own inferred memoization matches or exceeds any useMemo, useCallback, or React.memo you already wrote, and an incomplete dependency array blocks it from doing so:
// Missing dependency prevents further compiler optimization
function Component({ data, filter }) {
const filtered = useMemo(
() => data.filter(filter),
[data] // 'filter' is missing
);
return <List items={filtered} />;
}
The fix is either to complete the dependency array, or to delete the manual memoization entirely and let the compiler infer it from scratch — both are valid per the official docs, and the second is usually simpler once you trust the compiler.5 The first case — code that relies on memoization for correctness — is subtler: it means your app's behavior depends on a specific value keeping the same object reference across renders (a common cause is an effect dependency array), and since the compiler may memoize differently than your manual code did, changing that reference can cause effects to over-fire, under-fire, or create loops.2
How do I debug a silent bailout step by step?
Follow the debugging workflow React's own docs recommend: isolate with "use no memo", test without any memoization, fix the underlying Rules of React violation, then confirm the badge comes back.2 In full:
- Temporarily disable compilation on the suspect component with the
"use no memo"directive, placed as the first line in the function body:
function ProblematicComponent() {
"use no memo"; // Skip compilation for this component
// ... rest of component
}
If the bug you're chasing disappears once compilation is disabled, it's very likely a Rules of React violation the compiler exposed rather than caused.2
- Remove manual memoization (
useMemo,useCallback,React.memo) from the same component to check whether your app still misbehaves with no memoization of any kind. If it does, you have a genuine Rules of React violation to fix, independent of the compiler.2 - Fix the root cause first, test after each change, and only then remove the
"use no memo"directive. - Verify the "Memo ✨" badge reappears in DevTools once the directive is removed — that confirms the compiler is optimizing the component again.2
If you suspect an actual compiler bug rather than a Rules of React violation in your own code, React's guidance is to reproduce it minimally, confirm it only happens with compilation enabled, and file it against the react/react GitHub repository with your exact React and compiler versions.2
Do I still need useMemo, useCallback, and React.memo with React Compiler?
For new code, no — the compiler's own analysis is, in the React team's words, "as precise, or moreso" than what most developers write by hand, and it can even memoize in places manual hooks can't reach, such as values computed after an early conditional return.3 For existing code, the official recommendation is more conservative: leave existing useMemo/useCallback/React.memo calls in place rather than stripping them out reflexively, because removing manual memoization can change what the compiler decides to compile, and that's a behavior change worth testing rather than assuming is safe.3 The one case where manual memoization still earns its keep even with the compiler enabled is when a memoized value is consumed as an effect dependency and you need precise, explicit control over exactly when that effect re-fires.3
Should I migrate off eslint-plugin-react-compiler to eslint-plugin-react-hooks?
Yes — as of the 1.0 release, the compiler-powered lint rules ship inside eslint-plugin-react-hooks's recommended and recommended-latest presets, and the React team explicitly recommends removing the standalone eslint-plugin-react-compiler package in favor of it.3 The linter doesn't require the compiler itself to be installed, so there's no risk in upgrading eslint-plugin-react-hooks even before you turn the compiler on:3
npm install --save-dev eslint-plugin-react-hooks@latest
// eslint.config.js (Flat Config)
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([
reactHooks.configs.flat.recommended,
]);
Rules called out specifically at React Conf 2025 include set-state-in-render (catches setState calls that cause render loops), set-state-in-effect (flags expensive work hidden inside effects), and refs (prevents unsafe ref access during render) — all three ship as part of the same package migration.3 The old eslint-plugin-react-compiler package is still published on npm, but there's no reason to keep it installed alongside eslint-plugin-react-hooks@latest once you've migrated.3
How do I set up React Compiler in Next.js 16?
Install babel-plugin-react-compiler and set the top-level reactCompiler option in next.config.ts — as of Next.js 16, this is a stable config key, not an experimental flag, though it still isn't on by default.4
npm install -D babel-plugin-react-compiler
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactCompiler: true,
}
export default nextConfig
Next.js's own docs are direct about why it isn't the default yet: "Built-in support for the React Compiler is now stable in Next.js 16 following the React Compiler's 1.0 release... The reactCompiler configuration option has been promoted from experimental to stable. It is not enabled by default as we continue gathering build performance data across different application types."4 That same guide tells you to expect compile times in development and during builds to be higher once you enable it, since the underlying compiler still runs on Babel.4 The separate reactCompiler reference docs frame the impact more optimistically: Next.js ships a custom SWC pass that only routes files containing JSX or hooks through the Babel-based compiler instead of every file in your project, which the docs describe as keeping the slowdown "small and localized" compared to Next.js's default Rust-based compiler.6 If you want tighter control than an all-or-nothing flag, Next.js also supports an opt-in annotation mode, where only components explicitly marked with the 'use memo' directive get compiled:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactCompiler: {
compilationMode: 'annotation',
},
}
export default nextConfig
// app/page.tsx
export default function Page() {
'use memo'
// ...
}
"use no memo" still works as the inverse — opting a single component out even when the global flag is on.6
How do I set up React Compiler in Vite?
If you're on @vitejs/plugin-react 6.0.0 or later (current published version is 6.0.3), use the reactCompilerPreset export alongside @rolldown/plugin-babel — the plugin's older inline babel option was removed in that release:1
npm install -D @rolldown/plugin-babel
// vite.config.js
import { defineConfig } from 'vite';
import react, { reactCompilerPreset } from '@vitejs/plugin-react';
import babel from '@rolldown/plugin-babel';
export default defineConfig({
plugins: [
react(),
babel({
presets: [reactCompilerPreset()]
}),
],
});
If you're still on an older @vitejs/plugin-react release, the compiler is configured through the plugin's now-deprecated inline babel option instead:
// vite.config.js (pre-6.0.0 @vitejs/plugin-react)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
],
});
If you're on React Router rather than plain Vite + React, React's own docs point you to vite-plugin-babel directly instead of @vitejs/plugin-react's preset.1 Either way, whichever build tool you use, the compiler plugin must run first in the Babel plugin pipeline — it needs the original, untransformed source to analyze data flow correctly.1
Is React Compiler actually stable enough for production in 2026?
Yes, by the React team's own account — 1.0 shipped October 7, 2025 as "battle tested on major apps at Meta" and "fully production-ready," with Meta Quest Store reporting up to 12% faster initial loads and cross-page navigations, and some interactions over 2.5x faster, with neutral memory usage.3 New-project defaults back this up: Expo SDK 54 and later ship the compiler enabled out of the box, and both create-vite and create-next-app now offer compiler-enabled templates for fresh projects.3 That said, "stable" and "on by default" are different things — as of this writing, neither plain React tooling nor Next.js 16 turns the compiler on automatically for existing projects, specifically because framework maintainers are still gathering real-world build-performance data before flipping that switch.4 The safest adoption path for an existing codebase, per React's own upgrade guidance, is to pin the compiler to an exact version (--save-exact, not a semver range) rather than floating on ^1.0.0, since future versions may change how granularly values get memoized — and if your effects rely on specific values keeping stable references, that kind of change can alter behavior even though the API surface hasn't moved.3
Bottom line
A React Compiler bailout isn't a bug report — it's the compiler declining to optimize code it can't prove is safe, and it fails silently by design. Check the "Memo ✨" badge in React DevTools first, reproduce the suspect component in the Playground second, and only then reach for "use no memo" to isolate whether you're looking at a genuine Rules of React violation in your own code. Once you've migrated to eslint-plugin-react-hooks@latest and set up the compiler correctly for your build tool — the stable top-level reactCompiler flag in Next.js 16, or reactCompilerPreset in @vitejs/plugin-react 6+ — most future bailouts will show up as ESLint errors before they ever reach a debugging session. For related React 19 patterns, see our guides on building forms with React Hook Form and Zod, optimistic updates and rollback with TanStack Query, and Server Actions and optimistic UI in Next.js 16.
Footnotes
-
React, "Installation – React Compiler," https://react.dev/learn/react-compiler/installation — Babel/Vite/Next.js/React Router setup instructions, "Memo ✨" badge verification, compiled build output example,
"use no memo"opt-out. Fetched 2026-07-13. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 -
React, "Debugging and Troubleshooting – React Compiler," https://react.dev/learn/react-compiler/debugging — compiler-errors-vs-runtime-issues distinction, common breaking patterns, step-by-step debugging workflow, bug reporting process. Fetched 2026-07-13. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13
-
React, "React Compiler v1.0," https://react.dev/blog/2025/10/07/react-compiler-1 — stable release date, Meta Quest Store performance figures, eslint-plugin-react-hooks migration guidance, useMemo/useCallback/React.memo guidance, Playground reference, new-app defaults, upgrade/versioning guidance. Fetched 2026-07-13. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15
-
Next.js, "How to upgrade to version 16," https://nextjs.org/docs/app/guides/upgrading/version-16 (lastUpdated 2026-05-13, docs version 16.2.10) — React Compiler Support section: stable promotion from experimental, not-on-by-default rationale, installation steps. Fetched 2026-07-13. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
React, "preserve-manual-memoization – eslint-plugin-react-hooks," https://react.dev/reference/eslint-plugin-react-hooks/lints/preserve-manual-memoization — rule details and invalid/valid dependency-array examples. Fetched 2026-07-13. ↩ ↩2 ↩3 ↩4
-
Next.js, "reactCompiler," https://nextjs.org/docs/app/api-reference/config/next-config-js/reactCompiler (lastUpdated 2026-02-11, docs version 16.2.9) — SWC-optimized file targeting, annotation mode,
'use memo'/"use no memo"directives. Fetched 2026-07-13. ↩ ↩2 ↩3