# SSR Architecture Mizan's SSR adapter is a **Django template backend** rendering through an **embedded V8 engine inside the Mizan Rust binary** (`cores/mizan-rust-ssr`). No external JS runtime serves requests — node and bun are build-time tools only, and no frontend adapter imports an SSR runtime or meta-framework. ```python TEMPLATES = [ { 'BACKEND': 'mizan.ssr.MizanTemplates', 'DIRS': [BASE_DIR / 'frontend'], 'OPTIONS': { 'bundles': BASE_DIR / 'frontend' / '.mizan' / 'ssr', 'timeout': 5, }, } ] ``` `render(request, 'components/Hello.tsx', context)` renders a React component instead of a Django/Jinja2 template. **The template name IS a `.tsx`/`.jsx` file path**, resolved against `DIRS`; `get_template` returns a `MizanTemplate` wrapping the resolved component. The context dict becomes the component's props (`request` and `csrf_token` stripped). Rendered output is wrapped in `
` plus a `` hydration payload the Mizan kernel hydrates from — server-validated data crossing one way, as props. ## The engine `SsrEngine` (`cores/mizan-rust-ssr`) embeds a `deno_core` V8 runtime composed with `deno_web`, so the web-platform globals react-dom touches (`TextEncoder`/`TextDecoder`, `MessagePort`, timers) are real implementations, not shims — a partial polyfill is silent-failure-shaped; it passes until a render path hits the gap. - **The bundle is built, the engine evals it once.** `mizan-generate`'s SSR bundling step compiles each SSR entry component together with `react-dom/server.browser` into a self-contained bundle that assigns `globalThis.renderApp`. The engine evals the trusted bundle at construction and calls `renderApp(props)` per request. - **Props never enter evaluated source.** Per-render data crosses as a `v8::json::parse`d value passed as a function argument, so a prop string has no source to break out of — code injection is structurally absent, not filtered. - **One isolate per engine, one engine per worker thread.** V8's Locker constraint means an engine is not `Send`; the Django side holds one engine per (worker thread, bundle) pair. ## AFI boundary | Side | Responsibility | |---|---| | Backend adapter (`mizan/ssr`) | Django template-backend interface; resolves the component path to its built bundle; gathers props; wraps output for hydration | | Rust binary (`SsrEngine`, via PyO3) | Holds the evaled bundle; parses props to a V8 value; `renderApp(props)` → HTML string | | Build step (`mizan-generate`) | Bundles component + `react-dom/server.browser` into the `bundles` directory; the only place node/bun run | The Python side binds the engine through PyO3 — in-process FFI, no subprocess, no JSON-RPC framing. This is the Core-Consolidation shape: behavior lives in the binary; languages bind to it. ## No framework runtimes No frontend adapter imports an SSR runtime or meta-framework — Next, Nuxt, SvelteKit, React Server Components / Flight. Those add the client-payload-deserialization step behind the pre-auth RCE class (CVE-2025-55182); the AFI already provides the typed, Pydantic-validated, one-way version they would otherwise import unsafely. The guarantee is enforced, not assumed: `cores/mizan-rust-ssr/tests/no_rsc.rs` scans the authored SSR source and its dependencies for the forbidden token set (`react-server-dom`, `renderToReadableStream`, `renderToPipeableStream`, `createFromReadableStream`/`Fetch`, `use server`, `next/`, `nuxt`, `@sveltejs/kit`) and fails on presence — re-entry is loud. ## Why template backend - Django's template system is swappable by design (batteries included, but replaceable). - Django developers already use `render(request, template, context)` — no new API to learn. - URL routing, views, middleware, auth — all unchanged.