Install
$ agentstack add skill-litestar-org-litestar-skills-litestar-vite ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
litestar-vite
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. Python ViteConfig is the source of truth; the generated .litestar.json bridge lets JS config normally keep only litestar({ input: [...] }).
Code Style Rules
- Python: PEP 604 unions (
T | None); consumer Litestar app modules MAY usefrom __future__ import annotations. - TypeScript: strict mode;
defineConfigfromvite; onevite.config.tsper frontend project. - Keep
ViteConfigas the source of truth. Only duplicatebundleDir,hotFile, orassetUrlinvite.config.tsfor deliberate standalone/override workflows.
Quick Reference
Minimal SPA setup (Python side)
from litestar import Litestar
from litestar_vite import PathConfig, 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", # written to the .litestar.json bridge
),
dev_mode=True, # toggled by env in production
)
app = Litestar(plugins=[VitePlugin(config=vite_config)])
Minimal SPA setup (JS side)
// 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",
plugins: [
react(),
litestar({
input: ["resources/main.tsx", "resources/main.css"],
}),
],
resolve: { alias: { "@": "/resources" } },
})
Mode Selection
| 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:
- Need full SPA with client-side routing → spa
- Server-rendered HTML, sprinkle Vite-bundled JS → template
- HTMX-driven hypermedia with Vite assets → htmx + HTMXPlugin
- Server-side routing + JS page components, shared data → hybrid / inertia (see
../litestar-inertia/SKILL.md) - Already using Nuxt or SvelteKit → framework (
ssralias is accepted) - Building an Astro site → framework (
ssgalias is accepted) - Using Angular CLI rather than the Analog Vite example → external
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",
),
)
Explicit fullstack-spa override pattern
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: {
port: Number(process.env.VITE_PORT ?? 3006), // direct/two-port workflows only
},
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 owns bundle_dir, hot_file, and resource_dir. Duplicate them in JS only when intentionally overriding the .litestar.json bridge, as this explicit mono-repo example does.
Type Generation
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.json | output/routes.json | Route table changes | Route metadata consumed by the JS plugin | | 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 metadata consumed by the JS plugin | | page-props.ts | output/page-props.ts | Inertia handlers added/changed | Typed props for Inertia page components |
CLI:
litestar assets generate-types # one-off generation
litestar assets export-routes # routes.json metadata
litestar assets export-routes --typescript # 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 Helpers
Auto-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 ` in dev mode; no-op in prod | | {{ vitestatic('favicon.svg') }} | Resolve a static asset URL | | {{ viteroutes() }}` | Render inline route metadata for client-side routing |
Minimal base template:
{{ vite_hmr() }}
{{ vite('resources/main.tsx') }}
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})
CLI
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.json metadata
litestar assets doctor # Diagnose integration health
litestar assets status # Read-only status summary
HMR
In dev mode:
- Vite dev server runs on
runtime.port(e.g.,5173). - Plugin writes a "hot file" (path =
hot_file) signaling dev-mode is active. vite()returns Litestar-proxied dev URLs by default instead of manifest paths.vite_hmr()injects the HMR client script.- On rebuild, Vite pushes updates over WS; the page hot-swaps without a reload.
Common HMR gotchas:
- Hot file mismatch: remove JS
hotFileoverrides or align them withViteConfig.paths.hot_file. Mismatch ⇒ stale prod URLs in dev. - CORS errors: use default proxy mode first. In direct/two-port mode, set
server.cors: trueso the Litestar origin can fetch dev assets. - Port conflict: proxy mode can auto-pick a Vite port. In direct/two-port mode, pin
runtime.portandserver.port. - Browsers cache
manifest.json: cache-bust by hash; never serve manifest.json from a CDN with long TTL.
Production Build & Deploy
# Build for production
litestar assets build
# Outputs:
# /manifest.json ← URL → hashed-asset map
# /assets/*.js ← hashed JS bundles
# /assets/*.css ← hashed CSS bundles
# / ← copied from publicDir
In production:
- Set
dev_mode=False(env-toggled). - Litestar serves
bundle_diras static files OR a CDN serves them andbase(Vite) /assetUrl(plugin) points at the CDN. vite()readsmanifest.jsonand returns hashed asset tags.- HMR helpers become no-ops.
CDN pattern:
// vite.config.ts
export default defineConfig({
base: process.env.ASSET_URL ?? "/static/", // CDN URL in prod, /static/ in dev
...
})
Inertia integration
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.
HTMX integration
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.
Workflow
Step 1: Pick the Mode
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.
Step 2: Install
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.
Step 3: Wire ViteConfig (Python)
Define ViteConfig with paths=PathConfig(...), optional runtime=RuntimeConfig(...), and optional types=True / types=TypeGenConfig(...). Toggle dev_mode from an env var. Add to Litestar(plugins=[VitePlugin(config=...)]).
Step 4: Wire vite.config.ts (JS)
Add litestar() with input. Let the .litestar.json bridge provide bundleDir, hotFile, typegen paths, and asset URL unless you are deliberately overriding Python config. Set base for prod CDN if needed.
Step 5: Enable Type Generation (optional)
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.
Step 6: Wire Templates (template / HTMX modes)
Use vite_hmr() and vite() in your base template. For HTMX, register HTMXPlugin() and keep ViteConfig(mode="htmx", ...).
Step 7: Verify HMR
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.
Step 8: Build & Deploy
litestar assets build in CI → ship bundle_dir/ as static assets or push to CDN. Set dev_mode=False in production env.
Guardrails
ViteConfigis the source of truth — avoid JS-sidebundleDir,hotFile, andassetUrloverrides unless this is a standalone/mono-repo override. Mismatch breaks HMR or manifest resolution silently.- Use proxy mode by default — Vite can auto-pick a port and the hot file carries the actual URL. Pin
server.portonly for direct/two-port workflows. - Set
server.cors: trueonly for different public origins — proxy mode keeps Litestar as the public origin. - Toggle
dev_modefrom env, never hardcodeTruein committed code — leaving dev mode on in prod proxies to a non-existent dev server. - Keep
RuntimeConfig.start_dev_server=Truein dev solitestar runstarts/stops Vite. For prod, setdev_mode=False. - Commit generated types OR regenerate in CI and check no diff — a drift between OpenAPI and
schemas.tsis a runtime error. - Never serve
manifest.jsonwith long-TTL caching — frontend deploys depend on it being current. - One
vite.config.tsper frontend project — multiple configs in one repo confuse the plugin's path resolution. - Use
base(Vite) /assetUrl(plugin) for CDN deployments. Prefer env-driven values (process.env.ASSET_URL). - Not for Webpack/Rollup/esbuild/Parcel —
litestar-viteintegrates specifically with Vite's dev server protocol.
Validation Checkpoint
Before delivering a litestar-vite integration, verify:
- [ ] Mode (
spa/template/htmx/hybrid/framework/external) is explicit - [ ] HTMX apps use
mode="htmx"withHTMXPlugin() - [ ] Inertia apps put
InertiaConfigonViteConfigand register oneVitePlugin - [ ] JS-side
bundleDir/hotFile/assetUrloverrides are absent or intentionally matchViteConfig - [ ]
dev_modeis env-toggled - [ ] Direct/two-port workflows pin
server.port; proxy-mode workflows do not rely on a fixed Vite port - [ ]
server.cors: trueonly if Litestar and Vite are different public origins in dev - [ ] Template base file uses
vite_hmr()beforevite(...) - [ ] If
types=TypeGenConfig(...), generated types are committed or CI verifies they are up-to-date - [ ] Production build sets
dev_mode=Falseand shipsmanifest.json+ hashed bundles - [ ] CDN deploys set
base/assetUrlfromASSET_URLenv var - [ ] No competing Webpack/Rollup config in the same project
Example
Task: 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]
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [litestar-org](https://github.com/litestar-org)
- **Source:** [litestar-org/litestar-skills](https://github.com/litestar-org/litestar-skills)
- **License:** MIT
- **Homepage:** https://github.com/litestar-org/litestar-skills
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.