How It Works#
Understand the Inertia.js protocol and how it integrates with Litestar.
See also
Official Inertia.js docs: How It Works
The Inertia Protocol#
Inertia.js is a protocol for building SPAs with server-side routing. Instead of returning JSON from an API, your routes return page components:
Initial page load: Server renders full HTML with embedded page data
Subsequent navigation: Client makes XHR request, server returns JSON
Client updates: Inertia swaps page component without full reload
sequenceDiagram
participant Browser
participant Server
Note over Browser,Server: Initial Page Load
Browser->>Server: GET /dashboard
Server->>Browser: HTML with page JSON bootstrap
Note over Browser,Server: Subsequent Navigation
Browser->>Server: XHR GET /users<br>X-Inertia: true
Server->>Browser: JSON {component, props, url}
Note over Browser: Swap component, update URL
Request Flow#
The examples below show both bootstrap formats because litestar-vite now defaults to the
script-element transport while still supporting the classic data-page compatibility path.
Initial Load (Full HTML):
<!-- Classic data-page bootstrap -->
<div id="app" data-page='{"component":"Dashboard","props":{"user":"Alice"},"url":"/dashboard"}'></div>
<!-- Default script-element bootstrap -->
<div id="app"></div>
<script type="application/json" id="app_page" data-page="app">
{"component":"Dashboard","props":{"user":"Alice"},"url":"/dashboard"}
</script>
XHR Request (JSON):
GET /users HTTP/1.1
X-Inertia: true
X-Inertia-Version: abc123
{
"component": "Users",
"props": {"users": ["Alice", "Bob"]},
"url": "/users",
"version": "abc123"
}
Inertia Headers#
The protocol uses these HTTP headers:
Header |
Description |
|---|---|
|
Request: Client indicates Inertia request |
|
Request: Asset version for cache invalidation |
|
Request: Target component name for scoped partial reloads |
|
Request: Requested partial reload keys |
|
Request: Keys to exclude (v2) |
|
Request: Once-prop keys already cached client-side and safe to omit |
|
Request: Props to reset (v2) |
|
Request: Validation error bag name scoping form errors |
|
Response: Server confirms Inertia response |
|
Response: External redirect URL |
Status Codes#
Litestar’s Inertia integration produces and responds to standard HTTP status codes:
Status |
When Triggered |
Client Behavior |
|---|---|---|
|
Normal Inertia page response for initial navigation or subsequent XHR visits (see |
Inertia unpacks page JSON props and swaps the page component without a full browser reload. |
|
Background actions or Precognition pre-validation requests where validation succeeded without errors (asserted in |
Inertia leaves the current component and scroll position untouched; Precognition marks validated fields valid. |
|
Form submissions using |
Automatically converts the subsequent redirect navigation to a |
|
Asset version hash mismatch between client |
Inertia intercepts the response and triggers a full hard browser reload to download fresh frontend assets. |
|
Server-side validation errors raised by Litestar DTO or handler validation (handled in |
Inertia maps returned error bags into form component |
Version Checking#
Inertia uses asset versioning to detect outdated clients:
Client sends
X-Inertia-VersionheaderServer compares with current manifest hash
On mismatch, server returns
409 ConflictClient performs full page reload
Litestar-Vite automatically handles versioning using the Vite manifest.
Page Object Schema#
The Inertia page object represents the server-rendered payload delivered to the client on initial load
(via the script transport) and on XHR visits. In Litestar, this payload is modeled by the PageProps
dataclass (src/py/litestar_vite/inertia/types.py:257-316).
On the wire, Python snake_case attribute names are automatically converted to camelCase keys by
to_inertia_dict() (citing src/py/litestar_vite/inertia/types.py:39-54).
Top-Level Fields#
Wire Key (camelCase) |
Type |
Required |
Description |
|---|---|---|---|
|
|
Yes |
Name of the target page component (Python: |
|
|
Yes |
Data dictionary passed as component props (Python: |
|
|
Yes |
Current request URL path and query parameters (Python: |
|
|
Yes |
Current asset version hash for cache validation (Python: |
|
|
No |
Encrypt history state in browser history (Python: |
|
|
No |
Clear existing browser history state when navigating (Python: |
|
|
No |
Prop keys to shallow-merge with existing page props (Python: |
|
|
No |
Prop array keys to prepend to existing lists (Python: |
|
|
No |
Prop object keys to merge recursively (Python: |
|
|
No |
Array property keys to match items on when merging (Python: |
|
|
No |
Deferred prop keys grouped by fetch group (Python: |
|
|
No |
Once-prop values and client cache keys (Python: |
|
|
No |
Scroll region reset and preservation configs (Python: |
|
|
No |
Flashed session messages scoped by category (Python: |
Omission Rules#
The serialization pipeline implements two omission rules:
None-value omission: Optional fields with a
Nonevalue are dropped entirely from the serialized dictionary (src/py/litestar_vite/inertia/types.py:103-104).Boolean false suppression:
encryptHistoryandclearHistoryare deleted whenFalse(src/py/litestar_vite/inertia/types.py:312-315). On the wire, absent andfalseare identical; onlytrueis ever emitted.
Scroll Configuration#
The scrollProps mapping uses ScrollPropsConfig (src/py/litestar_vite/inertia/types.py:134-141)
with the shape:
pageName: Query parameter for pagination (default:"page")currentPage: Current page number (integer)previousPage: Previous page number ornullnextPage: Next page number ornull
This matches the client-side ScrollProps TypeScript interface defined at
src/js/src/inertia-helpers/index.ts:323-332.
Complete Response Payload Example#
{
"component": "Users/Index",
"props": {
"users": [
{"id": 1, "name": "Alice"}
]
},
"url": "/users?page=1",
"version": "b4a8e91",
"encryptHistory": true,
"flash": {
"success": ["User created successfully"]
},
"deferredProps": {
"default": ["analytics", "activityLog"]
},
"onceProps": {
"userPermissions": {
"can_edit": true,
"cache_key": "perm_u1"
}
},
"scrollProps": {
"usersList": {
"pageName": "page",
"currentPage": 1,
"previousPage": null,
"nextPage": 2
}
}
}
Litestar Integration#
Litestar-Vite implements the protocol through:
InertiaPlugin: Middleware for request/response handling
InertiaResponse: Response class for page data
InertiaMiddleware: Header parsing and validation
ViteAssetLoader: Asset versioning via manifest hash
from typing import Any
from litestar import get
# Simple Inertia route
@get("/dashboard", component="Dashboard")
async def dashboard() -> dict[str, Any]:
return {"user": "Alice", "stats": {"views": 100}}
# The plugin handles:
# 1. Detecting X-Inertia header
# 2. Building PageProps object
# 3. Returning JSON or HTML based on request type
# 4. Version checking
See Also#
The Inertia Protocol - Official protocol docs
Asset Versioning - Version checking details
Responses - Response classes