┌────────────────────────────────────────────────────────┐
│ axum Router │
│ ┌──────────────────────────────────────────────────┐ │
│ │ InertiaLayer │ │
│ │ ├─ reads session data (errors, flash) │ │
│ │ ├─ puts the config into request extensions │ │
│ │ └─ on response: finishes the InertiaResponse, │ │
│ │ writes session data, applies protocol rules │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ Inertia (extractor) │ │
│ │ inertia.render(...) → │ │
│ │ InertiaResponse builder │ │
│ └───────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Protocol core (no framework) │
│ • RequestInfo / PageObject │
│ • decide(): which response shape │
│ • prop resolver │
│ • Traits: RootView, SessionStore, │
│ SsrClient, SharedProps │
└──────────────────────────────────────┘
A handler returns an InertiaResponse, which is only a description: the component, the props, the closures. The layer finishes it after the handler returns, because only the layer has the request headers, the session data and the config.
The protocol core has no I/O and no framework types. protocol::decide is a pure function from the request to a response shape, and props::resolver::resolve is a function from the props and the request headers to the final props and metadata. Both have unit tests. An adapter for another framework can use them without a change.
| Trait | Purpose | Built in |
|---|---|---|
RootView |
The HTML shell of the first page load | MinimalRootView, ViteRootView, ClosureRootView |
SessionStore |
Errors and flash data between requests | CookieSessionStore, TowerSessionStore |
SsrClient |
Server-side rendering | HttpSsrClient |
SharedProps |
Props for every page | InertiaConfig::share (closure) |
IntoErrorBag |
Validation errors from any library | validator, garde, maps and pairs |
| Flag | Default | Effect |
|---|---|---|
axum |
on | Axum extractor, tower layer, InertiaForm, Router, Precognition |
multipart |
off | File uploads (UploadedFile, MultipartStream) |
ssr |
off | HTTP SSR client (reqwest) |
cookie-session |
off | Signed-cookie session store |
tower-sessions |
off | Session store on tower-sessions |
validator |
off | Validated<T>; IntoErrorBag for validator::ValidationErrors |
garde |
off | GardeValidated<T>; IntoErrorBag for garde::Report |
csrf |
off | CsrfLayer and CsrfTokens |
embed |
off | EmbeddedAssets for a single-binary deploy |
devtools |
off | Recorder for the Inertia DevTools extension |
ts |
off | TypeScript bindings (ts-rs, inventory), Inertia::page |
testing |
off | veer::testing: helpers for tests of your handlers |
A feature that is off brings in none of its dependencies.
Veer follows the Inertia v3 protocol as of client 3.8 and inertia-laravel 3.5.
| Area | Status |
|---|---|
HTML and JSON page responses, Vary |
✅ |
Asset versioning (409, X-Inertia-Version) |
✅ |
Redirects (303, external 409, fragment 409) |
✅ |
Partial reloads (only, except, dot paths, reset) |
✅ |
| Optional, deferred, once, always props | ✅ |
| Rescued deferred props | ✅ |
| Merge, prepend, deep merge, match-on | ✅ |
| Infinite scroll | ✅ |
Shared props, sharedProps |
✅ |
| Validation errors, error bags | ✅ |
| Flash data | ✅ |
| History encryption, clear history, preserve fragment | ✅ |
| Big integers | ✅ |
| Precognition | ✅ |
| Server-side rendering | ✅ |
| DevTools protocol | ✅ (no handler or component source links) |
The wire formats are checked against the protocol document, the inertia-laravel source, and the client source, and the example app is checked in a browser with the React client. Vue and Svelte use the same client core.
Prop::try_newneeds.rescue()to rescue a failure; without it anErrgives a500. This is the same rule, in Rust terms.- Sessions hold only veer's data (errors, flash, previous URL). Veer is not a general session library.
- The once-prop expiry is set with a
Duration(.until(...)).