[React] Adopting React Compiler in a Monorepo

2026-03-04 hit count image

Sharing the process of adopting React Compiler across a monorepo with 6 apps and shared packages, including issues encountered along the way — panicThreshold selection, eslint-disable strategies, ref.current false positives, and using the "use no memo" directive for troubleshooting.

react

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.

OptionBehavior
NONESilently skips when optimization isn’t possible
CRITICAL_ERRORSOnly fails build on critical violations
ALL_ERRORSFails 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.current during 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

ItemVerification Points
Page initial loaduseEffect initialization logic runs correctly (data fetch, state setup)
Form input/validationInput changes reflected immediately, error message display timing
Dialog open/closeState initialization, data reload
Table sorting/paginationSort order, data after page transition
Infinite scrollIntersectionObserver-based scroll loading
Route transitionsState 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
ScopeCompiler itself skips the functionOnly hides ESLint warning
BuildDoes not generate optimized codeDepends on panicThreshold
UseWhen runtime issues occurSuppressing lint errors

Response Flow

When issues are found during testing or QA, you can respond as follows.

  1. Abnormal behavior found during testing or QA
  2. Add "use no memo" to the component
  3. Confirm issue is resolved → keep if fixed
  4. 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.

React DevTools - Components marked with Memo by React Compiler

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.memo and incrementally raise panicThreshold.

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

You can use the applications that are created by this blog writer Deku.
Deku created the applications with Flutter.

If you have interested, please try to download them for free.



SHARE
Twitter Facebook RSS