MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

React Forget Compiler: Installation and Configuration Guide

Set up React Compiler: babel plugin, Next.js integration, and debugging optimized components.

ReactCompilerPerformanceSetup

By MinhVo

Introduction

The React Compiler (previously known as React Forget) is a build-time tool that automatically optimizes React applications by inserting memoization where needed. After years of development, it was released as a Babel plugin that integrates directly into your build pipeline. This guide provides a comprehensive walkthrough for installing, configuring, and debugging the React Compiler in various project setups.

Manual memoization with useMemo, useCallback, and React.memo has been a source of frustration for React developers. It's tedious to write, easy to get wrong, and adds significant cognitive overhead. The React Compiler eliminates this burden by analyzing your code at build time and automatically applying the correct optimizations.

Whether you're using Next.js, Vite, Create React App, or a custom build setup, this guide covers the exact steps to get the React Compiler running in your project, along with debugging techniques and troubleshooting common issues.

Build pipeline setup

Understanding the Compiler: Core Concepts

What the Compiler Does

The React Compiler transforms your React code at build time by:

  1. Analyzing component code: Understanding data flow, state usage, and effect dependencies
  2. Identifying optimization opportunities: Finding where memoization would prevent unnecessary re-renders
  3. Inserting memoization primitives: Adding the equivalent of useMemo and useCallback at the optimal points
  4. Preserving correctness: Ensuring the transformed code behaves identically to the original

The compiler understands React's rules and uses static analysis to determine:

  • Which values are derived from state or props
  • Which values remain stable across renders
  • Where to insert memoization boundaries
  • When memoization is unnecessary or harmful

Prerequisites and Requirements

Before installing the React Compiler, ensure your project meets these requirements:

  • React 19+: The compiler is designed for React 19. While it can work with React 18 in some cases, React 19 provides the best experience.
  • Node.js 18+: The compiler requires modern Node.js features
  • Compatible build tool: Babel, Vite, Next.js, or Webpack with Babel loader
  • ESLint with React Compiler rules: Recommended for catching rule violations

Understanding Compiler Rules

The compiler relies on your code following React's rules. These are the same rules that have always existed but are now enforced at build time:

  1. Components and hooks must be pure during rendering: No side effects, no mutations, same output for same input
  2. Hooks must be called at the top level: Not inside conditions, loops, or after early returns
  3. Don't mutate values after they've been used in JSX: Create new objects instead of modifying existing ones
  4. Don't rely on the order of effect execution: Effects may run in any order

Compiler architecture diagram

Architecture and Design Patterns

Build Pipeline Integration

The compiler integrates as a Babel plugin in your build pipeline:

Source Code → Babel Parser → React Compiler Plugin → Code Generation → Optimized Bundle

The plugin runs during the Babel transformation phase, after parsing but before code generation. It receives the AST (Abstract Syntax Tree) of your component code, analyzes it, and transforms it to include memoization logic.

Compilation Modes

The compiler supports two modes:

All Mode: Compiles all React components and hooks in your codebase. This is the recommended mode for new projects or after thorough testing.

// babel.config.js
['babel-plugin-react-compiler', { mode: 'all' }]

Annotation Mode: Only compiles components that have the 'use memo' annotation. This is ideal for gradual adoption in existing codebases.

// babel.config.js
['babel-plugin-react-compiler', { mode: 'annotation' }]
'use memo'; // This opt-in annotation enables compilation
 
function MyComponent({ data }) {
  const processed = expensiveProcess(data);
  return <div>{processed}</div>;
}

Debug Output

The compiler provides detailed debug output showing what it optimized:

[ReactCompiler] Compiling MyComponent
  - Memoizing filteredData (depends on: data, filter)
  - Memoizing handleClick (no dependencies)
  - Memoizing sortedList (depends on: filteredData)
  - Skipping externalValue (external mutable reference)

Step-by-Step Implementation

Step 1: Install the Compiler Package

npm install babel-plugin-react-compiler

Or with yarn:

yarn add babel-plugin-react-compiler

Or with pnpm:

pnpm add babel-plugin-react-compiler

Step 2: Configure for Next.js

For Next.js 15+ applications, add the compiler to your next.config.js:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    reactCompiler: true,
  },
};
 
module.exports = nextConfig;

Next.js handles the Babel integration automatically. No additional Babel configuration is needed.

For older Next.js versions or custom Babel configurations:

npm install babel-plugin-react-compiler
// babel.config.js (project root)
module.exports = {
  presets: ['next/babel'],
  plugins: [
    ['babel-plugin-react-compiler', {
      mode: 'all',
    }],
  ],
};

Step 3: Configure for Vite

For Vite projects using @vitejs/plugin-react:

npm install babel-plugin-react-compiler @vitejs/plugin-react
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
 
