EmpireUI
Get Pro
← Blog8 min read#react 19#react compiler#performance

React 19 Compiler: What It Does, What It Fixes and What to Expect

React 19's compiler auto-memoizes your components so you don't have to. Here's what it actually changes, what it still can't fix, and how to adopt it without pain.

Code on a monitor screen showing JavaScript React component syntax

The Short Version: What the React Compiler Actually Is

The React Compiler — shipped stable in React 19.0 (released late 2024, widely adopted through 2025 and into 2026) — is a build-time transform that reads your component code and automatically inserts memoization where it would help. You don't touch useMemo, useCallback, or React.memo yourself. The compiler does it.

That sounds almost too convenient, so let's be precise about what it actually does. It analyzes the dependency graph of your render functions at compile time, identifies values that don't change between renders, and wraps them in stable references. The output is still plain JavaScript that runs on any browser. No runtime magic, no new virtual machine. Just smarter compiled output.

Honestly, the biggest mental shift isn't the API — it's that you stop thinking about memoization as a performance optimization you apply by hand. In practice, this means you write components the obvious way first, and the compiler handles the granular caching. That's a genuine quality-of-life upgrade for teams who've been cargo-culting useCallback wrappers around every event handler.

Worth noting: the compiler doesn't replace React's reconciler or change how re-renders are triggered. It works inside the existing React model, not around it. If your performance problem is something like a massive component tree with deeply nested context, auto-memoization helps at the leaf level but the structural issue is still yours to solve.

What You Were Doing Before (And Why It Was a Mess)

Before the compiler landed, every React dev over 18 months of experience had developed an intuition for sprinkling useMemo and useCallback around hot paths. The problem? It was manual, inconsistent, and almost impossible to audit correctly at team scale.

Here's a classic example of what this looked like pre-compiler — arguably the most common pattern in every React codebase you've ever inherited: ``jsx // Pre-compiler: you wrote this by hand, everywhere function ProductList({ items, onSelect }) { const sortedItems = useMemo( () => [...items].sort((a, b) => a.name.localeCompare(b.name)), [items] ); const handleSelect = useCallback( (id) => onSelect(id), [onSelect] ); return sortedItems.map(item => ( <ProductCard key={item.id} item={item} onSelect={handleSelect} /> )); } ``

You'd also wrap the child in React.memo and then spend 40 minutes six months later debugging why a prop with a new object reference on every render was still blowing through that memo boundary. Sound familiar? The compiler catches this whole class of problem — it sees that handleSelect depends only on onSelect, which hasn't changed, and memoizes it without you asking.

That said, the manual approach wasn't all waste. Writing useMemo by hand forced you to think about dependencies explicitly. Some developers worry the compiler removes that discipline. Look, I'd argue the opposite — now you think about component shape and data flow rather than the incidental plumbing of memoization wrappers.

What the Compiler Does and Doesn't Optimize

The compiler works on components and hooks that follow the Rules of React. No side effects in render, pure functions, referentially stable inputs where possible. If your code already follows these rules, you get the optimization for free. If it doesn't, the compiler skips that component and leaves it alone — it won't break broken code, it just won't help it.

Here's what post-compiler code looks like. You write the obvious version: ``jsx // Post-compiler: just write normal code function ProductList({ items, onSelect }) { const sortedItems = [...items].sort((a, b) => a.name.localeCompare(b.name)); return sortedItems.map(item => ( <ProductCard key={item.id} item={item} onSelect={() => onSelect(item.id)} /> )); } `` The compiler transforms this at build time, inserting the correct memos automatically. The output bundle still has the memoization logic — you just didn't write it.

The compiler does not optimize: effects (useEffect, useLayoutEffect), imperative code outside components, third-party library internals, and anything that violates React's rules. It also won't help you if you're creating objects with new data inside render on every call — it can tell the value is unstable and won't pretend otherwise.

One more thing — the compiler has an escape hatch. Add 'use no memo' as a directive at the top of a component or hook file and the compiler skips it entirely. Useful during migration when you've got a particularly weird component that you want to handle yourself or validate manually before opting in.

Quick aside: if you're building something like a UI kit or design system with lots of leaf components — think all the animation-heavy stuff in a library like Empire UI — the compiler is basically a free performance pass. Small components that take stable props and return JSX are exactly what it's optimized for.

How to Enable It in Your Project

The compiler ships as a Babel plugin and a Vite/webpack transform. For most Next.js 14+ and 15+ projects, you opt in via next.config.js. Here's the canonical setup as of mid-2026: ``js // next.config.js const nextConfig = { experimental: { reactCompiler: true, }, }; export default nextConfig; ` For Vite, you add it as a plugin: `js // vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [ react({ babel: { plugins: ['babel-plugin-react-compiler'], }, }), ], }); ``

Run eslint-plugin-react-compiler alongside it. Seriously, don't skip this. The eslint plugin flags violations of React's rules that would cause the compiler to silently skip your component. You want to know about those — a skipped component isn't broken, but it also isn't getting the benefit you enabled the compiler for.

In practice, most mature codebases should run the compiler in 'opt-in' mode first rather than enabling it globally. Use 'use memo' as a per-file directive to test specific components before going all-in. This gives you a controlled migration path without a big-bang rollout that's hard to debug if something goes sideways.

