Server-Side Rendering¶
Litestar Vite provides unified Server-Side Rendering (SSR) for both Inertia.js pages (mode="hybrid") and UI component islands/fragments (mode="template"). Instead of requiring you to manage a separate Node HTTP server process, Litestar Vite manages the SSR IPC lifecycle automatically in both development and production.
How Dev and Prod SSR Differ¶
| Stage | Development (dev_mode=True) |
Production (dev_mode=False) |
Production without JS Runtime (ssr_transport="wasm") |
|---|---|---|---|
| IPC Transport | TCPStreamIPCTransport (POST /__litestar_ssr__ over pooled httpx2) |
StdioIPCTransport (persistent Node/Bun/Deno subprocess over stdin/stdout NDJSON pipes) |
WasmIPCTransport (embedded QuickJS engine on a dedicated worker thread via litestar-vite[wasm]) |
| Module Evaluation | Vite 7+ RunnableDevEnvironment.runner.import() (ModuleRunner with recursive importer HMR invalidation) |
Self-contained SSR bundle (bootstrap/ssr/ssr.js) loaded once at worker startup |
Self-contained ES-module ssr.js (ssr.noExternal: true) loaded via Context.module |
| Build Step Required? | No — edits to .tsx/.vue/.svelte files reflect immediately on the next refresh |
Yes — litestar assets build compiles both the client bundle and the SSR bundle |
Yes — litestar assets build inlines dependencies into ssr.js |
| Fault Tolerance | SSRCircuitBreaker + automatic fallback to client-side rendering (fallback_to_client=True) |
SSRCircuitBreaker + bounded stderr ring buffer + automatic worker restart |
SSRCircuitBreaker + per-request set_time_limit(timeout) + automatic context rebuild on timeout |
Self-Contained Production SSR & WasmIPCTransport¶
When building the production SSR bundle (ssrOutputDir), litestar-vite-plugin defaults ssr.noExternal to true so ssr.js is self-contained and can run under Node, Bun, Deno, or the experimental in-process WasmIPCTransport (uv add "litestar-vite[wasm]").
Production transport selection is controlled by RuntimeConfig.ssr_transport ("auto", "stdio", or "wasm") and resolved by resolve_ssr_transport():
"auto"(default): An explicitInertiaSSRConfig.commandwins, followed by the configured JavaScript runtime (node,bun, ordeno), and finallyWasmIPCTransportwhenquickjsis installed."stdio": Spawns the compiledssr.jsbundle in a child process (node,bun run, ordeno run --allow-read --allow-env). The worker (startSsrWorkerfromlitestar-vite-plugin/ssr-worker) readsprocess.stdinusing a streamingTextDecoder(withoutnode:readline) and exits with status0oncestdinreaches EOF and in-flight renders drain."wasm"(experimental): Evaluatesssr.jsinside an embedded QuickJS engine on a dedicated worker thread with no external JS runtime required on the host. Each request enforces a CPU time limit (timeout) and automatically rebuilds the context if a render times out.
Enabling Server-Side Rendering¶
Enable SSR by passing InertiaConfig(ssr=True) (or an explicit InertiaSSRConfig instance):
"""Server-Side Rendering (SSR) configuration for development and production IPC."""
from __future__ import annotations
from pathlib import Path
from litestar import Litestar, get
from litestar_vite import InertiaConfig, InertiaSSRConfig, PathConfig, RuntimeConfig, ViteConfig, VitePlugin
@get("/articles/architecture", component="Articles/Show", sync_to_thread=False)
def show_article() -> dict[str, str]:
"""Return Inertia props rendered on the server during initial page load."""
return {"slug": "architecture", "title": "Architecture Deep Dive"}
def create_app(*, dev_mode: bool = True) -> Litestar:
"""Configure SSR with automatic dev RunnableDevEnvironment and prod stdio worker."""
vite_config = ViteConfig(
mode="hybrid",
dev_mode=dev_mode,
paths=PathConfig(
resource_dir=Path("resources"), bundle_dir=Path("public/build"), ssr_output_dir=Path("bootstrap/ssr")
),
inertia=InertiaConfig(
root_template="index.html", ssr=InertiaSSRConfig(enabled=True, timeout=2.0, fallback_to_client=True)
),
runtime=RuntimeConfig(start_dev_server=False),
)
return Litestar(route_handlers=[show_article], plugins=[VitePlugin(config=vite_config)])
def main() -> None:
"""Verify SSR configuration resolves cleanly in development mode."""
app = create_app(dev_mode=True)
plugin = app.plugins.get(VitePlugin)
assert isinstance(plugin.config.inertia, InertiaConfig)
assert isinstance(plugin.config.inertia.ssr, InertiaSSRConfig)
assert plugin.config.inertia.ssr.enabled is True
__all__ = ("create_app", "main", "show_article")
HTML Slot Splicing¶
When SSR returns { head: string[], body: string }, Litestar Vite splices the rendered markup into your root_template using _InertiaSlots:
- If your
index.htmlincludes<!--inertia-head-->inside<head>and<!--inertia-body-->inside<div id="app">, Litestar Vite replaces those exact markers right-to-left in a single pass. - If
<!--inertia-body-->is absent, Litestar Vite automatically locates<div id="app"></div>(orInertiaSSRConfig.target_selector) and injects the rendered SSR HTML inside the container while placing<script type="application/json" data-page="app">immediately before it.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!--inertia-head-->
</head>
<body>
<div id="app"><!--inertia-body--></div>
<script type="module" src="/resources/main.tsx"></script>
</body>
</html>
Circuit Breaker & Client-Side Fallback¶
Production SSR should never take down your Python application if a third-party frontend component accesses window unguarded. InertiaSSRConfig configures SSRCircuitBreaker by default:
timeout=2.0: Any SSR request exceeding 2 seconds is cancelled cleanly.circuit_breaker_failure_threshold=3: After 3 consecutive failures, the circuit trips toOPENand bypasses IPC forcircuit_breaker_reset_timeout=30.0seconds before probing with a singleHALF_OPENrequest.fallback_to_client=True: Whenever SSR fails or the circuit isOPEN, Litestar logs a warning and returns the standard client-rendered HTML shell (200 OK) so the browser mounts the page seamlessly.