How React Compiler Memoization Works in Instatic: Configuration and Exceptions
Instatic enables React Compiler via Vite's Babel configuration to automatically memoize components and functions, while strictly limiting manual memoization to three specific exceptions: stabilizing callbacks for hook dependencies, optimizing critical list items, and escaping the compiler for unanalyzable code blocks.
The Instatic repository leverages the React Compiler to eliminate manual memoization overhead across its entire React application. By integrating the compiler into the build pipeline through Vite, the project automatically optimizes component re-renders without requiring useMemo, useCallback, or React.memo in standard code. Understanding how this automatic React Compiler memoization functions—and where it requires human intervention—is essential for contributing to the codebase.
How the React Compiler Is Configured in Instatic
Instatic enables the compiler project-wide through the Babel preset defined in vite.config.ts. The configuration imports reactCompilerPreset from @babel/preset-react-compiler and appends it to the Babel presets array:
import { reactCompilerPreset } from '@babel/preset-react-compiler'
export default defineConfig({
// …
plugins: [
// other Vite plugins …
],
// Babel is wired via Vite’s `babel` option
babel: {
presets: [reactCompilerPreset()],
},
})
This setup instructs Babel to process every component with the React Compiler, which performs static memoization automatically. The compiler analyzes dependency graphs at build time to determine when values, functions, and component outputs can be safely cached between renders.
Automatic Memoization and the "No Manual Memoization" Rule
When the React Compiler is active, it generates optimal memoization automatically for all functions, values, and components. Consequently, the Instatic codebase follows a strict "no manual memoization" guideline—developers must avoid useMemo, useCallback, and React.memo unless they fall under one of the documented exceptions. This rule reduces code noise and prevents redundant optimization efforts that the compiler already handles.
Standard components in src/ui/components/** rely entirely on this automatic behavior:
// src/ui/components/Button/Button.tsx
export function Button({ children, onClick }: Props) {
// No useCallback / useMemo needed – the compiler will memoize `onClick`
return <button onClick={onClick}>{children}</button>
}
The Three Exceptions to Automatic Memoization
The docs/reference/react-compiler.md file documents three specific scenarios where manual memoization remains necessary.
Stable Function Identity for Hook Dependencies
When a function is listed in a useEffect dependency array (or similar hook), you must wrap it with useCallback to satisfy the react-hooks/exhaustive-deps linter rule, even though the compiler handles the actual memoization. This ensures the linter correctly tracks dependencies without flagging missing entries.
export function AutoSave({ save }: { save: () => Promise<void> }) {
const stableSave = useCallback(() => {
void save()
}, [save]) // <- required for useEffect deps
useEffect(() => {
const id = setInterval(stableSave, 5000)
return () => clearInterval(id)
}, [stableSave])
return null
}
Critical List-Rendered Components
Components rendered inside large lists may be wrapped with React.memo to skip unnecessary re-renders, but only after rigorous performance validation proves the optimization necessary. This exception targets heavy components like those rendering complex SVGs or costly calculations where the compiler's automatic memoization might not provide sufficient isolation at the list item level.
// ListItem renders a complex SVG; memoised to avoid re‑renders
export const ListItem = React.memo(function ListItem({ item }: { item: Item }) {
return <div>{item.title}</div>
})
Compiler Escape-Hatch for Unanalyzable Code
If the compiler cannot process a function due to dynamic code generation or other edge cases, developers must disable the compiler for that specific block using the /* @react-compiler-disable */ directive. In these isolated blocks, manual memoization can be retained as needed since the compiler is not managing the component.
/* @react-compiler-disable */
export function UnsafeDynamicFunction({ fn }: { fn: Function }) {
// Runtime‑generated code that the compiler cannot analyze
return <button onClick={() => fn()}>Run</button>
}
Enforcement Through Linting
The project enforces these rules through eslint.config.js and react-doctor.config.json, which flag violations of the "no manual memoization" policy. These configurations ensure developers do not accidentally introduce useMemo or useCallback in standard components where the React Compiler already provides optimization.
Summary
- Instatic enables React Compiler memoization globally via the
reactCompilerPresetinvite.config.ts, which processes all components through Babel at build time. - The codebase prohibits manual memoization (
useMemo,useCallback,React.memo) because the compiler handles optimization automatically. - Three exceptions allow manual memoization: stabilizing callbacks for hook dependency arrays, memoizing performance-critical list items after validation, and using the
/* @react-compiler-disable */escape-hatch for code the compiler cannot analyze. - Linting rules in
eslint.config.jsenforce the manual memoization restrictions across thesrc/ui/components/**directory.
Frequently Asked Questions
Can I use useMemo and useCallback in Instatic?
Generally, no. According to the Instatic source code, you should avoid useMemo and useCallback in standard components because the React Compiler automatically memoizes values and functions. The only permitted uses are the three documented exceptions: stabilizing functions for hook dependencies, optimizing validated list items, and code blocks where the compiler is explicitly disabled.
How do I disable the React Compiler for a specific component?
Use the /* @react-compiler-disable */ comment immediately before the function definition. This escape-hatch tells Babel to skip compilation for that specific block, allowing you to implement manual memoization strategies for code that the compiler cannot statically analyze, such as runtime-generated functions.
Where is the React Compiler configured in the Instatic codebase?
The compiler is configured in vite.config.ts at the repository root. The file imports reactCompilerPreset from @babel/preset-react-compiler and includes it in the Babel presets array, ensuring all components in the project are processed automatically without requiring individual configuration.
Why does Instatic require useCallback for hook dependencies despite the compiler?
While the React Compiler handles the actual memoization, the react-hooks/exhaustive-deps ESLint rule cannot detect that the compiler will stabilize function identities. Wrapping callbacks with useCallback satisfies the linter's requirement that all dependencies be explicitly declared in hook arrays, preventing false positive linting errors while maintaining correct dependency tracking.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →