How React Compiler Memoization Works in the Instatic Admin App

React Compiler automatically memoizes components in Instatic's admin app, making manual useMemo, useCallback, and React.memo unnecessary except for three specific exceptions.

Instatic by CoreBunch leverages the React Compiler to eliminate manual optimization clutter across its admin interface. The compiler runs in infer mode, automatically inserting memoization caches for every component and hook it recognizes. This approach ensures peak performance without requiring developers to manually wrap functions or components in memoization logic.

Compiler Configuration and Infer Mode

The React Compiler is enabled globally via the Vite configuration in vite.config.ts (lines 39‑45). According to the source code, the compiler automatically constructs a memo-cache for every function it successfully analyzes, eliminating the need for handwritten memoization patterns.

The project specifically configures the compiler to run in infer mode, which proved essential for compatibility with Instatic's state management. In this mode, the compiler works cleanly with the immutable snapshots produced by Mutative, avoiding the "cannot perform 'get' on a revoked proxy" errors that previously occurred (lines 48‑52). This integration is critical because the admin UI relies heavily on Zustand stores processed through Mutative for state updates.

The Three Exceptions to Manual Memoization

Despite global auto-memoization, Instatic enforces a strict policy allowing manual memoization only in three sanctioned scenarios. All other instances are flagged as drift and must be removed.

Exception 1: Hook Dependency Arrays

When a function serves as a dependency for useEffect, useMemo, or useCallback, you must wrap it in useCallback to maintain stable identity. The static react-hooks/exhaustive-deps ESLint rule cannot see the compiler's runtime memoization, so explicit wrapping is required to satisfy the linter.

// ✅ Correct: useCallback kept for dependency array stability
const handlePointerDown = useCallback(
  (side: 'left' | 'right') => (event: ReactPointerEvent<HTMLDivElement>) => {
    // …implementation…
  },
  [effectiveWidth, activeBreakpoint],
);
// File: src/admin/pages/site/canvas/CanvasLiveSurface.tsx (lines 14‑25)

Exception 2: Hot List Rendering with React.memo

Components rendered inside large lists or recursive structures (such as node renderers) may use React.memo to prevent O(N) re-renders. This exception applies only to components that would otherwise cause significant performance bottlenecks during list updates.

// ✅ Allowed exception: React.memo on a hot list renderer
export const NodeRenderer = React.memo(function NodeRenderer({ node }) {
  // …render logic…
});
// Typical file: src/admin/pages/site/canvas/NodeRenderer.tsx

Exception 3: Compiler Escape Hatches and Ref Access

Functions that access refs during render or utilize patterns the compiler cannot analyze require either useCallback or the "use no memo" directive. This includes complex closures that trigger eslint-plugin-react-compiler warnings or cases requiring /* eslint-disable react-compiler/react-compiler */ comments.

// ✅ Escape-hatch: compiler cannot compile this function
/* eslint-disable react-compiler/react-compiler */
function specialCase() {
  // complex closure or ref access during render
}
/* eslint-enable react-compiler/react-compiler */

Enforcement Through Linting and CI

Instatic enforces these rules via eslint-plugin-react-compiler combined with eslint-plugin-react-hooks (configured in eslint.config.js). When bun run lint executes, any manual memoization found outside the three exceptions is flagged as policy drift. The configuration distinguishes legitimate exceptions from noise, ensuring only necessary memoization remains in the bundle.

The react-doctor tool also flags manual memoization but only emits warnings because it cannot recognize the three semantic exceptions. This dual-layer enforcement—strict ESLint rules in CI plus advisory warnings from react-doctor—maintains code consistency across the entire admin codebase.

Example Patterns in the Codebase

The src/ui/components/ContextMenu/ContextMenuSubmenu.tsx file demonstrates the standard Instatic pattern: it contains no manual memoization, allowing the compiler to handle all optimization transparently. Conversely, CanvasLiveSurface.tsx shows the approved exception pattern where useCallback preserves function identity specifically for an exhaustive-deps rule requirement.

Removing unnecessary useMemo calls reduces bundle size and eliminates duplicated logic while the compiler guarantees that components only re-render when props or state actually change. The full policy is documented in docs/reference/react-compiler.md and enforced automatically in CI pipelines.

Summary

  • Auto-memoization: The React Compiler in vite.config.ts (infer mode) automatically caches every eligible component and hook.
  • Three exceptions only: Manual memoization is permitted for hook dependencies, hot list renderers with React.memo, and compiler escape-hatches/ref cases.
  • Lint enforcement: eslint-plugin-react-compiler and eslint-plugin-react-hooks flag violations, while react-doctor provides secondary warnings.
  • Mutative compatibility: Infer mode prevents proxy revocation errors when working with Zustand and Mutative immutable updates.
  • Bundle impact: Eliminating manual memoization reduces code size and maintenance overhead without sacrificing performance.

Frequently Asked Questions

When should I use useCallback in the Instatic admin app?

Use useCallback only when the function appears in a dependency array for useEffect, useMemo, or useCallback itself, as the static ESLint rule cannot detect the compiler's automatic memoization. This ensures the exhaustive-deps rule receives a stable function identity.

Why do some components still use React.memo?

Components that render inside hot lists or recursive structures (like node renderers) keep React.memo to prevent O(N) re-render cascades. Each instance requires a one-line justification comment explaining the performance necessity.

How do I disable the React Compiler for a specific function?

Add the "use no memo" directive at the top of the function and wrap it with /* eslint-disable react-compiler/react-compiler */ comments. This escape-hatch is reserved for complex closures or ref-access patterns that the compiler cannot analyze.

What happens if I commit manual memoization that violates the policy?

CI runs bun run lint using eslint-plugin-react-compiler, which will fail the build if it detects manual memoization outside the three sanctioned exceptions. The code must be cleaned up before merging, ensuring consistent optimization across the Instatic admin codebase.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →