From litestar-skills
Auto-activate for litestar_vite imports, VitePlugin, ViteConfig, PathConfig, RuntimeConfig, TypeGenConfig, vite.config.ts, astro.config.mjs with `litestar-vite-plugin/astro`, `litestar assets` CLI. First-party plugin coordinating a Vite frontend with a Litestar backend across **SPA / template / HTMX / Inertia / framework** modes. Produces ViteConfig + vite.config.ts wiring, manifest resolution, TypeGen (routes.ts, schemas.ts, openapi.json, inertia-pages.json), Jinja helpers (`vite()`, `vite_hmr()`, `vite_react_refresh()`), HMR, production build. Supported frameworks: **React** (+ TanStack Router), **Vue 3**, **Svelte**, **Angular** (`@analogjs/vite-plugin-angular`), **HTMX+Jinja** (with `ls-for`/`ls-if`/`$data` client templating), **Inertia.js** (React/Vue ±Jinja), **Nuxt, SvelteKit, Astro**. Use when: wiring any frontend with Litestar, choosing a mode, scaffolding from an example, setting up HMR, or generating types. Not for Webpack, Rollup, esbuild, Parcel, or plain Vite outside Litestar.
npx claudepluginhub litestar-org/litestar-skills --plugin litestar-skillsThis skill uses the workspace's default tool permissions.
`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.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Builds scalable data pipelines, modern data warehouses, and real-time streaming architectures using Spark, dbt, Airflow, Kafka, and cloud platforms like Snowflake, BigQuery.
Builds production Apache Airflow DAGs with best practices for operators, sensors, testing, and deployment. For data pipelines, workflow orchestration, and batch job scheduling.
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.
It supports five modes — SPA, template, HTMX, Inertia (hybrid), and framework (SSR) — letting one plugin cover everything from a static-asset add-on to a full Inertia.js app.
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 ViteConfig, VitePlugin
vite_config = ViteConfig(
bundle_dir="public", # built assets land here
resource_dir="resources", # frontend source root
use_server_lifespan=True, # vite dev runs alongside `litestar run`
dev_mode=True, # toggled by env in production
hot_file="public/hot", # MUST match vite.config.ts hotFile
)
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 | Standalone single-page app (React, Vue, Svelte) with Litestar JSON API backend | dev_mode=True proxies to Vite; manifest in prod |
template | Server-rendered pages (Jinja2/Mako) with Vite-bundled JS/CSS sprinkles | vite_asset() template helper resolves dev/prod URLs |
htmx | HTMX hypermedia with Vite-bundled assets and HMR | template mode + htmx extras; partial HTML responses |
hybrid (Inertia) | Inertia.js — server routes returning Inertia responses + JS page components | litestar_vite.inertia.InertiaPlugin alongside VitePlugin |
framework | SSR frameworks (Nuxt, SvelteKit) | Plugin defers to the framework; coordinates port + manifest |
Decision tree:
../litestar-inertia/SKILL.md)VitePlugin config (Python)from litestar_vite import (
ViteConfig, VitePlugin, PathConfig, RuntimeConfig, TypeGenConfig,
)
vite_config = ViteConfig(
mode="spa",
paths=PathConfig(
root=".",
resource_dir="src",
bundle_dir="public",
public_dir="public",
vite_config="vite.config.ts",
),
runtime=RuntimeConfig(
port=5173,
host="localhost",
protocol="http",
hot_reload=True,
),
types=TypeGenConfig(
enabled=True,
generate_sdk=True,
generate_routes=True,
generate_schemas=True,
generate_page_props=True,
output="src/generated",
),
use_asset_linker=True,
use_server_lifespan=True,
dev_mode=False, # True in dev, False in prod (env-toggled)
hot_file="public/hot",
)
litestar-fullstack-spa/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-spa/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(
enabled=True,
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_asset('src/main.ts') }} | Resolve script URL (dev: proxied; prod: hashed manifest) |
{{ vite_css('src/app.css') }} | Render <link rel="stylesheet"> tag |
{{ vite_hmr_client() }} | Inject HMR client <script> in dev mode (no-op in prod) |
{{ vite_react_refresh() }} | Inject React Fast Refresh preamble before React app code |
Minimal base template:
<!DOCTYPE html>
<html>
<head>
{{ vite_hmr_client() }}
{{ vite_react_refresh() }}
{{ vite_css('src/app.css') }}
</head>
<body>
<div id="app"></div>
{{ vite_asset('src/main.tsx') | safe }}
</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 with `litestar run` when use_server_lifespan=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_asset() returns proxied URLs (http://localhost:5173/...) instead of manifest paths.vite_hmr_client() injects the HMR client script.Common HMR gotchas:
ViteConfig.hot_file and vite.config.ts hotFile must point to the same path. 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_asset() reads manifest.json once and returns hashed URLs.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 VitePlugin
from litestar_vite.inertia import InertiaPlugin, InertiaConfig
app = Litestar(plugins=[
VitePlugin(config=vite_config),
InertiaPlugin(config=InertiaConfig(root_template="base.html")),
])
See ../litestar-inertia/SKILL.md for client adapter setup.
For HTMX mode, use template mode in ViteConfig plus the HTMX htmx-vite plugin client. 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 or hybrid. Lock the choice before configuring — switching mode mid-project is painful.
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 TypeGenConfig(enabled=True, ...). Re-run litestar assets generate-types whenever DTOs change. CI should fail if generated files are out of date.
Use vite_hmr_client(), vite_react_refresh() (React only), vite_css(), vite_asset() in your base template.
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.use_server_lifespan=True for dev so litestar run starts/stops Vite. For prod, set dev_mode=False; the lifespan is irrelevant.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) is explicitbundle_dir and hotFile paths in ViteConfig match vite.config.tsdev_mode is env-toggledserver.port is pinned in vite.config.tsserver.cors: true if Litestar and Vite are on different origins in devvite_hmr_client() (and vite_react_refresh() for React) before any user JSTypeGenConfig.enabled=True, 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
from litestar_vite import ViteConfig, PathConfig, RuntimeConfig
import os
vite = ViteConfig(
paths=PathConfig(
root=".",
resource_dir="src/js/web",
bundle_dir="src/py/app/server/static/web",
vite_config="src/js/web/vite.config.ts",
),
runtime=RuntimeConfig(port=3006, hot_reload=True),
use_server_lifespan=True,
dev_mode=os.getenv("ENV", "dev") == "dev",
hot_file="src/py/app/server/static/web/hot",
)
# 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
</example>
For deep-dives on specific surfaces, see:
ViteConfig, PathConfig, RuntimeConfig, TypeGenConfig, and vite.config.ts reference.hybrid mode).