From litestar
Connects Vite frontend build pipeline to Litestar backend with dev-server proxying, HMR, manifest resolution, optional Inertia integration and TypeScript type generation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/litestar:litestar-viteThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
`litestar-vite` is the first-party plugin that connects a [Vite](https://vite.dev/) frontend build pipeline to a Litestar backend. It handles dev-server proxying, HMR coordination, manifest resolution for production assets, and (optionally) end-to-end type generation from Litestar OpenAPI to TypeScript.
litestar-vite is the first-party plugin that connects a Vite frontend build pipeline to a Litestar backend. It handles dev-server proxying, HMR coordination, manifest resolution for production assets, and (optionally) end-to-end type generation from Litestar OpenAPI to TypeScript.
The reference apps use spa, template, htmx, hybrid / inertia, framework / ssr / ssg, and external modes. Inertia is one VitePlugin configured with ViteConfig(inertia=InertiaConfig(...)); the plugin wires the internal Inertia integration from that config.
The plugin pairs with the npm package litestar-vite-plugin on the JS side. Both must agree on input, bundleDir, hotFile, and asset URL.
T | None); consumer Litestar app modules MAY use from __future__ import annotations.defineConfig from vite; one vite.config.ts per frontend project.ViteConfig and JS vite.config.ts are a single coupled contract. Change them together or HMR/manifest will silently break.from litestar import Litestar
from litestar_vite import PathConfig, RuntimeConfig, ViteConfig, VitePlugin
vite_config = ViteConfig(
mode="spa",
paths=PathConfig(
resource_dir="resources", # frontend source root
bundle_dir="public", # built assets land here
hot_file="hot", # MUST match vite.config.ts hotFile
),
runtime=RuntimeConfig(port=5173),
dev_mode=True, # toggled by env in production
)
app = Litestar(plugins=[VitePlugin(config=vite_config)])
// vite.config.ts
import { defineConfig } from "vite"
import litestar from "litestar-vite-plugin"
import react from "@vitejs/plugin-react"
export default defineConfig({
clearScreen: false,
publicDir: "public",
server: { cors: true },
plugins: [
react(),
litestar({
input: ["resources/main.tsx", "resources/main.css"],
}),
],
resolve: { alias: { "@": "/resources" } },
})
| Mode | Use For | Key Setup |
|---|---|---|
spa | React, Vue, Svelte, or Analog-powered Angular SPA with a Litestar JSON API backend | dev_mode=True proxies to Vite; manifest in prod |
template | Server-rendered Jinja2/Mako pages with Vite-bundled JS/CSS sprinkles | TemplateConfig + template helpers resolve dev/prod URLs |
htmx | HTMX hypermedia with Jinja templates and Vite-bundled assets | Add litestar-htmx; use hx-* and ls-* attributes |
hybrid / inertia | Inertia.js routes returning JS page components | ViteConfig(inertia=InertiaConfig(...)) on a single VitePlugin |
framework / ssr | Nuxt or SvelteKit SSR | JS framework owns rendering; Litestar provides/proxies API |
framework / ssg | Astro static generation | astro.config.mjs imports litestar-vite-plugin/astro |
external | Angular CLI or another external dev/build process | Litestar coordinates URLs/types while the external tool owns build |
Decision tree:
../litestar-inertia/SKILL.md)ssr alias is accepted)ssg alias is accepted)VitePlugin config (Python)from litestar_vite import (
ViteConfig, VitePlugin, PathConfig, RuntimeConfig, TypeGenConfig,
)
vite_config = ViteConfig(
mode="spa",
dev_mode=False, # True in dev, False in prod (env-toggled)
paths=PathConfig(
root=".",
resource_dir="src",
bundle_dir="public",
static_dir="src/public",
hot_file="hot",
asset_url="/static/",
),
runtime=RuntimeConfig(
port=5173,
host="localhost",
protocol="http",
executor="bun",
),
types=TypeGenConfig(
generate_sdk=True,
generate_routes=True,
generate_schemas=True,
generate_page_props=True,
output="src/generated",
),
)
From litestar-fullstack — src/js/web/vite.config.ts:
import path from "node:path"
import tailwindcss from "@tailwindcss/vite"
import { tanstackRouter } from "@tanstack/router-plugin/vite"
import react from "@vitejs/plugin-react"
import litestar from "litestar-vite-plugin"
import { defineConfig } from "vite"
export default defineConfig({
clearScreen: false,
base: process.env.ASSET_URL ?? "/static/web/",
publicDir: "public",
server: {
cors: true,
port: Number(process.env.VITE_PORT ?? 3006),
},
build: {
outDir: path.resolve(__dirname, "../../py/app/server/static/web"),
emptyOutDir: true,
},
plugins: [
tanstackRouter({ target: "react", autoCodeSplitting: true }),
tailwindcss(),
react(),
litestar({
input: ["src/main.tsx", "src/styles.css"],
bundleDir: path.resolve(__dirname, "../../py/app/server/static/web"),
hotFile: path.resolve(__dirname, "../../py/app/server/static/web/hot"),
}),
],
resolve: { alias: { "@": path.resolve(__dirname, "./src") } },
})
litestar-fullstack/src/py/app/server/plugins.py:
from litestar_vite import VitePlugin
from app import config
vite = VitePlugin(config=config.vite)
The config.vite ViteConfig references the same bundle_dir, hot_file, and resource_dir paths as the JS-side vite.config.ts. They are one coupled contract.
TypeGenConfig(
generate_sdk=True,
generate_routes=True,
generate_schemas=True,
generate_page_props=True, # Inertia only
output="src/generated",
)
| Output | Path | Trigger | Frontend Use |
|---|---|---|---|
openapi.json | output/openapi.json | Whenever OpenAPI schema changes | Source of truth for SDK + schemas |
routes.ts | output/routes.ts | Route table changes | route("name", { params }) typed URL builder |
schemas.ts | output/schemas.ts | Pydantic / msgspec DTO changes | components["schemas"]["User"] typed models |
inertia-pages.json | output/inertia-pages.json | Inertia handlers added/changed | Page-prop typing for Inertia adapters |
CLI:
litestar assets generate-types # one-off generation
litestar assets export-routes # routes.ts only
litestar --app app:app run # generates on startup if enabled
Frontend consumption:
// routes
import { route } from "@/generated/routes"
const url = route("users:get", { id: 123 })
// schemas
import type { components } from "@/generated/schemas"
type User = components["schemas"]["User"]
ViteAssetLoader and Template HelpersAuto-registered Jinja2 globals when a template engine is configured:
| Helper | Use |
|---|---|
{{ vite('resources/main.ts') }} | Render script/link tags for a Vite input; handles dev vs manifest |
{{ vite_hmr() }} | Inject HMR client <script> in dev mode; no-op in prod |
{{ vite_static('favicon.svg') }} | Resolve a static asset URL |
{{ vite_routes() }} | Render inline route metadata for client-side routing |
Minimal base template:
<!DOCTYPE html>
<html>
<head>
{{ vite_hmr() }}
{{ vite('resources/main.tsx') }}
</head>
<body>
<div id="app"></div>
</body>
</html>
For programmatic use inside a handler:
from litestar import get
from litestar.response import Template
from litestar_vite import ViteAssetLoader
loader = ViteAssetLoader(config=vite_config)
@get("/")
async def index() -> Template:
return Template("index.html", context={"vite": loader})
litestar assets init # Scaffold vite.config.ts and package.json
litestar assets install # Run npm/pnpm/bun install
litestar assets serve # Start Vite dev server (also auto-started when `dev_mode=True`)
litestar assets build # Production build (emits manifest.json + hashed bundles)
litestar assets generate-types # TypeScript type generation
litestar assets export-routes # routes.ts only
litestar assets status # Verify integration health
In dev mode:
runtime.port (e.g., 5173).hot_file) signaling dev-mode is active.vite() returns proxied URLs (http://localhost:5173/...) instead of manifest paths.vite_hmr() injects the HMR client script.Common HMR gotchas:
ViteConfig.paths.hot_file and vite.config.ts hotFile must point to the same marker. Mismatch ⇒ stale prod URLs in dev.server.cors: true in vite.config.ts so the Litestar origin can fetch dev assets.runtime.port and server.port; do not let Vite auto-pick.manifest.json: cache-bust by hash; never serve manifest.json from a CDN with long TTL.# Build for production
litestar assets build
# Outputs:
# <bundle_dir>/manifest.json ← URL → hashed-asset map
# <bundle_dir>/assets/*.js ← hashed JS bundles
# <bundle_dir>/assets/*.css ← hashed CSS bundles
# <bundle_dir>/<public files> ← copied from publicDir
In production:
dev_mode=False (env-toggled).bundle_dir as static files OR a CDN serves them and base (Vite) / assetUrl (plugin) points at the CDN.vite() reads manifest.json and returns hashed asset tags.CDN pattern:
// vite.config.ts
export default defineConfig({
base: process.env.ASSET_URL ?? "/static/", // CDN URL in prod, /static/ in dev
...
})
from litestar_vite import PathConfig, TypeGenConfig, ViteConfig, VitePlugin
from litestar_vite.inertia import InertiaConfig
vite = VitePlugin(
config=ViteConfig(
mode="hybrid",
paths=PathConfig(resource_dir="resources"),
inertia=InertiaConfig(root_template="base.html"),
types=TypeGenConfig(output="resources/generated"),
)
)
app = Litestar(plugins=[vite], middleware=[session_backend.middleware])
See ../litestar-inertia/SKILL.md for client adapter setup.
For HTMX + Jinja, use ViteConfig(mode="htmx", ...), Litestar TemplateConfig, and HTMXPlugin(). Vite handles JS/CSS bundling; Litestar returns partial HTML enriched with hx-* attributes. See ../litestar-htmx/SKILL.md.
Run the decision tree above. Most apps want spa, template, htmx, or hybrid. Lock the choice before configuring — switching mode mid-project rewires paths, assets, and TypeGen output.
pip install litestar-vite
npm install -D vite litestar-vite-plugin
# Plus a framework adapter, e.g.:
npm install -D @vitejs/plugin-react # or @vitejs/plugin-vue, etc.
Optional bootstrap: litestar assets init scaffolds vite.config.ts + package.json.
Define ViteConfig with bundle_dir, resource_dir, hot_file, runtime settings. Toggle dev_mode from an env var. Add to Litestar(plugins=[VitePlugin(config=...)]).
Add litestar() plugin with matching input, bundleDir, hotFile. Set server.cors: true, pin server.port, set base for prod CDN if needed.
For SPA / Inertia projects, set types=TypeGenConfig(...). Re-run litestar assets generate-types whenever DTOs change. CI should fail if generated files are out of date.
Use vite_hmr() and vite() in your base template.
For HTMX, register HTMXPlugin() and keep ViteConfig(mode="htmx", ...).
litestar run → check the dev banner shows Vite serving at http://localhost:5173. Edit a frontend file → browser updates without reload. If it doesn't, check the troubleshooting list below.
litestar assets build in CI → ship bundle_dir/ as static assets or push to CDN. Set dev_mode=False in production env.
ViteConfig paths and vite.config.ts paths are a single contract — bundle_dir, hot_file, resource_dir, asset URL, input must agree. Mismatch breaks HMR or manifest resolution silently.server.port in vite.config.ts — auto-picked ports break the plugin's URL resolution.server.cors: true when Litestar serves on a different origin than Vite in dev.dev_mode from env, never hardcode True in committed code — leaving dev mode on in prod proxies to a non-existent dev server.RuntimeConfig.start_dev_server=True in dev so litestar run starts/stops Vite. For prod, set dev_mode=False.schemas.ts is a runtime error.manifest.json with long-TTL caching — frontend deploys depend on it being current.vite.config.ts per frontend project — multiple configs in one repo confuse the plugin's path resolution.base (Vite) / assetUrl (plugin) for CDN deployments. Prefer env-driven values (process.env.ASSET_URL).litestar-vite integrates specifically with Vite's dev server protocol.Before delivering a litestar-vite integration, verify:
spa / template / htmx / hybrid / framework / external) is explicitmode="htmx" with HTMXPlugin()InertiaConfig on ViteConfig and register one VitePluginViteConfig.paths.bundle_dir and vite.config.ts bundleDir matchViteConfig.paths.hot_file and vite.config.ts hotFile matchdev_mode is env-toggledserver.port is pinned in vite.config.tsserver.cors: true if Litestar and Vite are on different origins in devvite_hmr() before vite(...)types=TypeGenConfig(...), generated types are committed or CI verifies they are up-to-datedev_mode=False and ships manifest.json + hashed bundlesbase / assetUrl from ASSET_URL env varTask: A Litestar SPA app with React + TanStack Router + Tailwind, building into the Litestar static dir, with HMR in dev.
# app/config/vite.py
import os
from pathlib import Path
from litestar_vite import PathConfig, RuntimeConfig, ViteConfig
PROJECT_ROOT = Path(__file__).resolve().parents[3]
FRONTEND_ROOT = PROJECT_ROOT / "src/js/web"
STATIC_DIR = PROJECT_ROOT / "src/py/app/server/static/web"
vite = ViteConfig(
paths=PathConfig(
root=FRONTEND_ROOT,
bundle_dir=STATIC_DIR,
hot_file="hot",
asset_url="/static/web/",
),
runtime=RuntimeConfig(port=3006, executor="bun", is_react=True),
dev_mode=os.getenv("ENV", "dev") == "dev",
)
# app/server/plugins.py
from litestar_vite import VitePlugin
from app import config
vite = VitePlugin(config=config.vite)
// src/js/web/vite.config.ts
import path from "node:path"
import tailwindcss from "@tailwindcss/vite"
import { tanstackRouter } from "@tanstack/router-plugin/vite"
import react from "@vitejs/plugin-react"
import litestar from "litestar-vite-plugin"
import { defineConfig } from "vite"
export default defineConfig({
clearScreen: false,
base: process.env.ASSET_URL ?? "/static/web/",
publicDir: "public",
server: { cors: true, port: Number(process.env.VITE_PORT ?? 3006) },
build: {
outDir: path.resolve(__dirname, "../../py/app/server/static/web"),
emptyOutDir: true,
},
plugins: [
tanstackRouter({ target: "react", autoCodeSplitting: true }),
tailwindcss(),
react(),
litestar({
input: ["src/main.tsx", "src/styles.css"],
bundleDir: path.resolve(__dirname, "../../py/app/server/static/web"),
hotFile: path.resolve(__dirname, "../../py/app/server/static/web/hot"),
}),
],
resolve: { alias: { "@": path.resolve(__dirname, "./src") } },
})
# Dev — Litestar boots Vite alongside the ASGI server
litestar --app app:app run
# Prod build
ENV=prod litestar assets build
For deep-dives on specific surfaces, see:
ViteConfig, PathConfig, RuntimeConfig, TypeGenConfig, and vite.config.ts reference.hybrid mode).npx claudepluginhub litestar-org/litestar-skills --plugin litestarSearches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.