Migration Guide (v0.15)

This guide helps you upgrade to v0.15.0 from earlier versions.

Note

v0.15 includes new features and improvements with minimal breaking changes. Most applications can upgrade by updating dependencies and adding new features incrementally.

Breaking Changes

Configuration Structure

v0.15 introduces nested configuration for better organization:

Before v0.15:

ViteConfig(
    bundle_dir="public",
    resource_dir="resources",
    hot_reload=True,
    port=5173,
)

v0.15+:

from litestar_vite import ViteConfig
from litestar_vite.config import PathConfig, RuntimeConfig

ViteConfig(
    paths=PathConfig(
        bundle_dir="public",
        resource_dir="resources",
    ),
    runtime=RuntimeConfig(
        hot_reload=True,
        port=5173,
    ),
)

Template Engine Removal

The ViteTemplateEngine has been removed. Use the standard Litestar Jinja engine:

from litestar.contrib.jinja import JinjaTemplateEngine
from litestar.template.config import TemplateConfig

app = Litestar(
    template_config=TemplateConfig(
        engine=JinjaTemplateEngine(directory="templates")
    ),
    plugins=[VitePlugin(...)]
)

CLI Command Group

Commands are now under the assets group:

  • litestar assets init - Initialize a new project

  • litestar assets build - Build assets

  • litestar assets serve - Serve assets

  • litestar assets generate-types - Generate TypeScript types

  • litestar assets doctor - Diagnose configuration issues (use --show-config to print effective configs, --fix to apply safe auto-fixes; restarting the app rewrites .litestar.json when runtime.set_environment=True)

  • litestar assets deploy - Deploy assets to CDN

  • litestar assets status - Check Vite integration status

Proxy Mode Default Change

The proxy_mode now has smarter auto-detection:

  • Default is still "vite" (whitelist mode - proxies Vite assets only)

  • When ExternalDevServer is configured, automatically switches to "proxy" mode

Impact:

If you were relying on implicit behavior with external dev servers (Angular CLI, Next.js), the mode now auto-configures correctly. No action needed in most cases.

To explicitly set the mode:

from litestar_vite import ViteConfig, RuntimeConfig

ViteConfig(
    runtime=RuntimeConfig(
        proxy_mode="vite",  # Explicit whitelist mode
    )
)

Mode Aliases

v0.15 introduces mode aliases for convenience:

Alias

Normalized To

Use Case

"inertia"

"hybrid"

Inertia.js SPA with server-side routing

"ssg"

"framework"

Static Site Generation (alias for framework mode)

Recommended usage:

# For Inertia.js applications
ViteConfig(mode="inertia")

# For meta-frameworks (Astro, etc.)
ViteConfig(mode="framework")  # or aliases: "ssr" / "ssg"

New Features

Vite 7.x Support

v0.15 adds support for Vite 7.x while maintaining compatibility with Vite 6.x.

Package.json Update:

{
  "peerDependencies": {
    "vite": "^6.0.0 || ^7.0.0"
  }
}

Migration:

Upgrade Vite in your frontend:

# npm
npm install -D vite@^7.0.0

# yarn
yarn add -D vite@^7.0.0

# pnpm
pnpm add -D vite@^7.0.0

# bun
bun add -D vite@^7.0.0

Inertia v2 Features

v0.15 adds support for Inertia.js v2 features:

History Encryption

Enable browser history encryption globally:

from litestar_vite.inertia import InertiaConfig

InertiaConfig(
    encrypt_history=True,  # New in v0.15
)

This prevents sensitive data from being visible in browser history after logout. See: History Encryption

New Helper:

from litestar_vite.inertia import clear_history

@post("/logout")
async def logout(request: Request) -> InertiaRedirect:
    request.session.clear()
    clear_history(request)  # Regenerate encryption key
    return InertiaRedirect(request, redirect_to="/login")

Deferred Props with Grouping

The new defer() helper supports grouped deferred props:

from litestar_vite.inertia import defer, InertiaResponse

@get("/dashboard", component="Dashboard")
async def dashboard() -> InertiaResponse:
    return InertiaResponse({
        "user": current_user,
        # Deferred props loaded after page render
        "teams": defer("teams", lambda: Team.all(), group="attributes"),
        "projects": defer("projects", lambda: Project.all(), group="attributes"),
        "permissions": defer("permissions", lambda: Permission.all()),
    })

Props in the same group are fetched together in a single request.

Merge Props

New merge() helper for combining data during partial reloads:

from litestar_vite.inertia import merge, InertiaResponse

