Table of Contents
Overview
In previous blog posts, we explored React rendering optimization and memoization using useMemo, useCallback, and React.memo. If you haven’t read them yet, please refer to the following links.
After completing the upgrade to React 19, we naturally began considering React Compiler adoption as the next step. For details on the React 19 migration, please refer to the following link.
React Compiler automatically memoizes components and hooks, preventing unnecessary re-renders without manually writing useMemo, useCallback, or React.memo. In this post, we share the process of adopting React Compiler across a monorepo consisting of 6 apps and shared packages, along with the issues we encountered.
Environment
The target project environment is as follows.
- React 19, Vite 5, TypeScript
- Yarn Workspaces-based monorepo (6 apps + shared library)
- ESLint 8 (legacy config format)
- All functional components (no Class components)
Adoption Process
Package Installation
First, install the required packages.
# Install babel-plugin-react-compiler
yarn add -D [email protected]
# Upgrade eslint-plugin-react-hooks (v6 integrates compiler validation rules)
# Install eslint-plugin-react-compiler
yarn add -D eslint-plugin-react-hooks@^6.1.0 [email protected]
Note that the recommended-latest preset in eslint-plugin-react-hooks v6 only works with ESLint 9’s flat config format. If you’re using ESLint 8’s legacy format, keep the existing recommended and install eslint-plugin-react-compiler separately.
ESLint Configuration
Add the react-compiler plugin and rule to the shared ESLint configuration file as follows.
// Shared ESLint config
module.exports = {
extends: ['./index.js', 'plugin:react-hooks/recommended'],
plugins: ['react-refresh', 'react-compiler'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'react-compiler/react-compiler': 'error',
},
};
Setting react-compiler/react-compiler to error catches code that violates React’s rules before build time.
For more details on how to configure ESLint in a React project, please refer to the following link.
Vite Configuration
Add the Babel plugin to each app’s vite.config.ts as follows.
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [['babel-plugin-react-compiler', { panicThreshold: 'NONE' }]],
},
}),
],
});
In a monorepo, if each app imports shared library sources directly, they are automatically compiled during each app’s build, so no separate configuration is needed for shared packages. Configuration only needs to be added to the app-level vite.config.ts.
For how to start a Vite-based React project, please refer to the following link.
Issues Encountered
Choosing panicThreshold
Initially, we set panicThreshold: 'CRITICAL_ERRORS'. This option skips components that can’t be optimized while only failing the build for critical errors.
However, existing eslint-disable react-hooks/exhaustive-deps comments in the code were classified as critical errors by the compiler, causing build failures. Since this pattern existed in 313 locations across the project, we changed to panicThreshold: 'NONE' to automatically skip optimization for those components.
The options for panicThreshold are as follows.
| Option | Behavior |
|---|---|
NONE | Silently skips when optimization isn’t possible |
CRITICAL_ERRORS | Only fails build on critical violations |
ALL_ERRORS | Fails build on all violations |
For gradual adoption, we recommend starting with NONE and raising it incrementally after stabilization.
eslint-disable Comment Strategy
Setting react-compiler/react-compiler to error causes lint errors at all 313 locations that previously violated hooks rules. We considered two approaches for handling this.
Adding at the top of files (not adopted)
/* eslint-disable react-compiler/react-compiler */
This disables compiler validation for the entire file, causing validation to be skipped for other components in the same file as well.
Adding inline at the exact location (adopted)
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
Since this applies only to the exact location, other code in the same file is validated normally. We ran lint to extract the exact files and line numbers with errors, then bulk-inserted the comments via script.
ref.current Mutation False Positives
Manipulating the DOM through ref.current inside event handlers does not violate React’s rules. However, the compiler sometimes generates a “you should not mutate the return value of a function” warning.
const handleLoadedMetadata = () => {
const video = videoRef.current;
if (!video || !timelineRef.current) return;
video.currentTime = 0; // Compiler warning
timelineRef.current.setCurrentTime(0); // Compiler warning
};
For these false positives, suppressing with eslint-disable-next-line is appropriate.
Risks and Testing Guide
Risk by Level
High — Rendering Behavior Changes
As the compiler automatically inserts memoization, code that previously ran on every render may be skipped.
- Code that executes side effects outside of
useEffect - Logic that depends on creating new objects/arrays on every render
- Patterns that read and write
ref.currentduring rendering
For more on React rendering behavior, please refer to the following blog.
High — Libraries Using Custom Reconcilers
Libraries like react-konva that customize React’s internal reconciler have a high chance of conflicting with the compiler’s memoization. Canvas-based rendering (annotations, bounding boxes, etc.) should be tested thoroughly.
Medium — Chart/Keyboard Libraries
Libraries like recharts and react-hotkeys-hook use their own state management or global event listeners internally. Chart rendering and keyboard shortcut behavior should be verified.
Low — Pure Rendering/Analytics Libraries
Libraries like qrcode.react and react-ga4 are either pure rendering components or only perform side effects, so the risk is low, but it’s still good to verify they work correctly.
Key Testing Items
| Item | Verification Points |
|---|---|
| Page initial load | useEffect initialization logic runs correctly (data fetch, state setup) |
| Form input/validation | Input changes reflected immediately, error message display timing |
| Dialog open/close | State initialization, data reload |
| Table sorting/pagination | Sort order, data after page transition |
| Infinite scroll | IntersectionObserver-based scroll loading |
| Route transitions | State cleanup and new page initialization |
Handling Issues: The “use no memo” Directive
If abnormal behavior is found in a specific component during testing or QA, the "use no memo" directive can exclude only that component from compiler optimization.
const ProblematicComponent = () => {
'use no memo';
return <div>...</div>;
};
Difference from ESLint disable
These two operate at different levels.
"use no memo" | eslint-disable react-compiler | |
|---|---|---|
| Scope | Compiler itself skips the function | Only hides ESLint warning |
| Build | Does not generate optimized code | Depends on panicThreshold |
| Use | When runtime issues occur | Suppressing lint errors |
Response Flow
When issues are found during testing or QA, you can respond as follows.
- Abnormal behavior found during testing or QA
- Add
"use no memo"to the component - Confirm issue is resolved → keep if fixed
- Identify root cause and fix code → remove
"use no memo"
Components optimized by the compiler show a Memo ✨ indicator in the browser’s React DevTools.

Summary
The key to React Compiler adoption is a gradual approach.
- Start with
panicThreshold: 'NONE', letting components that can’t be optimized be automatically skipped. - Set ESLint rules to
error, but suppress existing violations with inline comments. - If issues arise, opt out individual components with
"use no memo". - After stabilization, remove manual
useMemo/useCallback/React.memoand incrementally raisepanicThreshold.
Considering the complexity and error-proneness of manual memoization covered in previous blog posts, React Compiler’s automatic optimization is a significant improvement. However, the more rule violations in the existing codebase, the more issues you’ll encounter during adoption, so a gradual approach is crucial.
Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!
App promotion
Deku.Deku created the applications with Flutter.If you have interested, please try to download them for free.