export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          ['babel-plugin-react-compiler', {
            mode: 'all',
          }],
        ],
      },
    }),
  ],
});

Step 4: Configure for Webpack with Babel

For custom Webpack configurations:

npm install babel-loader @babel/core @babel/preset-react babel-plugin-react-compiler
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
            plugins: [
              ['babel-plugin-react-compiler', {
                mode: 'all',
              }],
            ],
          },
        },
      },
    ],
  },
};

Step 5: Install ESLint Plugin

The React Compiler comes with an ESLint plugin that catches rule violations before they cause compilation issues:

npm install eslint-plugin-react-compiler
// .eslintrc.js
module.exports = {
  plugins: ['react-compiler'],
  rules: {
    'react-compiler/react-compiler': 'error',
  },
};

This rule identifies code that violates React's rules, such as:

  • Mutating values after they've been used in JSX
  • Calling hooks conditionally
  • Using impure functions during rendering

Step 6: Verify Installation

Create a test component to verify the compiler is working:

// TestComponent.tsx
function TestComponent({ items, filter }) {
  // These should be automatically memoized by the compiler
  const filtered = items.filter(item => item.active === filter);
  const total = filtered.reduce((sum, item) => sum + item.value, 0);
 
  return (
    <div>
      <p>Total: {total}</p>
      <ul>
        {filtered.map(item => (
          <li key={item.id}>{item.name}: {item.value}</li>
        ))}
      </ul>
    </div>
  );
}

Run your build and check for compiler output:

# With Next.js
REACT_COMPILER_DEBUG=1 npm run build
 
# With Vite
REACT_COMPILER_DEBUG=1 npm run build
 
# Generic Babel
npx babel src --out-dir dist --verbose

Step 7: Gradual Adoption with Annotation Mode

For existing codebases, start with annotation mode:

// babel.config.js
module.exports = {
  plugins: [
    ['babel-plugin-react-compiler', {
      mode: 'annotation',
    }],
  ],
};

Then selectively opt-in components:

'use memo';
 
function PerformanceCriticalComponent({ data, filters }) {
  const filtered = data.filter(d => filters.includes(d.category));
  const sorted = [...filtered].sort((a, b) => b.score - a.score);
  
  return <DataGrid data={sorted} />;
}

Debugging workflow

Real-World Use Cases and Case Studies

Use Case 1: E-Commerce Product Listing

function ProductListing({ products, category, sortBy, priceRange }) {
  // Compiler automatically memoizes each derived computation
  const categoryFiltered = products.filter(p => p.category === category);
  const priceFiltered = categoryFiltered.filter(p => p.price >= priceRange.min && p.price <= priceRange.max);
  const sorted = [...priceFiltered].sort((a, b) => {
    if (sortBy === 'price') return a.price - b.price;
    if (sortBy === 'rating') return b.rating - a.rating;
    return a.name.localeCompare(b.name);
  });
  const totalCount = sorted.length;
 
  return (
    <div>
      <p>{totalCount} products found</p>
      <ProductGrid products={sorted} />
    </div>
  );
}

Use Case 2: Dashboard with Multiple Filters

function Dashboard({ rawData, dateRange, metrics, groupBy }) {
  const filteredByDate = rawData.filter(d => d.date >= dateRange.start && d.date <= dateRange.end);
  const selectedMetrics = filteredByDate.map(d => ({
    date: d.date,
    ...Object.fromEntries(metrics.map(m => [m, d[m]])),
  }));
  const grouped = groupByCategory(selectedMetrics, groupBy);
  const chartData = transformForChart(grouped);
 
  return (
    <div>
      <Chart data={chartData} />
      <DataTable data={selectedMetrics} />
    </div>
  );
}

Best Practices for Production

  1. Remove Existing Memoization: After enabling the compiler, remove useMemo, useCallback, and React.memo from your code. They're redundant and can interfere with the compiler's analysis.

  2. Run ESLint Plugin: Use eslint-plugin-react-compiler to catch rule violations before they cause compilation issues.

  3. Test After Enabling: Run your full test suite after enabling the compiler to catch any behavioral differences.

  4. Start with Annotation Mode: For existing codebases, use annotation mode to gradually adopt the compiler on a per-component basis.

  5. Monitor Performance: Use React DevTools Profiler to verify that the compiler is reducing re-renders as expected.

  6. Keep Dependencies Updated: The compiler is actively developed. Update babel-plugin-react-compiler regularly for bug fixes and improvements.

  7. Review Debug Output: Check compiler debug output to understand what was optimized and what was skipped.

  8. Handle Edge Cases: Some patterns may not compile correctly. Use the ESLint plugin to identify these cases and refactor accordingly.

Common Pitfalls and Solutions

