React Compiler and Sanity
Learn how to use the React Compiler with Sanity Studio, React 18 & 19, and for publishing Sanity plugins. Improve performance and reduce manual memoization.
Sanity Studio v3.65.0 introduced support for the React Compiler. The compiler improves performance by automatically optimizing component rendering. This reduces the amount of manual memoization developers have to do through APIs such as useMemo and useCallback.
If you use @sanity/pkg-utils and/or @sanity/plugin-kit to distribute custom plugins and tools on npm then it's also possible to use the compiler there.
Sanity Studio
Sanity Studio v5.0.0 and later requires React 19.2.2 or later, which has the React Compiler runtime built in. You don't need the react-compiler-runtime package in a studio.
Install the Babel plugin for the compiler, the ESLint plugin, and a TypeScript parser for ESLint:
npm install --save-dev babel-plugin-react-compiler eslint-plugin-react-hooks @typescript-eslint/parser
pnpm add --save-dev babel-plugin-react-compiler eslint-plugin-react-hooks @typescript-eslint/parser
yarn add --dev babel-plugin-react-compiler eslint-plugin-react-hooks @typescript-eslint/parser
bun add --dev babel-plugin-react-compiler eslint-plugin-react-hooks @typescript-eslint/parser
Set up your ESLint config. The compiler's checks ship in the plugin's recommended preset:
import reactHooks from 'eslint-plugin-react-hooks'
import tsParser from '@typescript-eslint/parser'
import {defineConfig} from 'eslint/config'
export default defineConfig([
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {parser: tsParser},
extends: [reactHooks.configs.flat.recommended],
},
])The recommended preset sets no files pattern, and ESLint lints only .js, .mjs, and .cjs files by default. Without the files and languageOptions above, ESLint skips every .ts and .tsx file in your studio and still exits 0, so the compiler checks look like they passed. If you already have an ESLint config, add the preset to a config object that sets files and a TypeScript parser rather than at the top level.
You don't need to fix all the warnings before you can start using the compiler, you can incrementally adopt it.
It's also recommended that you have strictNullChecks enabled.
Add reactCompiler to your sanity.cli.ts configuration and set target to '19':
import {defineCliConfig} from 'sanity/cli'
export default defineCliConfig({
api: {
projectId: 'YOUR_PROJECT_ID',
dataset: 'production',
},
reactStrictMode: true,
reactCompiler: {target: '19'},
})Rust-based transform (experimental)
As of Studio v6.11.0, an opt-in Rust-based transform is available via oxc-transform-react. It runs the React Compiler in a single native pass (no Babel in the pipeline), which is significantly faster.
Install oxc-transform-react instead of babel-plugin-react-compiler:
npm install --save-dev oxc-transform-react
pnpm add --save-dev oxc-transform-react
yarn add --dev oxc-transform-react
bun add --dev oxc-transform-react
Add transform: 'oxc' to your reactCompiler config in sanity.cli.ts:
import {defineCliConfig} from 'sanity/cli'
export default defineCliConfig({
api: {
projectId: 'YOUR_PROJECT_ID',
dataset: 'production',
},
reactStrictMode: true,
reactCompiler: {target: '19', transform: 'oxc'},
})The default transform: 'babel' is unchanged, and existing configs continue to work without any changes. The 'oxc' transform is experimental: review the compiled output before deploying to production. If you use a custom jsxImportSource, stay on 'babel'.
Embedded Studios
If your studio is hosted inside something like a Next.js, Remix app or otherwise not using sanity build and sanity dev commands?
If so you'll have to enable the compiler through one of the methods documented here.
Publishing Sanity plugins and tools
Since the compiler needs to run on the original source code, studios can't compile the libraries they use. Instead, library authors need to ship compiled code to npm.
@sanity/pkg-utils builds libraries for npm. It handles ESM, CJS, and the React Compiler, and it's what @sanity/plugin-kit sets up for you.
Install the Babel plugin for the compiler, the ESLint plugin, and a TypeScript parser for ESLint:
npm install --save-dev babel-plugin-react-compiler eslint-plugin-react-hooks @typescript-eslint/parser
pnpm add --save-dev babel-plugin-react-compiler eslint-plugin-react-hooks @typescript-eslint/parser
yarn add --dev babel-plugin-react-compiler eslint-plugin-react-hooks @typescript-eslint/parser
bun add --dev babel-plugin-react-compiler eslint-plugin-react-hooks @typescript-eslint/parser
Since v7 of eslint-plugin-react-hooks the new React Compiler checks are included in the recommended preset.
import reactHooks from 'eslint-plugin-react-hooks'
import tsParser from '@typescript-eslint/parser'
import {defineConfig} from 'eslint/config'
export default defineConfig([
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {parser: tsParser},
extends: [reactHooks.configs.flat.recommended],
},
])The recommended preset sets no files pattern, and ESLint lints only .js, .mjs, and .cjs files by default. Without the files and languageOptions above, ESLint skips every .ts and .tsx file in your library and still exits 0, so the compiler checks look like they passed. If you already have an ESLint config, add the preset to a config object that sets files and a TypeScript parser rather than at the top level.
You don't need to fix all the warnings before you can start using the compiler, you can incrementally adopt it.
It's also recommended that you have strictNullChecks enabled.
If your library supports React 18, install react-compiler-runtime as a direct dependency:
npm install --save-exact react-compiler-runtime
pnpm add --save-exact react-compiler-runtime
yarn add --exact react-compiler-runtime
bun add --exact react-compiler-runtime
Pin it to an exact version. The --save-exact flag writes the exact version to package.json instead of a caret range.
Next, add the reactCompiler option to your package.config.ts:
import {defineConfig} from '@sanity/pkg-utils'
export default defineConfig({
// ... other settings
reactCompiler: {target: '18'}, // matches the minimum `react` major your library supports
})reactCompiler is a single top-level option in @sanity/pkg-utils v12 and later. Earlier majors used babel: {reactCompiler: true} with a separate reactCompilerOptions, both of which v12 removed. @sanity/plugin-kit v10 requires @sanity/pkg-utils v12 or later.
To use the Rust-based transform with @sanity/pkg-utils, install oxc-transform-react and add transform: 'oxc':
npm install --save-dev oxc-transform-react
pnpm add --save-dev oxc-transform-react
yarn add --dev oxc-transform-react
bun add --dev oxc-transform-react
import {defineConfig} from '@sanity/pkg-utils'
export default defineConfig({
// ... other settings
reactCompiler: {target: '18', transform: 'oxc'},
})Using @sanity/tsdown-config directly
Library authors who use @sanity/tsdown-config directly (rather than @sanity/pkg-utils) configure the React Compiler in tsdown.config.ts. The same transform: 'oxc' option is available:
npm install --save-dev oxc-transform-react
pnpm add --save-dev oxc-transform-react
yarn add --dev oxc-transform-react
bun add --dev oxc-transform-react
import {defineConfig} from '@sanity/tsdown-config'
export default defineConfig({
tsconfig: 'tsdown.dist.json',
reactCompiler: {target: '19', transform: 'oxc'},
})The same experimental caveat applies: transform: 'oxc' is @alpha, so review the generated output before publishing to npm.
Troubleshooting
Follow the official troubleshooting docs in case you run into problems. In our experience it's incredibly rare for the compiler to create a regression, it typically choses to skip over optimizing components it deems unsafe, or too complex to safely memoize.
Should a rare problem occur it's often enough to add 'use no memo' at the top of the affected file, to buy you time and find the fix. And then use ESLint with eslint-plugin-react-hooks to find issues that could be the root cause. Running React Strict Mode is also incredibly helpful to uncover root issues.