# Lockness Inertia.js Adapter Build modern single-page applications (SPAs) with server-side routing using Inertia.js protocol. ## Installation ```typescript import { inertiaMiddleware } from '@lockness/inertia' ``` ## Quick Start ### Configure Middleware ```typescript // app/kernel.tsx import { App } from '@lockness/core' import { inertiaMiddleware } from '@lockness/inertia' const app = new App() app.useMiddleware( inertiaMiddleware({ version: '1.0.0', }), ) await app.init({ controllersDir: './app/controller', staticDir: 'public', }) app.listen(3000) ``` ### Controller Usage ```typescript // app/controller/dashboard_controller.tsx import { type Context, Controller, Get } from '@lockness/core' @Controller('/') export class DashboardController { @Get('/dashboard') async show(c: Context) { const inertia = c.get('inertia') return inertia.render('Dashboard', { user: await getCurrentUser(c), stats: await getDashboardStats(), }) } } ``` ## Shared Props (Global Data) Share data available in all Inertia responses: ```typescript // app/kernel.tsx app.useMiddleware(async (c, next) => { const inertia = c.get('inertia') inertia.share({ auth: { user: await getCurrentUser(c), }, flash: c.get('session')?.flash ?? {}, appName: 'My App', }) return next() }) ``` ## Lazy Props Resolve expensive data only when component requests it: ```typescript @Get('/users/:id') async show(c: Context) { const inertia = c.get('inertia') const id = c.req.param('id') return inertia.render('Users/Show', { user: await this.userService.findById(id), // Only resolved if component requests it activity: () => this.activityService.getRecent(id), notifications: async () => await this.notificationService.for(id), }) } ``` ## Advanced Configuration ### Dynamic Version with Custom Root View ```typescript import { type InertiaConfig, inertiaMiddleware } from '@lockness/inertia' const inertiaConfig: InertiaConfig = { // Dynamic version based on build hash version: () => Deno.env.get('BUILD_HASH') ?? '1.0.0', // Custom root view rootView: (page) => { return ` ${page.props.title ?? 'My App'}
` }, } app.useMiddleware(inertiaMiddleware(inertiaConfig)) ``` ## How Inertia Works ### The Protocol 1. **First Load (No X-Inertia header)** - Server returns full HTML with `data-page` attribute - Client boots from embedded JSON data 2. **Subsequent Requests (X-Inertia: true)** - Server returns JSON with component and props - Client swaps component without page reload 3. **Version Checking** - Client sends `X-Inertia-Version` header - Server returns 409 if version mismatches - Client performs full page reload for latest assets 4. **Redirect Handling** - PUT/PATCH/DELETE redirects converted from 302 to 303 - Ensures proper form submission handling ### Page Object ```typescript { "component": "Users/Show", "props": { "user": { "id": 1, "name": "John" }, "errors": {} }, "url": "/users/1", "version": "1.0.0" } ``` ## API Reference ### `inertia.render(component, props?, options?)` Render an Inertia response: ```typescript return inertia.render('Dashboard', { users: await this.userService.all(), }) ``` **Parameters:** - `component` - Component name (e.g., `'Users/Index'`) - `props` - Props object (can include lazy functions) - `options.encryptHistory` - Encrypt history state - `options.clearHistory` - Clear encrypted history ### `inertia.share(props)` Share props globally: ```typescript inertia.share({ user: currentUser, flash: flashMessages, }) ``` ## Client-Side Setup ### React Example ```tsx // app.tsx import { createInertiaApp } from '@inertiajs/react' import { createRoot } from 'react-dom/client' createInertiaApp({ resolve: (name) => { const pages = import.meta.glob('./Pages/**/*.tsx', { eager: true }) return pages[`./Pages/${name}.tsx`] }, setup({ el, App, props }) { createRoot(el).render() }, }) ``` ### Vue Example ```typescript // app.js import { createInertiaApp } from '@inertiajs/vue3' import { createApp, h } from 'vue' createInertiaApp({ resolve: (name) => { const pages = import.meta.glob('./Pages/**/*.vue', { eager: true }) return pages[`./Pages/${name}.vue`] }, setup({ el, App, props, plugin }) { createApp({ render: () => h(App, props) }) .use(plugin) .mount(el) }, }) ``` ## Best Practices ### 1. Use Shared Props for Global Data ```typescript // ✅ Good - set once in middleware app.useMiddleware(async (c, next) => { const inertia = c.get('inertia') inertia.share({ user: await getCurrentUser(c) }) return next() }) // ❌ Bad - repeated in every controller return inertia.render('Dashboard', { user: await getCurrentUser(c), // ... }) ``` ### 2. Use Lazy Props for Expensive Operations ```typescript return inertia.render('Users/Show', { user: await this.userService.find(id), // Always resolved activity: () => this.activityService.recent(id), // Only if requested }) ``` ### 3. Version Your Assets Properly ```typescript inertiaMiddleware({ version: () => Deno.env.get('BUILD_HASH') ?? Date.now().toString(), }) ``` ## Troubleshooting ### Version Mismatches (409 errors) Use consistent versioning: ```typescript // Development inertiaMiddleware({ version: '1.0' }) // Production inertiaMiddleware({ version: () => Deno.env.get('BUILD_HASH')! }) ``` ### Shared Props Not Updating Set shared props in middleware, not globally: ```typescript app.useMiddleware(async (c, next) => { const inertia = c.get('inertia') inertia.share({ timestamp: Date.now() }) // ✅ Fresh every request return next() }) ``` ## Resources - [Inertia.js Official Docs](https://inertiajs.com) - [Inertia.js Protocol](https://inertiajs.com/the-protocol) - [Lockness Framework](https://lockness.land/docs)