Quick Start: Single-Page Application¶
In SPA mode (mode="spa"), Litestar owns your JSON API routes and serves a transformed index.html shell for all unmatched non-API GET routes via AppHandler. Your frontend router (react-router, vue-router, or TanStack Router) handles client-side navigation while TypeGenConfig generates a fully typed TypeScript SDK from Litestar's OpenAPI schema.
Install Dependencies¶
And in your frontend workspace:
Configure Litestar¶
Register VitePlugin with mode="spa" and enable TypeGenConfig so Litestar exports openapi.json and routes.json on startup:
"""Single-Page Application (SPA) quick-start example with Litestar and Vite."""
from __future__ import annotations
from pathlib import Path
from litestar import Litestar, get
from msgspec import Struct
from litestar_vite import PathConfig, RuntimeConfig, TypeGenConfig, ViteConfig, VitePlugin
class SummaryStats(Struct, frozen=True, gc=False):
"""Dashboard summary payload returned by the JSON API."""
active_users: int
requests_per_second: float
region: str
@get("/api/summary", sync_to_thread=False)
def get_summary() -> SummaryStats:
"""Return typed summary metrics for the SPA frontend."""
return SummaryStats(active_users=128, requests_per_second=412.5, region="us-east-1")
def create_app() -> Litestar:
"""Create a Litestar application configured for SPA mode."""
vite_config = ViteConfig(
mode="spa",
dev_mode=True,
paths=PathConfig(root=Path(), resource_dir=Path("src"), bundle_dir=Path("public/build")),
runtime=RuntimeConfig(start_dev_server=False),
types=TypeGenConfig(generate_sdk=True, generate_routes=True),
)
return Litestar(route_handlers=[get_summary], plugins=[VitePlugin(config=vite_config)])
def main() -> None:
"""Verify the SPA example application constructs cleanly."""
app = create_app()
plugin = app.plugins.get(VitePlugin)
assert plugin.config.mode == "spa"
assert SummaryStats(active_users=128, requests_per_second=412.5, region="us-east-1").active_users == 128
__all__ = ("SummaryStats", "create_app", "get_summary", "main")
Configure Vite¶
Point litestar-vite-plugin at your application entrypoint (src/main.tsx or src/main.ts). All path, port, and typegen settings are automatically synchronized from .litestar.json:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import litestar from "litestar-vite-plugin";
export default defineConfig({
plugins: [
react(),
litestar({
input: ["src/main.tsx"],
}),
],
});
Create the HTML Shell and Run¶
Place index.html in your project root (or resource_dir). In mode="spa", AppHandler automatically injects @vite/client (and the React Fast Refresh preamble when is_react=True) in development, and swaps in hashed manifest.json bundle tags in production:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Litestar SPA</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Start the unified development server:
Open http://127.0.0.1:8000 in your browser:
GET /api/summaryreturns the JSONSummaryStatsresponse from Litestar.GET /(and any client route like/settings) serves the transformedindex.htmlshell./src/main.tsxand/vite-hmrstream throughViteProxyMiddlewareover port8000.