Serinus Inertia
Use inertia_dart from Serinus route handlers.
serinus_inertia adapts Serinus requests and responses to the Inertia protocol
and gives you:
InertiaModulefor app-wide defaultsRequestContext.inertia()for rendering Inertia pagesRequestContext.inertiaLocation()for409 X-Inertia-Locationresponses- asset-aware HTML bootstrapping for Vite
- optional SSR integration, including managed
nodeorbunprocesses
Install
Add the server package:
dart pub add serinus_inertia
If you do not already have a client app, add the usual Inertia React stack in your frontend:
npm install @inertiajs/react react react-dom
npm install -D vite @vitejs/plugin-react
Getting Started
The shortest useful setup has four parts:
- Register
InertiaModule - Render a page from a Serinus controller
- Bootstrap the client app
- Run the server and Vite together
1. Register InertiaModule
Register the module once in your root module. If you provide asset settings, the default HTML wrapper will inject the correct Vite tags for first visits.
import 'package:serinus/serinus.dart';
import 'package:serinus_inertia/serinus_inertia.dart';
class AppModule extends Module {
AppModule()
: super(
imports: [
InertiaModule(
options: InertiaOptions(
version: '1.0.0',
assets: InertiaAssetOptions(
entry: 'src/main.jsx',
clientDirectory: 'client',
includeReactRefresh: true,
),
sharedProps: (context) async => {
'appName': 'Serinus Inertia',
},
),
),
],
controllers: [AppController()],
);
}
2. Render a page from a controller
Once the module is registered, render pages with context.inertia().
import 'package:inertia_dart/inertia_dart.dart';
import 'package:serinus/serinus.dart';
import 'package:serinus_inertia/serinus_inertia.dart';
class AppController extends Controller {
AppController() : super('/') {
on(Route.get('/'), (context) async {
return context.inertia(
component: 'Home',
props: {
'title': 'Hello from Serinus',
'stats': LazyProp(() => loadStats()),
},
);
});
}
}
context.inertia() returns:
- an HTML document on the first visit
- a JSON page payload for
X-Inertia: truerequests
3. Bootstrap the client app
The React client can use the same page tree for both client-side rendering and SSR.
import { createInertiaApp } from '@inertiajs/react'
import { createRoot, hydrateRoot } from 'react-dom/client'
import './index.css'
createInertiaApp({
title: (title) => (title ? `${title} | Serinus Inertia` : 'Serinus Inertia'),
resolve: (name) => {
const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true })
return pages[`./Pages/${name}.jsx`]
},
setup({ el, App, props }) {
if (el.hasChildNodes()) {
hydrateRoot(el, <App {...props} />)
return
}
createRoot(el).render(<App {...props} />)
},
})
If you want the package to generate the Vite hot-file plugin for you, add a small Dart script and run it once:
import 'dart:io';
import 'package:inertia_dart/inertia_dart.dart';
Future<void> main() async {
await writeInertiaViteHotFilePlugin(Directory('client'));
}
That writes client/inertia_hot_file.js, which you can import from
vite.config.js.
4. Run the app in development
In one terminal, run Vite:
cd client
npm install
npm run dev
In a second terminal, run your Serinus app:
dart pub get
dart run bin/main.dart
Open http://127.0.0.1:4000.
InertiaModule
InertiaModule is the high-level integration point. Use it when you want
shared defaults instead of repeating options in every handler.
InertiaOptions can define:
versionelementIdsharedPropsencryptHistoryclearHistoryassetsssrhtmlBuilder
If htmlBuilder is omitted, the default wrapper renders:
- the Inertia bootstrap container
- Vite dev tags or production asset tags when
assetsis configured - SSR head and body when SSR is enabled
Client Assets
Use InertiaAssetOptions when you want the package to know where the
frontend lives.
For development asset resolution, the package uses this order:
devServerUrlif you set it explicitlyhotFileif the Vite hot file exists- the production manifest when neither dev option is available
The package reads a single Vite hot file, not multiple hot files. By default
that path is client/public/hot.
If you want to generate the Vite plugin file from Dart instead of copying one
manually, use writeInertiaViteHotFilePlugin(Directory('client')) from
package:inertia_dart/inertia_dart.dart.
InertiaModule(
options: InertiaOptions(
assets: InertiaAssetOptions(
entry: 'src/main.jsx',
clientDirectory: 'client',
// Optional overrides:
// manifestPath: 'frontend/dist/.vite/manifest.json',
// hotFile: 'frontend/public/hot',
// devServerUrl: 'http://localhost:5173',
// baseUrl: '/',
),
),
)
That keeps the first-visit HTML generation inside the package instead of hand-writing script and stylesheet tags.
If your dev server runs somewhere unusual, either point directly at it:
assets: InertiaAssetOptions(
entry: 'src/main.jsx',
clientDirectory: 'client',
devServerUrl: 'http://127.0.0.1:5174',
),
or override the hot file path if your tooling writes it somewhere else:
assets: InertiaAssetOptions(
entry: 'src/main.jsx',
clientDirectory: 'client',
hotFile: 'frontend/public/hot',
),
SSR
Use InertiaSsrOptions for first-visit SSR.
Managed by Serinus
Enable manageProcess if you want Serinus to launch node or bun as a
separate child process during startup and stop it again on shutdown.
InertiaModule(
options: InertiaOptions(
ssr: InertiaSsrOptions(
enabled: true,
manageProcess: true,
runtime: 'node', // or 'bun'
bundle: 'client/dist/ssr.js',
),
),
)
When manageProcess is enabled and endpoint is omitted, the default endpoint
is http://127.0.0.1:13714/render, with matching /health and /shutdown
endpoints.
Managed separately
If you prefer to keep the SSR runtime outside the Serinus process, point at an existing endpoint instead:
InertiaModule(
options: InertiaOptions(
ssr: InertiaSsrOptions(
enabled: true,
endpoint: Uri.parse('http://127.0.0.1:13714/render'),
// Optional:
// healthEndpoint: Uri.parse('http://127.0.0.1:13714/health'),
),
),
)
You can start the SSR server from the package executable:
dart run serinus_inertia:ssr start --bundle client/dist/ssr.js
dart run serinus_inertia:ssr check
dart run serinus_inertia:ssr stop
The start command auto-detects common bundle paths such as:
client/dist/ssr.jsclient/dist/server/entry-server.jsbootstrap/ssr/ssr.mjs
The older dart run serinus_inertia:serinus_inertia ssr:start form remains
supported as an alias.
Custom gateways
If you already have your own SSR transport, pass a gateway implementation directly:
InertiaModule(
options: InertiaOptions(
ssr: InertiaSsrOptions(
enabled: true,
gateway: MyCustomSsrGateway(),
),
),
)
Low-Level Helpers
Use the lower-level helpers when you want to control response handling yourself.
final inertiaRequest = inertiaRequestFromSerinus(context.request);
final page = await context.buildInertiaPageData(
component: 'Dashboard',
props: {'user': {'name': 'Ada'}},
);
final response = await context.buildInertiaResponse(
component: 'Dashboard',
props: {'user': {'name': 'Ada'}},
);
applyInertiaResponse(context.res, response);
return response.html ?? response.toJson();
Location Responses
Use context.inertiaLocation('/login') when you need an Inertia
409 X-Inertia-Location response.
Example App
See example/ for a complete Serinus + Vite demo with:
- a simple landing page
- a users page with lazy props and remembered history state
- a feature lab for deferred props, merge props, scroll props, flash data, and history flags
- optional SSR instructions for both managed and external runtime modes
Libraries
- serinus_inertia
- Public entry point for the Serinus adapter package for Inertia Dart.