Astro¶
Astro integration with Litestar Vite for content-focused sites with optional islands of interactivity.
Quick Start¶
litestar assets init --template astro
This creates an Astro project with TypeScript support and multi-framework component support.
Project Structure¶
Astro applications use the Astro integration:
my-app/
├── app.py # Litestar backend
├── package.json
├── astro.config.mjs # Astro configuration with Litestar integration
├── tsconfig.json
└── src/
├── pages/
│ └── index.astro # Pages (file-based routing)
├── components/
│ └── Card.astro # Astro components
└── generated/ # Generated types from OpenAPI
Backend Setup¶
"""Astro example - shared "Library" backend + Astro static site frontend.
All examples in this repository expose the same backend:
- `/api/summary` - overview + featured book
- `/api/books` - list of books
- `/api/books/{book_id}` - single book
Dev mode (default):
litestar --app-dir examples/astro run
Production mode (serves static build):
VITE_DEV_MODE=false litestar --app-dir examples/astro run
"""
import os
from pathlib import Path
from litestar import Controller, Litestar, get
from litestar.exceptions import NotFoundException
from msgspec import Struct
from litestar_vite import PathConfig, RuntimeConfig, TypeGenConfig, ViteConfig, VitePlugin
here = Path(__file__).parent
DEV_MODE = os.getenv("VITE_DEV_MODE", "true").lower() in {"true", "1", "yes"}
class Book(Struct):
id: int
title: str
author: str
year: int
tags: list[str]
class Summary(Struct):
app: str
headline: str
total_books: int
featured: Book
BOOKS: list[Book] = [
Book(id=1, title="Async Python", author="C. Developer", year=2024, tags=["python", "async"]),
Book(id=2, title="Type-Safe Web", author="J. Dev", year=2025, tags=["typescript", "api"]),
Book(id=3, title="Frontend Patterns", author="A. Designer", year=2023, tags=["frontend", "ux"]),
]
def _get_book(book_id: int) -> Book:
for book in BOOKS:
if book.id == book_id:
return book
raise NotFoundException(detail=f"Book {book_id} not found")
def _get_summary() -> Summary:
"""Build summary data."""
return Summary(
app="litestar-vite library", headline="One backend, many frontends", total_books=len(BOOKS), featured=BOOKS[0]
)
class LibraryController(Controller):
"""Library API controller."""
@get("/api/summary")
async def summary(self) -> Summary:
"""Overview endpoint used across all examples."""
return _get_summary()
@get("/api/books")
async def books(self) -> list[Book]:
"""Return all books."""
return BOOKS
@get("/api/books/{book_id:int}")
async def book_detail(self, book_id: int) -> Book:
"""Return a single book by id."""
return _get_book(book_id)
vite = VitePlugin(
config=ViteConfig(
mode="ssg", # Static Site Generation: proxy in dev, serve static in prod
dev_mode=DEV_MODE,
paths=PathConfig(
root=here,
bundle_dir=Path("dist"), # Astro outputs to dist/ by default
),
types=TypeGenConfig(generate_zod=True),
# Fixed port for E2E tests - can be removed for local dev or customized for production
runtime=RuntimeConfig(port=5051),
)
)
app = Litestar(route_handlers=[LibraryController], plugins=[vite], debug=True)
Key points:
mode="framework"enables meta-framework integration mode (aliases:mode="ssr"/mode="ssg")ExternalDevServerdelegates dev server to AstroTypeGenConfigenables type generation for Astro
Astro Configuration¶
import tailwindcss from "@tailwindcss/vite"
import { defineConfig } from "astro/config"
import litestar from "litestar-vite-plugin/astro"
// Litestar manages the dev server port via VITE_PORT and runtime config.
// The Astro integration reads the port automatically - no hardcoding needed.
// LITESTAR_PORT is the backend API server port (default 8000).
const LITESTAR_PORT = process.env.LITESTAR_PORT ?? "8000"
export default defineConfig({
integrations: [
litestar({
// API proxy points to the Litestar backend
apiProxy: `http://localhost:${LITESTAR_PORT}`,
apiPrefix: "/api",
// Match Python TypeGenConfig paths
types: {
output: "src/generated",
openapiPath: "src/generated/openapi.json",
routesPath: "src/generated/routes.json",
},
}),
],
vite: {
plugins: [tailwindcss()],
},
})
The Litestar integration provides:
API proxy configuration (
apiProxy)Type generation integration
Automatic port coordination with Litestar backend
Configuration options:
apiProxy- URL of Litestar backend (default:http://localhost:8000)apiPrefix- API route prefix to proxy (default:/api)types- Type generation configuration
API Integration¶
Server-Side Data Fetching¶
Astro pages can fetch data at build time or on-demand (SSR):
---
// src/pages/users/[id].astro
import type { User } from '../../generated/types.gen'
import { route } from '../../generated/routes'
const { id } = Astro.params
const response = await fetch(route('users:show', { id }))
const user: User = await response.json()
---
<html>
<head>
<title>{user.name}</title>
</head>
<body>
<h1>{user.name}</h1>
<p>{user.email}</p>
</body>
</html>
Static Site Generation (SSG)¶
Use getStaticPaths for static generation:
---
import type { User } from '../../generated/types.gen'
export async function getStaticPaths() {
const response = await fetch('http://localhost:8000/api/users')
const users: User[] = await response.json()
return users.map(user => ({
params: { id: user.id },
props: { user },
}))
}
const { user } = Astro.props
---
<h1>{user.name}</h1>
Client-Side Interactivity¶
Add interactive islands with client:* directives:
---
// src/pages/index.astro
import Counter from '../components/Counter'
---
<html>
<body>
<h1>Welcome</h1>
<!-- Only hydrates on client when visible -->
<Counter client:visible />
</body>
</html>
// src/components/Counter.tsx (React, Vue, Svelte, etc.)
import { useState } from 'react'
import { route } from '../generated/routes'
export default function Counter() {
const [count, setCount] = useState(0)
const [summary, setSummary] = useState(null)
async function loadSummary() {
const res = await fetch(route('summary'))
setSummary(await res.json())
}
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<button onClick={loadSummary}>Load Summary</button>
{summary && <p>{summary.headline}</p>}
</div>
)
}
Running¶
# Recommended: Litestar manages both servers
litestar run --reload
# Alternative: Run separately
litestar assets serve --production # Astro server
litestar run --reload # Backend API (in another terminal)
Type Generation¶
With types=TypeGenConfig() enabled in Python:
litestar assets generate-types
This generates TypeScript types in src/generated/ (path configured in astro.config.mjs).
Deployment¶
For production:
Build Astro:
litestar assets buildServe the built site:
Framework SSR:
litestar assets serve --productionruns the Astro serverStatic mode: Serve
dist/via Litestar’s static files handler
Rendering Modes¶
Astro supports multiple rendering modes:
Mode |
Use Case |
Output |
|---|---|---|
Static (SSG) |
Blogs, marketing sites |
Pre-built HTML files |
Server (SSR) |
Dynamic content, auth |
On-demand rendering |
Hybrid |
Mix of static + dynamic |
Some pre-built, some on-demand |
Configure in astro.config.mjs:
export default defineConfig({
output: 'server', // 'static', 'server', or 'hybrid'
integrations: [litestar({ /* ... */ })],
})
See Also¶
Type Generation - TypeScript type generation