-`M3Root` renders `<html>` with the mode class, `color-scheme` and the scheme's colour roles inline, so the first byte is themed.
- With `readCookie={false}` (or `mode="system"`) the shell is static: Cache Components and a static export need that. The inline
pre-paint script reads the `m3-theme` cookie in the browser and sets `.dark` / `.light` before first paint, so a saved
preference never flashes. Without `readCookie={false}` the layout reads the cookie on the server (dynamic).
- A Server Component that reads the cookie (`readM3ThemeMode()`) belongs behind `<Suspense>`; `proxy.ts` guarantees the cookie
exists on the first request, so the server and the client agree from the first byte (`examples/app/app/theme`).
- The client provider is a few hundred bytes (cookie, `.dark`, `.light`, `data-m3-theme`, `color-scheme`). The Material You
engine (material-color-utilities, about 90 KB) is a separate chunk, loaded only when the seed, variant or contrast differ from the
scheme the server inlined. It replaces the `@aphrody/m3-theme/react` provider: the client entry is 94 KB before and 3.3 KB now
(`docs/reports/M3-NEXT-BUNDLE.md`).
## Fonts
`next/font` needs literal arguments in the module that calls it, so a library cannot load fonts for you. Load them in
`layout.tsx` and hand the CSS variable to `M3Root`, which points the M3 reference typefaces at it:
```tsx
constroboto=localFont({
src:"./RobotoFlex.woff2",
variable:"--font-roboto-flex",
weight:"100 1000",
});
<M3Root
className={roboto.variable}
typefaces={{
brand:"var(--font-roboto-flex), sans-serif",
plain:"var(--font-roboto-flex), sans-serif",
}}
/>;
```
`@aphrody/m3-fonts/fonts.css` is the alternative: `@font-face` rules for the M3 typefaces, imported from the global stylesheet.
## Tauri static export
`withM3(config, { tauri: true })` (or `"auto"`: only when the Tauri CLI runs the build) sets `output: "export"`, unoptimized
images, trailing slashes, `cacheComponents: false` and `NEXT_PUBLIC_M3_STATIC=1`, so `M3Root` stops reading the cookie.
`TAURI_DEV_HOST` adds the dev `assetPrefix` a phone needs. A static export has no `proxy.ts`, route handlers (unless
`force-static`) or server cookies: the preference lives in the cookie the client writes. Point Tauri's `frontendDist` at `../out`.
`examples/tauri` imports only the sheets of the three components it renders.
## On Bun, no Node
`bun --bun next build` runs Next itself on Bun, but Turbopack spawns `node` for its PostCSS and loader workers and fails when that
is not Bun (`node: --bun is not allowed in NODE_OPTIONS`). `m3-next` puts a `node` that is Bun first on PATH, so the whole
build, workers included, runs on one runtime and Turbopack stays the bundler (no `--webpack` fallback: `examples/app`, cold, back to back on one machine: 4.5 s with
Turbopack, 15.3 s with `--webpack`). `m3-next dev`, `build` and `start` pass their arguments to `next`.
### Moved to aphrody-labs/bun
The runner (`bunNodeShim`, `nextBin`, `nextCommand`) and its tests now live in `@aphrody/next-bun`
(`packages/bun-next/lib/run.js` and `test/integration/next-bun/` of aphrody-labs/bun, also `next-bun dev|build|start`).
`bin/m3-next.ts` re-exports them and keeps the `m3-next` usage. The dependency is the published