PitfallImpactSolution
Not using React 19Compiler may not work correctlyUpgrade to React 19
Mixing manual and auto memoizationConflicting optimizationsRemove all manual memoization after enabling compiler
Violating React rulesCompilation errors or incorrect behaviorFix ESLint violations before enabling compiler
Missing Babel configurationCompiler doesn't runVerify babel-plugin-react-compiler is in plugins array
External mutable stateCompiler assumes immutabilityUse useSyncExternalStore for external state
Console warnings in dev modeNoise in development outputFilter compiler-specific warnings

Performance Optimization

Build Time Impact

The compiler adds time to your build process. Typical impact:

Project SizeWithout CompilerWith CompilerOverhead
Small (50 components)2s2.5s+25%
Medium (200 components)8s11s+37%
Large (1000 components)30s45s+50%

Runtime Performance Gains

The runtime benefits typically outweigh the build time overhead:

  • Fewer re-renders: 30-60% reduction in unnecessary component re-renders
  • Smaller update batches: More efficient reconciliation
  • Reduced memory allocation: Less garbage collection from temporary objects

Caching Compiled Output

// babel.config.js with caching
module.exports = {
  plugins: [
    ['babel-plugin-react-compiler', {
      mode: 'all',
      // Enable compilation caching
      cacheDirectory: '.cache/react-compiler',
    }],
  ],
};

Comparison with Alternatives

FeatureReact CompilerManual useMemo/useCallbackwhy-did-you-renderReact.memo
AutomationFully automaticManualDetection onlyManual
CoverageAll componentsPer-hookDetection onlyPer-component
Learning CurveLowMediumLowLow
Bundle Size ImpactSlight increaseNoneDev onlyNone
Build Time ImpactModerateNoneNoneNone
AccuracyHighDeveloper-dependentN/ADeveloper-dependent

Advanced Patterns

Custom Compiler Configuration

// babel.config.js with advanced options
module.exports = {
  plugins: [
    ['babel-plugin-react-compiler', {
      mode: 'all',
      // Custom configuration
      panicThreshold: 'NONE', // Don't panic on compilation errors
      logger: {
        logEvent(filename, event) {
          if (event.kind === 'CompileSuccess') {
            console.log(`✓ ${filename}: ${event.fnName}`);
          }
        },
      },
    }],
  ],
};

Integration with CI/CD

# GitHub Actions workflow
name: Build with React Compiler
on: [push, pull_request]
 
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run lint:compiler  # ESLint with react-compiler rules
      - run: npm run build
      - run: npm test

Testing Strategies

Verifying Compiler Output

import { render, screen, fireEvent } from '@testing-library/react';
 
describe('Compiled component behavior', () => {
  it('maintains referential equality for memoized values', () => {
    const references: any[] = [];
    
    function TestChild({ value, onClick }) {
      references.push({ value, onClick });
      return <button onClick={onClick}>{value}</button>;
    }
 
    function Parent() {
      const [count, setCount] = useState(0);
      const [text, setText] = useState('');
      const memoizedValue = computeExpensiveValue(count);
      const handleClick = () => console.log('clicked');
      
      return (
        <div>
          <input value={text} onChange={(e) => setText(e.target.value)} />
          <TestChild value={memoizedValue} onClick={handleClick} />
          <button onClick={() => setCount(c => c + 1)}>Increment</button>
        </div>
      );
    }
 
    render(<Parent />);
    references.length = 0;
 
    // Typing should not cause TestChild to re-render
    fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } });
    expect(references).toHaveLength(0);
  });
});

Future Outlook

The React Compiler represents a paradigm shift in React development. Future developments include:

  • Deeper framework integration: Native support in all major React frameworks
  • Server Component optimization: Automatic memoization for server components
  • Smarter analysis: Better handling of complex patterns and edge cases
  • IDE integration: Real-time feedback about compiler optimizations in your editor
  • Performance metrics: Built-in reporting on compilation effectiveness

Production Deployment and Monitoring

Deploying React applications to production requires careful consideration of build optimization, error tracking, and performance monitoring. A well-configured production build can significantly improve user experience through faster load times and more reliable error reporting.

Build Optimization Checklist

Before deploying, verify that your production build is fully optimized:

// next.config.js
module.exports = {
  reactStrictMode: true,
  poweredByHeader: false,
  compress: true,
 
  // Optimize images
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
    minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
  },
 
  // Security headers
  async headers() {
    return [{
      source: '/(.*)',
      headers: [
        { key: 'X-Frame-Options', value: 'DENY' },
        { key: 'X-Content-Type-Options', value: 'nosniff' },
        { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
      ],
    }];
  },
 
  // Webpack optimization
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.optimization.splitChunks = {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendor',
            chunks: 'all',
          },
        },
      };
    }
    return config;
  },
};

Error Tracking Integration

Configure Sentry or a similar error tracking service to capture and categorize production errors:

import * as Sentry from '@sentry/nextjs';
 
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1,
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  integrations: [
    new Sentry.BrowserTracing(),
    new Sentry.Replay({
      maskAllText: true,
      blockAllMedia: true,
    }),
  ],
  beforeSend(event) {
    // Filter out known non-critical errors
    if (event.exception?.values?.[0]?.type === 'ChunkLoadError') {
      return null;
    }
    return event;
  },
});

Health Check Endpoints

Implement health check endpoints that your load balancer and monitoring systems can use to verify application availability:

// pages/api/health.ts
export default async function handler(req, res) {
  try {
    // Check database connectivity
    await db.raw('SELECT 1');
 
    // Check external service dependencies
    const redisPing = await redis.ping();
 
    res.status(200).json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      services: {
        database: 'connected',
        redis: redisPing === 'PONG' ? 'connected' : 'degraded',
      },
      uptime: process.uptime(),
    });
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message,
    });
  }
}

This comprehensive monitoring approach ensures you detect and respond to production issues quickly, maintaining high availability for your users.

Community Resources and Further Learning

The technology landscape evolves rapidly, making continuous learning essential for maintaining expertise. Building a systematic approach to staying current with developments in your technology stack ensures you can leverage new features and avoid deprecated patterns.

Curated Learning Pathways

Rather than consuming content randomly, create structured learning pathways aligned with your current projects and career goals. Start with official documentation and specification documents, which provide the most accurate and comprehensive information. Follow this with hands-on tutorials and workshops that reinforce concepts through practical application.

Technical blogs from framework maintainers and core team members often provide deeper insights into design decisions and upcoming features. Subscribe to the official blogs of your primary frameworks and libraries to stay ahead of breaking changes and deprecation timelines.

Contributing to Open Source

Contributing to open-source projects in your technology stack provides unparalleled learning opportunities. Start with documentation improvements and bug reports, then progress to fixing small issues tagged as "good first issue" in your favorite projects. This direct engagement with maintainers and the codebase accelerates your understanding far beyond what passive learning can achieve.

# Setting up for contribution
git clone https://github.com/project/repository.git
cd repository
git checkout -b fix/issue-description
 
# Run the project's contribution setup
npm run setup:dev
npm run test  # Ensure tests pass before making changes
 
# Make your changes, then run the full test suite
npm run test:full
npm run lint
npm run build
 
# Submit your contribution
git add -A
git commit -m "fix: description of the fix
 
Closes #1234"
git push origin fix/issue-description

Building a Technical Knowledge Base

Maintain a personal knowledge base that captures insights, solutions, and patterns you discover during your work. Tools like Obsidian, Notion, or even a simple Markdown repository can serve as an external memory that grows more valuable over time.

Organize your notes by topic rather than chronologically, and include code examples, links to relevant documentation, and explanations of why certain approaches work better than others. When you encounter a particularly insightful article or conference talk, write a summary that captures the key takeaways and how they apply to your current projects.

Follow key conferences and their published talks to stay informed about emerging patterns and best practices. Many conferences publish recorded talks on YouTube within weeks of the event, making world-class technical content freely accessible.

Join relevant Discord servers, Slack communities, and forums where practitioners discuss real-world challenges and solutions. These communities provide early warning about emerging issues and access to collective wisdom that isn't available through formal documentation.

Mentorship and Knowledge Sharing

Teaching others is one of the most effective ways to deepen your own understanding. Consider writing technical blog posts, giving talks at local meetups, or mentoring junior developers. The process of explaining concepts to others forces you to organize your knowledge and identify gaps in your understanding.

Pair programming sessions with colleagues of different experience levels create mutual learning opportunities. Senior developers gain fresh perspectives on problems they've solved the same way for years, while junior developers benefit from exposure to production-grade thinking and decision-making processes.

Conclusion

The React Compiler eliminates the tedium of manual memoization by automatically optimizing your React applications at build time. Installing and configuring it is straightforward across all major build tools.

Key takeaways:

  1. Install babel-plugin-react-compiler and add it to your Babel configuration
  2. Framework integrations are available for Next.js (experimental flag), Vite (plugin option), and Webpack
  3. Use ESLint plugin to catch rule violations before enabling the compiler
  4. Start with annotation mode for gradual adoption in existing codebases
  5. Remove manual memoization after enabling the compiler to avoid conflicts
  6. Monitor build times and runtime performance to verify the compiler's effectiveness
  7. Keep the compiler updated as it's actively developed with frequent improvements

The React Compiler lets you write clean, simple React code while achieving the same performance as manually optimized applications. It's the future of React development, and getting started today puts you ahead of the curve.