Million.js replaces React’s virtual DOM diffing with a fine-grained block-based update system — block(Component) wraps a component so Million handles its DOM updates directly, bypassing React reconciliation for static-structure components. <For each={items}> renders lists 70% faster than items.map(item => <Component />). The million/compiler Vite or Babel plugin automatically converts compatible components to blocks at build time without manual wrapping. <Slot> marks dynamic positions inside blocks for React to manage. Million works as a drop-in addition to existing React apps — no rewrite required. Components with conditional rendering or dynamic structure fall back to React’s virtual DOM automatically. Million is most effective on data-heavy UIs: tables, dashboards, and large lists where the DOM structure is stable but data changes frequently. Claude Code identifies block candidates, adds million/compiler to build config, wraps high-frequency components with block(), and replaces array maps with For components.
CLAUDE.md for Million.js
## Million.js Stack
- Version: million >= 3.1
- Block: import { block } from "million/react"; const TableRow = block(TableRowComponent)
- For: import { For } from "million/react"; <For each={items}>{item => <Row item={item} />}</For>
- Compiler: add million/compiler to vite.config.ts — auto-optimizes compatible components
- Slot: import { Slot } from "million/react" — dynamic content inside blocks
- Rules: blocks can't use hooks that cause re-renders at the slot level; static structure required
- Profiles: Chrome DevTools > Performance > record interactions on large lists
- Fallback: non-block components work as-is — gradual adoption
Vite Plugin Setup
// vite.config.ts — Million.js compiler plugin
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import million from "million/compiler"
export default defineConfig({
plugins: [
// Million compiler BEFORE React plugin
million.vite({
auto: true, // Automatically optimize compatible components
mute: false, // Log optimized components
}),
react(),
],
})
// next.config.ts — Million.js for Next.js
import million from "million/compiler"
import type { NextConfig } from "next"
const nextConfig: NextConfig = {
reactStrictMode: true,
}
export default million.next(nextConfig, {
auto: { rsc: false }, // Skip React Server Components (already optimized)
})
Manual Block Components
// components/data/PriceRow.tsx — block for high-frequency price updates
import { block, For } from "million/react"
// Good block candidate: static structure, only data changes
interface PriceData {
symbol: string
price: number
change: number
changePercent: number
volume: number
}
// Wrap the component — Million handles DOM updates
const PriceRowBlock = block(function PriceRow({ data }: { data: PriceData }) {
const isPositive = data.change >= 0
return (
<tr className="border-b hover:bg-muted/50 text-sm">
<td className="px-4 py-2 font-mono font-semibold">{data.symbol}</td>
<td className="px-4 py-2 tabular-nums">${data.price.toFixed(2)}</td>
<td className={`px-4 py-2 tabular-nums ${isPositive ? "text-green-600" : "text-red-600"}`}>
{isPositive ? "+" : ""}{data.change.toFixed(2)}
</td>
<td className={`px-4 py-2 tabular-nums ${isPositive ? "text-green-600" : "text-red-600"}`}>
{isPositive ? "+" : ""}{data.changePercent.toFixed(2)}%
</td>
<td className="px-4 py-2 tabular-nums text-muted-foreground">
{(data.volume / 1_000_000).toFixed(1)}M
</td>
</tr>
)
})
// Table using For for fast list rendering
export function PriceTable({ prices }: { prices: PriceData[] }) {
return (
<table className="w-full">
<thead>
<tr className="text-left text-xs font-medium text-muted-foreground uppercase">
<th className="px-4 py-2">Symbol</th>
<th className="px-4 py-2">Price</th>
<th className="px-4 py-2">Change</th>
<th className="px-4 py-2">%</th>
<th className="px-4 py-2">Volume</th>
</tr>
</thead>
<tbody>
{/* For is significantly faster than .map() for long lists */}
<For each={prices}>
{(price) => <PriceRowBlock data={price} key={price.symbol} />}
</For>
</tbody>
</table>
)
}
Blocks with Slot for Dynamic Content
// components/data/DataCard.tsx — block with Slot for dynamic children
import { block, Slot } from "million/react"
interface DataCardProps {
title: string
value: string
change: number
// Dynamic content via Slot — React manages this part
icon?: React.ReactNode
actions?: React.ReactNode
}
// Static layout as block — Slot marks React-managed regions
const DataCard = block(function DataCardInner({
title,
value,
change,
icon,
actions,
}: DataCardProps) {
return (
<div className="rounded-xl border bg-card p-5">
<div className="flex items-center justify-between mb-3">
<span className="text-sm font-medium text-muted-foreground">{title}</span>
{/* Slot wraps dynamic React content */}
<Slot>{icon}</Slot>
</div>
<p className="text-2xl font-bold tabular-nums">{value}</p>
<div className="flex items-center justify-between mt-2">
<span className={`text-sm ${change >= 0 ? "text-green-600" : "text-red-600"}`}>
{change >= 0 ? "↑" : "↓"} {Math.abs(change).toFixed(1)}%
</span>
<Slot>{actions}</Slot>
</div>
</div>
)
})
// Dashboard reusing DataCard block
export function MetricsDashboard({ metrics }: { metrics: typeof mockMetrics }) {
return (
<div className="grid grid-cols-4 gap-4">
<For each={metrics}>
{(metric) => (
<DataCard
key={metric.id}
title={metric.title}
value={metric.value}
change={metric.change}
icon={<MetricIcon type={metric.type} />}
actions={<TrendChart data={metric.trend} />}
/>
)}
</For>
</div>
)
}
function MetricIcon({ type }: { type: string }) {
// Dynamic — handled by React, not affected by block
const icons: Record<string, string> = {
revenue: "💰", users: "👥", orders: "📦", conversion: "📈",
}
return <span className="text-lg">{icons[type] ?? "📊"}</span>
}
function TrendChart({ data }: { data: number[] }) {
// Small sparkline — React renders this normally
const max = Math.max(...data)
const min = Math.min(...data)
const range = max - min || 1
return (
<svg width="60" height="20" className="text-primary">
<polyline
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
points={data.map((v, i) => `${(i / (data.length - 1)) * 60},${20 - ((v - min) / range) * 18}`).join(" ")}
/>
</svg>
)
}
const mockMetrics = [
{ id: "1", title: "Revenue", value: "$48,295", change: 12.5, type: "revenue", trend: [30, 45, 38, 52, 48, 65, 72] },
{ id: "2", title: "Users", value: "2,847", change: 8.1, type: "users", trend: [20, 28, 35, 30, 42, 45, 51] },
]
Auto Mode with Opt-Out
// With million/compiler auto: true, compatible components are optimized automatically.
// Use the "use no memo" directive to opt out specific components.
// components/ComplexAnimated.tsx — not suitable for Million (has dynamic structure)
"use no memo" // Opts this component OUT of auto-optimization
import { useState } from "react"
export function AnimatedAccordion({ items }: { items: { title: string; content: string }[] }) {
const [open, setOpen] = useState<string | null>(null)
// Dynamic structure (conditional children) — NOT a good block candidate
return (
<div className="space-y-2">
{items.map(item => (
<div key={item.title} className="border rounded-lg">
<button
onClick={() => setOpen(open === item.title ? null : item.title)}
className="w-full flex justify-between items-center px-4 py-3 text-left"
>
{item.title}
<span className={`transition-transform ${open === item.title ? "rotate-180" : ""}`}>▼</span>
</button>
{open === item.title && (
<div className="px-4 pb-3 text-sm text-muted-foreground">
{item.content}
</div>
)}
</div>
))}
</div>
)
}
For the React Compiler (Babel) alternative when automatic memoization via the official React Compiler (babel-plugin-react-compiler) is preferred — React Compiler from the React team automatically adds useMemo and useCallback without any component wrapping, working on a broader range of component patterns than Million’s block approach, see the React Compiler guide. For the virtualization alternative when the performance bottleneck is rendering thousands of rows that are off-screen — @tanstack/react-virtual or react-window render only visible items, eliminating the need for block optimization for very large lists, see the virtual list guide. The Claude Skills 360 bundle includes Million.js skill sets covering block components, For lists, and compiler setup. Start with the free tier to try React performance optimization generation.