@get("/posts", component="Posts")
async def list_posts(page: int = 1) -> InertiaResponse:
    posts = await Post.paginate(page=page, per_page=20)
    return InertiaResponse({
        # Append new posts to existing list
        "posts": merge("posts", posts.items),
    })

Supports strategies: "append" (default), "prepend", "deep".

New Inertia Helpers

Helper

Description

error(request, key, message)

Set validation errors in session

only(*keys)

Filter props to include only specified keys

except_(*keys)

Filter props to exclude specified keys

scroll_props()

Create infinite scroll configuration

Example:

from litestar_vite.inertia import error, only, scroll_props

# Validation errors
@post("/users")
async def create_user(request: Request, data: UserCreate) -> InertiaResponse:
    if not data.email:
        error(request, "email", "Email is required")
        return InertiaRedirect(request, "/users/new")

# Prop filtering
@get("/users", component="Users")
async def list_users() -> InertiaResponse:
    return InertiaResponse(
        {"users": [...], "teams": [...], "stats": [...]},
        prop_filter=only("users"),  # Only send users prop
    )

# Infinite scroll
@get("/posts", component="Posts")
async def list_posts(page: int = 1) -> InertiaResponse:
    posts = await Post.paginate(page=page, per_page=20)
    return InertiaResponse(
        {"posts": merge("posts", posts.items)},
        scroll_props=scroll_props(
            current_page=page,
            previous_page=page - 1 if page > 1 else None,
            next_page=page + 1 if posts.has_more else None,
        ),
    )

Automatic Pagination Support

v0.15 automatically detects pagination containers and generates scroll props:

from litestar_vite.inertia import InertiaResponse

@get("/posts", component="Posts")
async def list_posts(limit: int = 10, offset: int = 0) -> InertiaResponse:
    # Returns OffsetPagination with items, total, limit, offset
    posts = await Post.paginate(limit=limit, offset=offset)

    # Automatically unwraps items and generates scroll_props
    return InertiaResponse({"posts": posts})

Supports: - litestar.pagination.OffsetPagination - litestar.pagination.ClassicPagination - advanced_alchemy.service.OffsetPagination - Any object with items attribute and pagination metadata

Inertia Type Generation

New type generation options for Inertia page props:

InertiaTypeGenConfig:

from litestar_vite.config import InertiaConfig, InertiaTypeGenConfig

InertiaConfig(
    type_gen=InertiaTypeGenConfig(
        include_default_auth=True,   # Include User/AuthData interfaces
        include_default_flash=True,  # Include FlashMessages interface
    ),
)

Default Types (when ``include_default_auth=True``):

export interface User {
  id: string
  email: string
  name?: string | null
}

export interface AuthData {
  isAuthenticated: boolean
  user?: User
}

Extending Defaults:

// Extend via module augmentation
declare module 'litestar-vite-plugin/inertia' {
  interface User {
    avatarUrl?: string | null
    roles: Role[]
    teams: Team[]
  }
}

Custom User Models:

If your user model has different required fields (e.g., uuid instead of id):

InertiaConfig(
    type_gen=InertiaTypeGenConfig(
        include_default_auth=False,  # Disable defaults
    ),
)

Then define your own User interface in TypeScript:

declare module 'litestar-vite-plugin/inertia' {
  interface User {
    uuid: string
    username: string
  }
}

TypeScript Enhancements

Enhanced TypeGenConfig:

New options for type generation:

Option

Type

Description

generate_page_props

bool

Generate Inertia page props types (default: True)

page_props_path

Path | None

Path for page props metadata JSON

routes_ts_path

Path | None

Path for typed routes TypeScript file

fallback_type

Literal["unknown", "any"]

Fallback value type for untyped dict/list in Inertia page props (default: unknown)

type_import_paths

dict[str, str]

Map props type names to TypeScript import paths for schemas excluded from OpenAPI

Example:

from litestar_vite.config import TypeGenConfig

ViteConfig(
    types=TypeGenConfig(
        output=Path("src/generated"),
        generate_page_props=True,     # Enable page props generation
        page_props_path=Path("src/generated/inertia-pages.json"),
        routes_ts_path=Path("src/generated/routes.ts"),
    ),
)

TypeScript Helpers:

New TypeScript utilities in litestar-vite-plugin:

HTMX Utilities:

import {
  addDirective,
  registerHtmxExtension,
  setHtmxDebug,
  swapJson,
} from 'litestar-vite-plugin/helpers'

// Add HTMX directive
addDirective('my-directive', (element) => { ... })

// Register HTMX extension
// Note: Registration is explicit (no auto-registration)
registerHtmxExtension()