Worth noting: the React team ships react-compiler-healthcheck, a CLI tool that scans your project and tells you what percentage of components are compiler-compatible. Run it before you enable anything: ``bash npx react-compiler-healthcheck@latest `` Typical output on a well-maintained codebase is 70–90% compatibility. The outliers are usually components that mutate state in unexpected places or have side effects in render.

Performance Gains: Real Numbers and Realistic Expectations

Meta (who built the compiler) ran it on Instagram web and reported significant interaction-time improvements, particularly on list-heavy screens where dozens of components were re-rendering on user interactions. The gains are most visible in apps that previously under-invested in manual memoization — which is, honestly, most apps.

What you shouldn't expect: miracles on a slow device with a 4MB bundle, or a fix for a component that runs an expensive synchronous calculation on every keystroke. The compiler memoizes values between renders. It doesn't make individual calculations faster, it makes React smarter about when to skip them. There's a difference.

For a component library that renders a lot of small styled elements — like a card grid, a nav bar, or a toolbar with icon buttons — you can expect 20–40% fewer redundant renders in typical interactive scenarios. If you're building UI-heavy apps and you're using something like the components at Empire UI or integrating with animation libraries like Framer Motion (see also our best React UI libraries 2026 roundup), this kind of baseline render reduction is worth having before you even think about animation frame budgets.

Profiling is still essential. Use the React DevTools Profiler with the 'Record why each component rendered' option (introduced in DevTools 5.x) to see what the compiler is and isn't memoizing. Don't assume the compiler handled everything — verify it, especially for your highest-traffic screens.

Common Mistakes and Edge Cases to Watch For

The biggest mistake teams make is enabling the compiler and removing all their existing useMemo and useCallback calls immediately. Don't. The compiler will add them back where it needs to, but if you delete them from components the compiler skips (due to rule violations), you've just introduced a regression. Remove them incrementally, after confirming the compiler is handling that component.

Conditional hooks still break the compiler, same as they always broke React. If you've got if (condition) { useState(...) } anywhere, fix it regardless of the compiler — but now you also have the eslint plugin flagging it for you, which is a nice forcing function. ``jsx // Still broken — compiler skips this component function BadComponent({ isAdmin }) { if (isAdmin) { const [role, setRole] = useState('admin'); // Rules of Hooks violation } return <div />; } ``

Objects created inline in JSX props are another area to watch. Even with the compiler, passing style={{ color: 'red', fontSize: 16 }} directly into a component creates a new object reference every render. The compiler can detect that the values don't change and optimize this in many cases, but if the object depends on a prop that changes frequently, it can't help. Keep passing stable references where you can.

One more thing — third-party libraries that use deprecated patterns or internal React APIs might behave unexpectedly when the compiler starts memoizing their wrappers in ways they didn't anticipate. Test your library integrations. Most major libraries (React Query, Zustand, Jotai) are already compiler-compatible as of 2026, but anything older or more exotic deserves a sanity check. While you're auditing your stack, it's also a good time to review your nextjs-app-router-guide setup — the compiler and App Router pair well together.

Should You Adopt It Now?

Yes, but incrementally. The compiler is stable as of React 19 and actively maintained. It's not experimental anymore — it's production-grade tooling that Meta ships to hundreds of millions of users. That's a meaningful signal.

Start with the healthcheck CLI. If you're above 75% compatibility, enable it in opt-in mode, use the eslint plugin to clean up violations, and gradually expand coverage. If you're below 75%, the violations the eslint plugin surfaces are worth fixing anyway — they're usually real code-quality problems dressed up as compatibility issues.

For greenfield projects in 2026, just turn it on from day one. Write components the simple way, let the compiler handle memoization, and save the mental energy for actual product problems. That's the future the React team is building toward, and honestly, it's the right call.

If you're doing something heavily visual — lots of animated components, style switching, dynamic UI — the compiler fits naturally into that workflow. Check out how Empire UI's component catalog handles things like glassmorphism cards or neobrutalism layouts at neobrutalism. The render patterns there are exactly the kind the compiler handles well: stable props, pure renders, lots of visual composition without heavy logic.

FAQ

Does the React Compiler replace useMemo and useCallback?

Effectively yes — for components the compiler can analyze, you don't need to write them manually. But don't delete existing ones until you've confirmed the compiler is covering those components via the DevTools Profiler.

Does the compiler work with TypeScript?

Yes, fully. The Babel plugin and the Next.js integration both handle TypeScript out of the box. Type information doesn't affect what the compiler can or can't optimize.

Will it break existing code?

Only if your code already violates the Rules of React. The compiler skips components it can't safely optimize, so it doesn't break them — it just doesn't help them. Fix the violations, then the compiler kicks in.

How do I know if the compiler is actually working on my components?

Use react-compiler-healthcheck before enabling it, and React DevTools Profiler after. Components the compiler optimized will show fewer re-renders in the flame graph for the same interactions.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

React Compiler Beta: What It Auto-Optimizes and When You Still Need MemoReact 19: What Actually Changed and What You Should Use NowReact UI Library Bundle Size Compared: shadcn, MUI, Mantine, Empire UIReact vs Svelte in 2026: Honest Comparison After 5 Years of SvelteKit