← Back to articles

React Compiler vs Million.js vs Preact Signals: Best React Performance (2026)

React's re-rendering model is its biggest performance footprint. Three different solutions tackle this problem: React Compiler (auto-memoization), Million.js (virtual DOM replacement), and Preact Signals (fine-grained reactivity). Here's how they compare.

Quick Comparison

FeatureReact CompilerMillion.jsPreact Signals
ApproachAuto-memoization at buildReplace virtual DOMFine-grained reactivity
Official ReactYes (Meta)No (community)No (Preact team)
SetupBabel pluginBabel plugin + compilerPackage install
Code changesNone (automatic)Block componentsSignal primitives
Performance gain2-10x fewer re-renders70%+ faster DOM updatesSurgical updates
React compatibility100%~95%~90%
Production readyYes (2026)ExperimentalStable
Bundle impact~0 (build-time)~4KB~1KB

React Compiler: The Official Solution

React Compiler (formerly React Forget) automatically adds memoization to your React components at build time. No code changes required.

How It Works

React Compiler analyzes your component code and automatically inserts useMemo, useCallback, and React.memo equivalents where beneficial. You write normal React code; the compiler optimizes it.

// You write this (normal React):
function TodoList({ todos, filter }) {
  const filtered = todos.filter(t => t.status === filter);
  return filtered.map(t => <TodoItem key={t.id} todo={t} />);
}

// Compiler produces optimized version that:
// - Memoizes `filtered` (only recomputes when todos/filter change)
// - Memoizes TodoItem renders (skips unchanged items)

Strengths

  • Zero code changes. Add the Babel plugin and your entire app is optimized.
  • Official Meta support. This is React's future. Instagram uses it in production.
  • Correct by default. The compiler understands React's rules and applies safe optimizations.
  • No learning curve. Your team doesn't need to learn new APIs.
  • Works with existing code. Drop-in optimization for any React codebase.

Weaknesses

  • Compilation time. Adds to build time (typically 10-20%).
  • Not a silver bullet. Reduces unnecessary re-renders but doesn't change React's fundamental model.
  • Debugging. Optimized code can be harder to trace in devtools.
  • Rules of React. Your code must follow React's rules (pure render functions, proper hook usage). Non-compliant code won't be optimized.

Best For

Every React project. If you're on React 19+, enable the compiler. There's almost no downside.

Million.js: Virtual DOM Replacement

Million.js takes a radical approach — it replaces React's virtual DOM diffing with a faster block-based approach for specific components.

How It Works

Million.js identifies components that can use its "block" optimization — static structure with dynamic values — and bypasses React's virtual DOM entirely for those components.

import { block } from 'million/react';

// Wrap performance-critical components
const TodoItem = block(function TodoItem({ todo }) {
  return (
    <div className="todo-item">
      <span>{todo.title}</span>
      <span>{todo.status}</span>
    </div>
  );
});

Strengths

  • Dramatic speedups. Up to 70% faster DOM updates for block-compatible components.
  • Great for lists. Rendering large lists (1000+ items) is where Million.js shines.
  • Compiler mode. Automatic block detection without manual wrapping.
  • Small footprint. ~4KB gzipped.
  • Easy to adopt. Wrap components selectively. No all-or-nothing.

Weaknesses

  • Limited compatibility. Not all components can be "blocked." Dynamic children, refs, and complex patterns may not work.
  • Experimental. Still evolving. Not recommended for production-critical paths.
  • Extra dependency. Another build tool in your chain.
  • Diminishing returns with React Compiler. If React Compiler eliminates most re-renders, Million.js provides less incremental benefit.

Best For

Applications with very large lists or tables where rendering performance is measurably impacting UX. Benchmark before adopting.

Preact Signals: Fine-Grained Reactivity

Signals bring fine-grained reactivity to React — only the specific DOM nodes that use a signal's value update when it changes. No component re-renders at all.

How It Works

import { signal, computed } from '@preact/signals-react';

const count = signal(0);
const doubled = computed(() => count.value * 2);

function Counter() {
  // This component NEVER re-renders
  // Only the text node updates when count changes
  return (
    <div>
      <p>Count: {count}</p>
      <p>Doubled: {doubled}</p>
      <button onClick={() => count.value++}>+1</button>
    </div>
  );
}

Strengths

  • Surgical precision. Only the specific text node or attribute updates. Zero component re-renders.
  • Simple mental model. Create a signal, use it anywhere. No memo, no useCallback, no dependency arrays.
  • Tiny bundle. ~1KB gzipped.
  • Global state built-in. Signals work across components without Context or state management libraries.
  • Computed values. Derived state that auto-updates. No useMemo needed.

Weaknesses

  • Not official React. Uses React internals in ways the React team hasn't endorsed.
  • Compatibility concerns. May break with future React updates.
  • Different paradigm. Team members need to learn reactive programming concepts.
  • Limited ecosystem. Third-party React libraries expect useState/useReducer, not signals.
  • React Compiler conflict. The compiler optimizes the useState model. Signals bypass it entirely.

Best For

Performance-critical components where even memoized re-renders are too expensive. Real-time dashboards, financial tickers, and high-frequency update UIs.

Performance Comparison

For a list of 10,000 items with frequent updates:

OperationPlain ReactReact CompilerMillion.jsPreact Signals
Initial render120ms110ms80ms100ms
Single item update15ms5ms2ms0.1ms
Filter change80ms30ms25ms15ms
Scroll (virtualized)8ms6ms4ms3ms

Note: These are illustrative. Real performance depends on your specific component structure and update patterns. Always benchmark your actual app.

Can You Combine Them?

CombinationWorks?Recommended?
React Compiler + Million.jsYesMaybe (diminishing returns)
React Compiler + SignalsPartiallyNo (conflicting models)
Million.js + SignalsYesOnly if you understand both

Recommendation: Start with React Compiler (free optimization). Only add Million.js or Signals if profiling shows specific bottlenecks that the compiler doesn't address.

Decision Framework

  1. Start with React Compiler. Enable it. Free performance. No code changes.
  2. Profile your app. Use React DevTools Profiler. Find actual bottlenecks.
  3. If large lists are slow → evaluate Million.js for those specific components.
  4. If high-frequency updates are slow → evaluate Signals for those specific components.
  5. If everything is fine → stop. Don't optimize what isn't broken.

FAQ

Do I need any of these for a typical app?

React Compiler: yes, enable it (free optimization). Million.js and Signals: probably not unless you have measurable performance issues.

Will React Compiler make useMemo and useCallback obsolete?

Effectively, yes. The compiler adds them automatically where beneficial. You can stop writing them manually.

Are Signals the future of React?

Unlikely. The React team has chosen compilation over reactive primitives. Signals work well but aren't on React's official roadmap.

Does Million.js work with Next.js?

Yes, with configuration. The compiler plugin integrates with Next.js's Babel/SWC pipeline.

The Verdict

  • React Compiler: Enable on every project. Free optimization, zero effort.
  • Million.js: Consider for extreme list/table performance after profiling.
  • Preact Signals: Consider for real-time, high-frequency update UIs after profiling.

For 95% of React apps in 2026, React Compiler alone is sufficient. The other tools are specialized solutions for edge cases that most apps never hit.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.