99

Concurrent visitors over the last 30 minutes. (~)

Peak concurrent visitors -
Yesterday: 0 / Today: 0

React Compiler Essentials

React Compiler Essentials

This post is based on React Compiler v1.0.0.

React Compiler is a build-time optimization tool that handles component re-rendering and memoization automatically, so you can get optimal performance without complex manual optimization.
You no longer need to use the useMemo and useCallback hooks or the memo function.
It's officially supported from React v19, and with extra configuration you can also use it in React v17 and v18.

# Before the compiler

Before React Compiler, to optimize re-rendering, you had to manually memoize components, functions, and values with React.memo, useCallback, and useMemo.
This prevents unnecessary re-renders and extra computation, but it hurts code readability and makes you write more complex code.

/src/components/Counter.tsx
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { memo, useMemo } from 'react' function Counter({ count, onIncrease }: { count: number onIncrease: () => void }) { console.log('Counter component rendered!') // Memoize the computed value with useMemo const double = useMemo(() => { console.log('double value computed!') return count * 2 }, [count]) return ( <div> <p>Count: {count}</p> <p>Double: {double}</p> <button onClick={onIncrease}>Increase!</button> </div> ) } // Memoize the component with memo export default memo(Counter)
/src/App.tsx
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { useState, useCallback } from 'react' import Counter from '@/components/Counter' export default function App() { const [text, setText] = useState('') const [count, setCount] = useState(0) // Memoize the function with useCallback const increase = useCallback(() => { console.log('increase function called!') setCount(c => c + 1) }, []) return ( <> <input placeholder="Type anything!" value={text} onChange={e => setText(e.target.value)} /> <Counter count={count} onIncrease={increase} /> </> ) }

# After the compiler

With React Compiler, your code is optimized even beyond what you'd get by writing manual memoization yourself.
When state changes, only the parts that need it are re-rendered or recomputed, which is also called fine-grained reactivity.

/src/components/Counter.tsx
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function getDouble(count: number) { console.log('double value computed!') return count * 2 } export default function Counter({ count, onIncrease }: { count: number onIncrease: () => void }) { console.log('Counter component rendered!') const double = getDouble(count) return ( <> <p>Count: {count}</p> <p>Double: {double}</p> <button onClick={onIncrease}>Increase!</button> </> ) }
/src/App.tsx
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { useState } from 'react' import Counter from '@/components/Counter' export default function App() { const [text, setText] = useState('') const [count, setCount] = useState(0) const increase = () => { console.log('increase function called!') setCount(c => c + 1) } return ( <> <input placeholder="Type anything!" value={text} onChange={e => setText(e.target.value)} /> <Counter count={count} onIncrease={increase} /> </> ) }

# Using it in a new project

If you create a new project with the Vite build tool or Next.js, you can use React Compiler very easily.

Vite setup

BASH
1
2
3
4
5
npm create vite@latest . ◇ Select a framework: React ◇ Select a variant: TypeScript + React Compiler # ...

Next.js setup

BASH
1
2
3
4
5
6
7
npx create-next-app@latest . ✔ Would you like to use the recommended Next.js defaults? › No, customize settings ✔ Would you like to use TypeScript? … No / Yes ✔ Which linter would you like to use? › ESLint ✔ Would you like to use React Compiler? … No / Yes # ...

# Using it in an existing project

Let's look at the steps needed to use React Compiler in an existing project.
It's a good idea to adopt it gradually: test it on small parts first, make sure it's stable, and then expand it to the whole project.
First, install the required package, babel-plugin-react-compiler, as follows.

BASH
1
npm i -D babel-plugin-react-compiler

Vite setup

In the vite.config.ts configuration file, add babel-plugin-react-compiler as a Babel plugin in the React options.
You can simply specify the plugin name to use React Compiler with its defaults, or add compiler options for gradual adoption.

/vite.config.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [ react({ babel: { plugins: [ // 'babel-plugin-react-compiler' // or ['babel-plugin-react-compiler', { // Compiler options.. }] ] } }) ] })

Next.js setup

In Next.js v15.3.1 and later, you can use React Compiler easily.
In the next.config.ts configuration file, enable the SWC-based React Compiler with the reactCompiler option.

/next.config.ts
TS
1
2
3
4
5
6
7
8
9
10
import type { NextConfig } from 'next' const nextConfig: NextConfig = { // reactCompiler: true // or reactCompiler: { // Compiler options.. } } export default nextConfig

# React 17/18 support

With extra configuration, you can use React Compiler in React v17 or v18 too.
Install the react-compiler-runtime package and specify the React version you use with the target compiler option.
This lets the compiler use the correct API implementation for your React version or polyfill missing APIs.

BASH
1
npm i react-compiler-runtime
Install the package
TS
1
2
3
4
5
6
7
{ plugins: [ ['babel-plugin-react-compiler', { target: '17' }] ] }
Add compiler options / Vite
TS
1
2
3
4
5
{ reactCompiler: { target: '17' } }
Add compiler options / Next.js

# ESLint integration

You can use ESLint rules that help you identify code that React Compiler can't optimize.
Configure the recommended rule preset of the eslint-plugin-react-hooks ESLint plugin.

Your existing project may already use the eslint-plugin-react-hooks ESLint plugin.
Check whether it's installed and which version it is, and update it to v7 or later if needed.

BASH
1
npm i -D eslint-plugin-react-hooks@^7
Install the package
/eslint.config.js
JS
1
2
3
4
5
6
7
8
9
import reactHooks from 'eslint-plugin-react-hooks' export default defineConfig([ { extends: [ reactHooks.configs.flat.recommended ] } ])
Configure the recommended preset

# Directory-based

Babel's overrides option lets you apply different plugins to different parts of your codebase.
You can start with a specific directory and expand gradually.

Vite setup

/vite.config.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react({ babel: { overrides: [ { test: [/src\/features\/.*\.(js|jsx|ts|tsx)$/], plugins: [ ['babel-plugin-react-compiler', { target: '17' }] ] } ] } })] })

# Opt-out based

React Compiler automatically identifies and compiles components and hooks that follow React's naming rules.
If you don't want some components or hooks to be compiled for testing purposes, you can exclude them with the 'use no memo' directive, and this is called opting out.
The directive must be used at the top level of the component or hook function.

/src/components/Counter.tsx
TSX
1
2
3
4
5
export default function Counter() { 'use no memo' // ... }

# Opt-in based

If you don't want to compile anything except the few components or hooks you want to test, you can use the 'use memo' directive.
Unlike the 'use no memo' directive, however, compiling only the code that has the directive requires the compilationMode: 'annotation' option.
This is called opting in.

TS
1
2
3
4
5
6
7
{ plugins: [ ['babel-plugin-react-compiler', { compilationMode: 'annotation' }] ] }
Add compiler options

Add the 'use memo' directive to the components or hooks you want to compile.

/src/components/Counter.tsx
TSX
1
2
3
4
5
export default function Counter() { 'use memo' // ... }