// Enable HTMX debug mode
setHtmxDebug(true)

// Swap JSON response into element
swapJson(element, { data: 'value' })

Inertia Helpers:

import { unwrapPageProps } from 'litestar-vite-plugin/inertia-helpers'

// Unwrap page props from Inertia response
const props = unwrapPageProps(pageData)

Runtime Configuration

New RuntimeConfig Options:

Option

Type

Description

http2

bool

Enable HTTP/2 for proxy connections (default: True)

start_dev_server

bool

Control dev server startup (default: True)

HTTP/2 Support:

ViteConfig(
    runtime=RuntimeConfig(
        http2=True,  # Better multiplexing for proxy requests
    ),
)

HTTP/2 improves proxy performance by allowing multiple requests over a single connection. WebSocket traffic (HMR) uses a separate connection and is unaffected.

SPA Configuration

New SPAConfig Options:

Option

Type

Description

cache_transformed_html

bool

Cache transformed HTML in production (default: True)

Example:

from litestar_vite.config import SPAConfig

ViteConfig(
    spa=SPAConfig(
        inject_csrf=True,
        cache_transformed_html=True,  # Cache for performance
    ),
)

Note: Caching is automatically disabled when inject_csrf=True because CSRF tokens are per-request.

External Dev Server Enhancements

Enhanced configuration for external dev servers (Angular CLI, Next.js, etc.):

Option

Type

Description

target

str | None

URL of external dev server

command

list[str] | None

Custom command to start dev server

build_command

list[str] | None

Custom build command

http2

bool

Enable HTTP/2 for proxy (default: False)

enabled

bool

Enable/disable external proxy (default: True)

Example:

from litestar_vite.config import ExternalDevServer

ViteConfig(
    runtime=RuntimeConfig(
        external_dev_server=ExternalDevServer(
            target="http://localhost:4200",  # Angular CLI
            command=["ng", "serve"],
            build_command=["ng", "build"],
            http2=True,  # Better performance
        ),
    ),
)

New CLI Templates

v0.15 adds new framework templates for litestar assets init:

Template

Description

react-router

React with React Router 7+

react-tanstack

React with TanStack Router

svelte-inertia

Svelte 5 with Inertia.js

Usage:

litestar assets init --template react-router myapp
litestar assets init --template react-tanstack myapp
litestar assets init --template svelte-inertia myapp

Troubleshooting

Common Migration Issues

Issue: Type Generation Not Working

Symptom:

Type generation doesn’t run after upgrading to v0.15.

Solution:

Check that your Python backend is exporting the new metadata files:

# Check for generated files
ls src/generated/inertia-pages.json
ls src/generated/routes.json

If missing, ensure type generation is enabled:

ViteConfig(
    types=TypeGenConfig(
        generate_page_props=True,
    ),
)

Issue: Inertia History Encryption Not Working

Symptom:

encrypt_history=True doesn’t encrypt history.

Solution:

History encryption is a client-side feature. Ensure you’re using Inertia.js v2:

npm install @inertiajs/react@^2.0.0  # or vue3/svelte

Then update your frontend code to support history encryption.

Issue: Pagination Auto-Detection Conflicts

Symptom:

Pagination container is unwrapped but you want the full object.

Solution:

Manually construct the response props:

@get("/posts", component="Posts")
async def list_posts() -> InertiaResponse:
    pagination = await Post.paginate(limit=10, offset=0)

    # Manually control what's sent
    return InertiaResponse({
        "items": pagination.items,
        "total": pagination.total,
        "limit": pagination.limit,
        "offset": pagination.offset,
    })

Issue: Unknown Mode Error

Symptom:

Error about unknown mode when using old mode names.

Solution:

v0.15 supports these modes: spa, template, htmx, hybrid, framework, external.

You can also use aliases: inertia (for hybrid) and ssr / ssg (for framework).

# Recommended for Inertia.js
ViteConfig(mode="inertia")

# Recommended for meta-frameworks
ViteConfig(mode="framework")

Upgrade Checklist

Task

Update litestar-vite to v0.15.0

Migrate to nested configuration (PathConfig, RuntimeConfig)

Replace ViteTemplateEngine with JinjaTemplateEngine (if applicable)

Update CLI commands to use litestar assets prefix

Upgrade Vite to 6.x or 7.x

Update Inertia.js to v2 (if using Inertia features)

Review proxy mode configuration (usually auto-detected correctly)

Test type generation (run litestar assets generate-types)

Run make test to verify everything works

Run make lint to check for issues

Next Steps

Getting Help

If you encounter issues during migration: