@arponascension/express-inertia 1.0.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -156
- package/dist/engine.d.mts +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.js +54 -15
- package/dist/engine.js.map +1 -1
- package/dist/engine.mjs +54 -15
- package/dist/engine.mjs.map +1 -1
- package/dist/index.d.mts +13 -3
- package/dist/index.d.ts +13 -3
- package/dist/index.js +145 -56
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +144 -57
- package/dist/index.mjs.map +1 -1
- package/dist/{types-Co2XESgs.d.mts → types-DgBPobaU.d.mts} +11 -1
- package/dist/{types-Co2XESgs.d.ts → types-DgBPobaU.d.ts} +11 -1
- package/dist/vite.d.mts +6 -2
- package/dist/vite.d.ts +6 -2
- package/dist/vite.js +30 -6
- package/dist/vite.js.map +1 -1
- package/dist/vite.mjs +30 -6
- package/dist/vite.mjs.map +1 -1
- package/package.json +22 -4
package/README.md
CHANGED
|
@@ -1,39 +1,89 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Express-Inertia — Inertia.js Middleware, Adapter & SSR for Express.js
|
|
2
2
|
|
|
3
3
|
[](https://npmjs.com/package/@arponascension/express-inertia)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://github.com/arponascension/express-inertia/actions)
|
|
6
7
|
[](https://github.com/arponascension/express-inertia)
|
|
7
8
|
|
|
8
|
-
**
|
|
9
|
+
**@arponascension/express-inertia** is a production-ready **Inertia.js adapter for Express.js**. It brings the official [Inertia.js](https://inertiajs.com) protocol to **Node.js and Express** apps so you can build modern **single-page applications (SPAs)** with **Vue 3**, **React**, or **Svelte** using classic server-side routing and controllers — without the complexity of a REST API or a client-side router.
|
|
10
|
+
|
|
11
|
+
It ships with a **Blade-style EJS template engine**, **zero-config Vite** integration (HMR in development, hashed `manifest.json` in production), **server-side rendering (SSR)** with circuit-breaker resilience, **security hardening**, **edge-runtime support** (Cloudflare Workers, Vercel Edge, Netlify Edge), and **first-class TypeScript** types — all in a tree-shakeable ~40KB bundle.
|
|
12
|
+
|
|
13
|
+
> New to Inertia.js? Read [What is Inertia.js?](#what-is-inertiajs) or the [official documentation](https://inertiajs.com/docs).
|
|
9
14
|
|
|
10
15
|
---
|
|
11
16
|
|
|
12
|
-
##
|
|
17
|
+
## Table of Contents
|
|
18
|
+
|
|
19
|
+
- [What is Inertia.js?](#what-is-inertiajs)
|
|
20
|
+
- [Why use Inertia.js with Express?](#why-use-inertiajs-with-express)
|
|
21
|
+
- [Features](#features)
|
|
22
|
+
- [Installation](#installation)
|
|
23
|
+
- [Quick Start: Express + Inertia + Vue 3](#quick-start-express--inertia--vue-3)
|
|
24
|
+
- [Using Inertia.js with React](#using-inertiajs-with-react)
|
|
25
|
+
- [Blade-style EJS Directives for Express](#blade-style-ejs-directives-for-express)
|
|
26
|
+
- [Vite Integration for Express](#vite-integration-for-express)
|
|
27
|
+
- [Server-Side Rendering (SSR) for SEO](#server-side-rendering-ssr-for-seo)
|
|
28
|
+
- [Response API: `res.inertia()`](#response-api-resinertia)
|
|
29
|
+
- [Prop Helpers (Lazy, Deferred, Merge)](#prop-helpers-lazy-deferred-merge)
|
|
30
|
+
- [Security Hardening](#security-hardening)
|
|
31
|
+
- [Structured Logging](#structured-logging)
|
|
32
|
+
- [Edge Runtime Compatibility](#edge-runtime-compatibility)
|
|
33
|
+
- [Resilience: Circuit Breaker & Retry](#resilience-circuit-breaker--retry)
|
|
34
|
+
- [Performance](#performance)
|
|
35
|
+
- [API Reference](#api-reference)
|
|
36
|
+
- [FAQ](#faq)
|
|
37
|
+
- [Compatibility Matrix](#compatibility-matrix)
|
|
38
|
+
- [Testing](#testing)
|
|
39
|
+
- [Resources & Ecosystem](#resources--ecosystem)
|
|
40
|
+
- [License](#license)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## What is Inertia.js?
|
|
45
|
+
|
|
46
|
+
[Inertia.js](https://inertiajs.com) is a protocol (by [Jonathan Reinink](https://twitter.com/reinink)) for building **modern single-page applications (SPAs)** with **classic server-side routing and controllers**. Instead of building a JSON API and a separate JavaScript frontend, you keep writing server-side routes and controllers exactly as you do today — but render your pages with **Vue**, **React**, or **Svelte** components.
|
|
47
|
+
|
|
48
|
+
Inertia requests return plain JSON to the client (no full page reloads); real full-page visits return your server-rendered HTML template. Page components, props, versions, and partial reloads are all handled automatically by the Inertia protocol.
|
|
13
49
|
|
|
14
|
-
|
|
50
|
+
**express-inertia** is the Inertia.js server-side adapter for **Express.js** / **Node.js** — the Node equivalent of the official [Laravel](https://inertiajs.com/docs/getting-started) and [Rails](https://github.com/inertiajs/inertia-rails) adapters. The client side stays 100% compatible with the official `@inertiajs/vue3`, `@inertiajs/react`, and `@inertiajs/svelte` packages.
|
|
51
|
+
|
|
52
|
+
## Why use Inertia.js with Express?
|
|
53
|
+
|
|
54
|
+
- **No API layer, no client router** — classic MVC controllers on the server, components on the client.
|
|
55
|
+
- **SEO-friendly** — full-page visits serve real HTML; enable **SSR** to pre-render pages for search engine indexing.
|
|
56
|
+
- **Fast navigation** — Inertia requests are small JSON payloads with automatic prop merging, lazy evaluation, and deferred data.
|
|
57
|
+
- **One codebase** — policies, validation, and DB queries stay where they belong, in your Express routes.
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
| Feature | What you get |
|
|
15
62
|
|---|---|
|
|
16
|
-
| Blade-style EJS | Write Laravel Blade syntax (`@inertia`, `@vite`, `@csrf`) directly
|
|
17
|
-
| Zero-config Vite |
|
|
18
|
-
| SSR
|
|
19
|
-
| Security
|
|
20
|
-
| Form
|
|
21
|
-
| Prefetch
|
|
22
|
-
| Edge
|
|
23
|
-
| Structured
|
|
63
|
+
| **Blade-style EJS directives** | Write Laravel Blade syntax (`@inertia`, `@vite`, `@csrf`, `@inertiaHead`) directly inside `.ejs` templates |
|
|
64
|
+
| **Zero-config Vite integration** | Automatic HMR in development; hashed `manifest.json` asset resolution in production |
|
|
65
|
+
| **Server-side rendering (SSR)** | Express + Inertia SSR endpoint proxying with retry/backoff and **circuit breaker** fallback to client rendering |
|
|
66
|
+
| **Security hardening** | Component name validation against path traversal, `viewData` sanitization, SRI hash generation |
|
|
67
|
+
| **Form helpers** | Semantic `postForm`, `putForm`, `patchForm`, `deleteForm` with flash messages |
|
|
68
|
+
| **Prefetch & preload** | Auto-generate `<link rel="prefetch">` / `<link rel="preload">` tags from the Vite manifest |
|
|
69
|
+
| **Edge compatible** | Works on Cloudflare Workers, Vercel Edge, Netlify Edge via injected manifest/config (no `fs`) |
|
|
70
|
+
| **Structured logging** | Pluggable logger abstraction with request correlation IDs (`X-Request-ID`) |
|
|
71
|
+
| **TypeScript first** | Full type definitions, ESM + CJS builds, tree-shakeable ~40KB bundle |
|
|
24
72
|
|
|
25
73
|
---
|
|
26
74
|
|
|
27
|
-
##
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
**Requirements:** Node.js `>=18` (the runtime globals `fetch`, `AbortController`, `crypto.randomUUID`, and `fs.rmSync` are used by the SSR and middleware layers). Express `^4.18 || ^5` is a peer dependency.
|
|
28
78
|
|
|
29
79
|
```bash
|
|
30
80
|
npm install @arponascension/express-inertia ejs express
|
|
31
81
|
|
|
32
|
-
# Choose one client adapter:
|
|
82
|
+
# Choose one client adapter — Vue 3:
|
|
33
83
|
npm install @inertiajs/vue3 vue
|
|
34
84
|
npm install -D vite @vitejs/plugin-vue
|
|
35
85
|
|
|
36
|
-
# Or
|
|
86
|
+
# Or React:
|
|
37
87
|
npm install @inertiajs/react react react-dom
|
|
38
88
|
npm install -D vite @vitejs/plugin-react
|
|
39
89
|
```
|
|
@@ -46,9 +96,9 @@ npm install express
|
|
|
46
96
|
|
|
47
97
|
---
|
|
48
98
|
|
|
49
|
-
##
|
|
99
|
+
## Quick Start: Express + Inertia + Vue 3
|
|
50
100
|
|
|
51
|
-
### 1. Configure Express
|
|
101
|
+
### 1. Configure the Express Inertia middleware
|
|
52
102
|
|
|
53
103
|
```ts
|
|
54
104
|
import express from 'express';
|
|
@@ -57,17 +107,17 @@ import { inertia, createInertiaEngine } from '@arponascension/express-inertia';
|
|
|
57
107
|
|
|
58
108
|
const app = express();
|
|
59
109
|
|
|
60
|
-
// Blade-compatible EJS
|
|
110
|
+
// Blade-compatible EJS view engine for Express
|
|
61
111
|
app.engine('ejs', createInertiaEngine());
|
|
62
112
|
app.set('view engine', 'ejs');
|
|
63
113
|
app.set('views', path.join(__dirname, 'views'));
|
|
64
114
|
|
|
65
|
-
// Parse request bodies
|
|
115
|
+
// Parse request bodies and serve static files
|
|
66
116
|
app.use(express.json());
|
|
67
117
|
app.use(express.urlencoded({ extended: true }));
|
|
68
118
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
69
119
|
|
|
70
|
-
// Inertia
|
|
120
|
+
// Inertia.js middleware for Express
|
|
71
121
|
app.use(
|
|
72
122
|
inertia({
|
|
73
123
|
rootView: 'base.ejs',
|
|
@@ -79,7 +129,7 @@ app.use(
|
|
|
79
129
|
})
|
|
80
130
|
);
|
|
81
131
|
|
|
82
|
-
//
|
|
132
|
+
// Classic server-side routes
|
|
83
133
|
app.get('/', (req, res) => {
|
|
84
134
|
res.inertia('Home', { title: 'Welcome Home' });
|
|
85
135
|
});
|
|
@@ -87,7 +137,7 @@ app.get('/', (req, res) => {
|
|
|
87
137
|
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
|
|
88
138
|
```
|
|
89
139
|
|
|
90
|
-
### 2. Create
|
|
140
|
+
### 2. Create the root view (`views/base.ejs`)
|
|
91
141
|
|
|
92
142
|
```html
|
|
93
143
|
<!DOCTYPE html>
|
|
@@ -107,7 +157,7 @@ app.listen(3000, () => console.log('Server running on http://localhost:3000'));
|
|
|
107
157
|
</html>
|
|
108
158
|
```
|
|
109
159
|
|
|
110
|
-
### 3. Configure the Vue client (`src/main.ts`)
|
|
160
|
+
### 3. Configure the Vue 3 client (`src/main.ts`)
|
|
111
161
|
|
|
112
162
|
```ts
|
|
113
163
|
import { createApp, h } from 'vue';
|
|
@@ -121,7 +171,25 @@ createInertiaApp({
|
|
|
121
171
|
});
|
|
122
172
|
```
|
|
123
173
|
|
|
124
|
-
### 4. Configure
|
|
174
|
+
### 4. Configure Vite (`vite.config.ts`)
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { defineConfig } from 'vite';
|
|
178
|
+
import vue from '@vitejs/plugin-vue';
|
|
179
|
+
import { inertiaVitePlugin } from '@arponascension/express-inertia/vite';
|
|
180
|
+
|
|
181
|
+
export default defineConfig({
|
|
182
|
+
plugins: [vue(), inertiaVitePlugin()],
|
|
183
|
+
base: '/build/',
|
|
184
|
+
build: { manifest: true, outDir: 'public/build' },
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Start Express and Vite in separate terminals during development; `inertiaVitePlugin()` writes `public/hot` so the EJS root view automatically uses the Vite dev server. Run `vite build` before production deployment to write `public/build/.vite/manifest.json`.
|
|
189
|
+
|
|
190
|
+
## Using Inertia.js with React
|
|
191
|
+
|
|
192
|
+
Install the React client adapter, configure the React client (`src/main.tsx`):
|
|
125
193
|
|
|
126
194
|
```tsx
|
|
127
195
|
import { createRoot } from 'react-dom/client';
|
|
@@ -142,21 +210,7 @@ For React, use a `.tsx` entrypoint in the root view and include `@viteReactRefre
|
|
|
142
210
|
@vite('src/main.tsx')
|
|
143
211
|
```
|
|
144
212
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
import { defineConfig } from 'vite';
|
|
149
|
-
import vue from '@vitejs/plugin-vue';
|
|
150
|
-
import { inertiaVitePlugin } from '@arponascension/express-inertia/vite';
|
|
151
|
-
|
|
152
|
-
export default defineConfig({
|
|
153
|
-
plugins: [vue(), inertiaVitePlugin()],
|
|
154
|
-
base: '/build/',
|
|
155
|
-
build: { manifest: true, outDir: 'public/build' },
|
|
156
|
-
});
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
For React, replace the Vue plugin with `@vitejs/plugin-react` and keep the same `inertiaVitePlugin()`:
|
|
213
|
+
Replace the Vue plugin with `@vitejs/plugin-react` in `vite.config.ts` and keep the same `inertiaVitePlugin()`:
|
|
160
214
|
|
|
161
215
|
```ts
|
|
162
216
|
import { defineConfig } from 'vite';
|
|
@@ -170,13 +224,11 @@ export default defineConfig({
|
|
|
170
224
|
});
|
|
171
225
|
```
|
|
172
226
|
|
|
173
|
-
Start Express and Vite in separate terminals during development; `inertiaVitePlugin()` writes `public/hot` so the EJS root view automatically uses the Vite dev server. Run `vite build` before production deployment to write `public/build/.vite/manifest.json`.
|
|
174
|
-
|
|
175
227
|
---
|
|
176
228
|
|
|
177
|
-
##
|
|
229
|
+
## Blade-style EJS Directives for Express
|
|
178
230
|
|
|
179
|
-
Write Laravel Blade-style syntax inside EJS templates:
|
|
231
|
+
Write Laravel Blade-style syntax inside EJS templates, powered by a custom Express view engine:
|
|
180
232
|
|
|
181
233
|
| Directive | Component Tag | Output |
|
|
182
234
|
|---|---|---|
|
|
@@ -190,7 +242,7 @@ Write Laravel Blade-style syntax inside EJS templates:
|
|
|
190
242
|
| `@routes` | `<x-routes />` | Ziggy / route definitions |
|
|
191
243
|
| `@json(myVar)` | — | Safely stringifies a JS object |
|
|
192
244
|
|
|
193
|
-
### Custom
|
|
245
|
+
### Custom directives
|
|
194
246
|
|
|
195
247
|
```ts
|
|
196
248
|
import { registerDirective } from '@arponascension/express-inertia';
|
|
@@ -200,11 +252,87 @@ registerDirective('uppercase', (args) => `<%= (${args}).toUpperCase() %>`);
|
|
|
200
252
|
|
|
201
253
|
---
|
|
202
254
|
|
|
203
|
-
##
|
|
255
|
+
## Vite Integration for Express
|
|
256
|
+
|
|
257
|
+
Auto-detects the Vite dev server via the hot file (`public/hot`) and reads `manifest.json` in production:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
app.use(
|
|
261
|
+
inertia({
|
|
262
|
+
vite: {
|
|
263
|
+
publicDir: 'public',
|
|
264
|
+
buildDir: 'build',
|
|
265
|
+
devServerUrl: 'http://localhost:5173',
|
|
266
|
+
hotFile: 'public/hot',
|
|
267
|
+
base: '/build/',
|
|
268
|
+
},
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Prefetch and preload helpers
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { createPrefetchHelper } from '@arponascension/express-inertia';
|
|
277
|
+
|
|
278
|
+
const prefetch = createPrefetchHelper(viteHelper);
|
|
279
|
+
|
|
280
|
+
// In your base template:
|
|
281
|
+
<%= prefetch.prefetch('src/Pages/Dashboard.vue') %>
|
|
282
|
+
// <link rel="prefetch" href="/build/assets/Dashboard.abc1234.js">
|
|
283
|
+
|
|
284
|
+
<%= prefetch.preload('src/main.ts') %>
|
|
285
|
+
// <link rel="preload" href="/build/assets/main.abc1234.js">
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Server-Side Rendering (SSR) for SEO
|
|
291
|
+
|
|
292
|
+
**Server-side rendering (SSR) pre-renders your JavaScript pages on the server, so visitors and search engines receive fully rendered HTML** — better indexability, faster first paint, and a better core-web-vitals story. Inertia's official SSR server is a Node.js background process; `express-inertia` proxies render requests to it (default `http://127.0.0.1:13714/render`) with **retries, exponential backoff, a circuit breaker, and graceful client-side fallback** if SSR fails.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
app.use(
|
|
296
|
+
inertia({
|
|
297
|
+
ssr: {
|
|
298
|
+
enabled: true,
|
|
299
|
+
url: 'http://127.0.0.1:13714/render',
|
|
300
|
+
timeout: 2000,
|
|
301
|
+
fallback: true,
|
|
302
|
+
retry: { maxRetries: 2, baseDelayMs: 200, maxDelayMs: 2000 },
|
|
303
|
+
circuitBreaker: { failureThreshold: 5, cooldownMs: 30000 },
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Or pass a custom in-process render function (no HTTP endpoint needed):
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
app.use(
|
|
313
|
+
inertia({
|
|
314
|
+
ssr: {
|
|
315
|
+
enabled: true,
|
|
316
|
+
render: async (page) => {
|
|
317
|
+
return {
|
|
318
|
+
head: [`<title inertia>${page.props.title}</title>`],
|
|
319
|
+
body: '<div id="app">...</div>',
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Set up the SSR server for your client framework following the [official Inertia.js SSR guide](https://inertiajs.com/docs/v3/advanced/server-side-rendering).
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Response API: `res.inertia()`
|
|
204
332
|
|
|
205
333
|
### `res.inertia(component, props?, viewData?)`
|
|
206
334
|
|
|
207
|
-
Renders an Inertia response. In AJAX requests it returns JSON; on full page loads it renders the root template
|
|
335
|
+
Renders an Inertia response. In AJAX (X-Inertia) requests it returns JSON; on full page loads it renders the root template with `viewData` passed only to the template:
|
|
208
336
|
|
|
209
337
|
```ts
|
|
210
338
|
app.get('/users', (req, res) => {
|
|
@@ -216,7 +344,7 @@ app.get('/users', (req, res) => {
|
|
|
216
344
|
});
|
|
217
345
|
```
|
|
218
346
|
|
|
219
|
-
### Form
|
|
347
|
+
### Form helpers
|
|
220
348
|
|
|
221
349
|
Semantic form submission helpers with automatic method semantics:
|
|
222
350
|
|
|
@@ -238,7 +366,7 @@ app.delete('/account', (req, res) => {
|
|
|
238
366
|
});
|
|
239
367
|
```
|
|
240
368
|
|
|
241
|
-
### Flash
|
|
369
|
+
### Flash messages
|
|
242
370
|
|
|
243
371
|
```ts
|
|
244
372
|
res.inertia
|
|
@@ -246,17 +374,17 @@ res.inertia
|
|
|
246
374
|
.inertia('Dashboard');
|
|
247
375
|
```
|
|
248
376
|
|
|
249
|
-
### Redirect
|
|
377
|
+
### Redirect helpers
|
|
250
378
|
|
|
251
379
|
```ts
|
|
252
|
-
// External redirect / full reload
|
|
380
|
+
// External redirect / full reload (409 + X-Inertia-Location)
|
|
253
381
|
res.inertia.location('https://stripe.com/checkout');
|
|
254
382
|
|
|
255
383
|
// Back to referrer (303 See Other)
|
|
256
384
|
res.inertia.back('/dashboard');
|
|
257
385
|
```
|
|
258
386
|
|
|
259
|
-
### History
|
|
387
|
+
### History encryption (Inertia v2)
|
|
260
388
|
|
|
261
389
|
```ts
|
|
262
390
|
res.inertia.encryptHistory(true);
|
|
@@ -266,7 +394,9 @@ res.inertia('SecretReport');
|
|
|
266
394
|
|
|
267
395
|
---
|
|
268
396
|
|
|
269
|
-
##
|
|
397
|
+
## Prop Helpers (Lazy, Deferred, Merge)
|
|
398
|
+
|
|
399
|
+
Fine-grained control over what props are sent to the client:
|
|
270
400
|
|
|
271
401
|
```ts
|
|
272
402
|
import { lazy, always, defer, merge, optional } from '@arponascension/express-inertia';
|
|
@@ -295,102 +425,41 @@ app.get('/dashboard', (req, res) => {
|
|
|
295
425
|
|
|
296
426
|
---
|
|
297
427
|
|
|
298
|
-
##
|
|
428
|
+
## Security Hardening
|
|
299
429
|
|
|
300
|
-
|
|
430
|
+
### Component name validation
|
|
301
431
|
|
|
302
|
-
|
|
303
|
-
app.use(
|
|
304
|
-
inertia({
|
|
305
|
-
vite: {
|
|
306
|
-
publicDir: 'public',
|
|
307
|
-
buildDir: 'build',
|
|
308
|
-
devServerUrl: 'http://localhost:5173',
|
|
309
|
-
hotFile: 'public/hot',
|
|
310
|
-
base: '/build/',
|
|
311
|
-
},
|
|
312
|
-
})
|
|
313
|
-
);
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### Prefetch Helpers
|
|
317
|
-
|
|
318
|
-
```ts
|
|
319
|
-
import { createPrefetchHelper } from '@arponascension/express-inertia';
|
|
320
|
-
|
|
321
|
-
const prefetch = createPrefetchHelper(viteHelper);
|
|
322
|
-
|
|
323
|
-
// In your base template:
|
|
324
|
-
<%= prefetch.prefetch('src/Pages/Dashboard.vue') %>
|
|
325
|
-
// <link rel="prefetch" href="/build/assets/Dashboard.abc1234.js">
|
|
326
|
-
|
|
327
|
-
<%= prefetch.preload('src/main.ts') %>
|
|
328
|
-
// <link rel="preload" href="/build/assets/main.abc1234.js">
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
---
|
|
332
|
-
|
|
333
|
-
## 🌐 Server-Side Rendering (SSR)
|
|
334
|
-
|
|
335
|
-
With built-in resilience:
|
|
336
|
-
|
|
337
|
-
```ts
|
|
338
|
-
app.use(
|
|
339
|
-
inertia({
|
|
340
|
-
ssr: {
|
|
341
|
-
enabled: true,
|
|
342
|
-
url: 'http://127.0.0.1:13714/render',
|
|
343
|
-
timeout: 2000,
|
|
344
|
-
fallback: true,
|
|
345
|
-
retry: { maxRetries: 2, baseDelayMs: 200, maxDelayMs: 2000 },
|
|
346
|
-
circuitBreaker: { failureThreshold: 5, cooldownMs: 30000 },
|
|
347
|
-
},
|
|
348
|
-
})
|
|
349
|
-
);
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
Or custom render function:
|
|
432
|
+
Prevents path traversal and injection attacks:
|
|
353
433
|
|
|
354
434
|
```ts
|
|
355
435
|
app.use(
|
|
356
436
|
inertia({
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return {
|
|
361
|
-
head: [`<title inertia>${page.props.title}</title>`],
|
|
362
|
-
body: '<div id="app">...</div>',
|
|
363
|
-
};
|
|
364
|
-
},
|
|
437
|
+
security: {
|
|
438
|
+
validateComponentNames: true,
|
|
439
|
+
componentNamePattern: /^[\w\/-]+$/,
|
|
365
440
|
},
|
|
366
441
|
})
|
|
367
442
|
);
|
|
368
443
|
```
|
|
369
444
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
## 🔒 Security
|
|
445
|
+
### ViewData sanitization
|
|
373
446
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
Prevents path traversal and injection attacks:
|
|
447
|
+
Automatically strips functions and `undefined` values from template data. Independently configurable from component name validation via `sanitizeViewData`:
|
|
377
448
|
|
|
378
449
|
```ts
|
|
379
450
|
app.use(
|
|
380
451
|
inertia({
|
|
381
452
|
security: {
|
|
382
|
-
validateComponentNames:
|
|
383
|
-
|
|
453
|
+
validateComponentNames: false, // allow arbitrary component names
|
|
454
|
+
sanitizeViewData: true, // still sanitize view data (default)
|
|
384
455
|
},
|
|
385
456
|
})
|
|
386
457
|
);
|
|
387
458
|
```
|
|
388
459
|
|
|
389
|
-
|
|
460
|
+
Set `sanitizeViewData: false` to preserve functions and `undefined` values in template locals (e.g. when passing helper functions to your root view by design).
|
|
390
461
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
### SRI Hash Generation
|
|
462
|
+
### SRI hash generation
|
|
394
463
|
|
|
395
464
|
```ts
|
|
396
465
|
import { generateSriHash } from '@arponascension/express-inertia';
|
|
@@ -401,7 +470,7 @@ const hash = await generateSriHash(assetContent);
|
|
|
401
470
|
|
|
402
471
|
---
|
|
403
472
|
|
|
404
|
-
##
|
|
473
|
+
## Structured Logging
|
|
405
474
|
|
|
406
475
|
Replace `console.warn` with a pluggable logger:
|
|
407
476
|
|
|
@@ -412,7 +481,7 @@ import pino from 'pino';
|
|
|
412
481
|
setGlobalLogger(createLogger({ prefix: 'my-app', logger: pino() }));
|
|
413
482
|
```
|
|
414
483
|
|
|
415
|
-
### Request
|
|
484
|
+
### Request correlation IDs
|
|
416
485
|
|
|
417
486
|
```ts
|
|
418
487
|
import { requestIdMiddleware } from '@arponascension/express-inertia';
|
|
@@ -425,9 +494,9 @@ Correlates logs across the request lifecycle using `X-Request-ID` or auto-genera
|
|
|
425
494
|
|
|
426
495
|
---
|
|
427
496
|
|
|
428
|
-
##
|
|
497
|
+
## Edge Runtime Compatibility
|
|
429
498
|
|
|
430
|
-
Works on Cloudflare Workers, Vercel Edge, and Netlify Edge by bypassing Node.js `fs`/`path
|
|
499
|
+
Works on Cloudflare Workers, Vercel Edge, and Netlify Edge by bypassing Node.js `fs`/`path` — inject the manifest, config, and template source directly:
|
|
431
500
|
|
|
432
501
|
```ts
|
|
433
502
|
const viteHelper = createViteHelper({
|
|
@@ -444,9 +513,9 @@ app.engine('ejs', createInertiaEngine({
|
|
|
444
513
|
|
|
445
514
|
---
|
|
446
515
|
|
|
447
|
-
##
|
|
516
|
+
## Resilience: Circuit Breaker & Retry
|
|
448
517
|
|
|
449
|
-
### Circuit
|
|
518
|
+
### Circuit breaker (SSR)
|
|
450
519
|
|
|
451
520
|
Prevents cascade failures when the SSR endpoint is down:
|
|
452
521
|
|
|
@@ -460,7 +529,7 @@ const breaker = new CircuitBreaker({
|
|
|
460
529
|
});
|
|
461
530
|
```
|
|
462
531
|
|
|
463
|
-
### Retry with
|
|
532
|
+
### Retry with exponential backoff
|
|
464
533
|
|
|
465
534
|
```ts
|
|
466
535
|
import { calculateBackoff } from '@arponascension/express-inertia';
|
|
@@ -470,29 +539,29 @@ const delay = calculateBackoff(attempt, 200, 2000);
|
|
|
470
539
|
|
|
471
540
|
---
|
|
472
541
|
|
|
473
|
-
##
|
|
542
|
+
## Performance
|
|
474
543
|
|
|
475
|
-
- **Tree-shakeable**:
|
|
476
|
-
- **Template caching**:
|
|
477
|
-
- **Lazy props**:
|
|
478
|
-
- **Prefetching**:
|
|
479
|
-
- **Minified bundle**: ~40KB
|
|
544
|
+
- **Tree-shakeable**: import only what you need (ESM + CJS dual builds)
|
|
545
|
+
- **Template caching**: compiled EJS templates cached (mtime-based in development, ejs `cache: true` in production)
|
|
546
|
+
- **Lazy props**: defer expensive computations until needed
|
|
547
|
+
- **Prefetching**: preload likely navigation targets from the Vite manifest
|
|
548
|
+
- **Minified bundle**: ~40KB main entry
|
|
480
549
|
|
|
481
550
|
---
|
|
482
551
|
|
|
483
|
-
##
|
|
552
|
+
## API Reference
|
|
484
553
|
|
|
485
554
|
### Middleware
|
|
486
555
|
|
|
487
556
|
| Export | Description |
|
|
488
557
|
|---|---|
|
|
489
|
-
| `inertia(options?)` | Express middleware factory |
|
|
558
|
+
| `inertia(options?)` | Inertia.js Express middleware factory |
|
|
490
559
|
| `createInertia(options?)` | Same as `inertia`, explicit name |
|
|
491
560
|
| `requestIdMiddleware()` | Assigns `req.id` for log correlation |
|
|
492
561
|
| `createInertiaEngine(options?)` | EJS view engine with Blade support |
|
|
493
562
|
| `inertiaEngine` | Default engine instance |
|
|
494
563
|
|
|
495
|
-
### Prop
|
|
564
|
+
### Prop helpers
|
|
496
565
|
|
|
497
566
|
| Export | Description |
|
|
498
567
|
|---|---|
|
|
@@ -520,7 +589,7 @@ const delay = calculateBackoff(attempt, 200, 2000);
|
|
|
520
589
|
|
|
521
590
|
| Export | Description |
|
|
522
591
|
|---|---|
|
|
523
|
-
| `CircuitBreaker` | CLOSED/OPEN/HALF_OPEN state machine |
|
|
592
|
+
| `CircuitBreaker` | CLOSED / OPEN / HALF_OPEN state machine |
|
|
524
593
|
| `shouldRetry(error, status, codes)` | Retry decision helper |
|
|
525
594
|
| `calculateBackoff(attempt, base, max)` | Exponential backoff with jitter |
|
|
526
595
|
| `resetAllCircuitBreakers()` | Bulk reset for testing |
|
|
@@ -530,32 +599,85 @@ const delay = calculateBackoff(attempt, 200, 2000);
|
|
|
530
599
|
| Export | Description |
|
|
531
600
|
|---|---|
|
|
532
601
|
| `createViteHelper(config?)` | Vite asset resolver |
|
|
533
|
-
| `inertiaVitePlugin(opts?)` | Auto write/remove hot file |
|
|
534
|
-
| `createPrefetchHelper(viteHelper)` | Prefetch/preload generator |
|
|
602
|
+
| `inertiaVitePlugin(opts?)` | Auto write/remove the Vite hot file |
|
|
603
|
+
| `createPrefetchHelper(viteHelper)` | Prefetch/preload tag generator |
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## FAQ
|
|
608
|
+
|
|
609
|
+
### What is Inertia.js?
|
|
610
|
+
|
|
611
|
+
Inertia.js is a protocol for building modern single-page applications (SPAs) using classic server-side routing and controllers, without building an API. Server routes render Vue, React, or Svelte page components; navigation between pages uses small JSON responses instead of full page reloads.
|
|
612
|
+
|
|
613
|
+
### How do I use Inertia.js with Express?
|
|
614
|
+
|
|
615
|
+
Install `@arponascension/express-inertia`, register the `inertia()` middleware (plus the `createInertiaEngine()` EJS view engine), and return `res.inertia('Component', { props })` from your routes. See the [Quick Start](#quick-start-express--inertia--vue-3) above.
|
|
616
|
+
|
|
617
|
+
### Does express-inertia support Vue 3, React, and Svelte?
|
|
618
|
+
|
|
619
|
+
Yes — the client side uses the official `@inertiajs/vue3`, `@inertiajs/react`, and `@inertiajs/svelte` packages, so whichever framework you use with Vite works with this adapter.
|
|
620
|
+
|
|
621
|
+
### Does express-inertia support server-side rendering (SSR)?
|
|
622
|
+
|
|
623
|
+
Yes. Enable `ssr: { enabled: true }` and point it at the official Inertia SSR server (or pass a custom `render` function). SSR is protected by retries, exponential backoff, and a circuit breaker with client-side fallback.
|
|
624
|
+
|
|
625
|
+
### What template engine does express-inertia use?
|
|
626
|
+
|
|
627
|
+
EJS, extended with Laravel Blade-style directives (`@inertia`, `@vite`, `@csrf`, `@inertiaHead`, and more). A custom `createInertiaEngine()` is provided so templates load through Express's normal view engine mechanism.
|
|
628
|
+
|
|
629
|
+
### Is express-inertia compatible with edge runtimes?
|
|
630
|
+
|
|
631
|
+
Yes. Provide `manifest`/`isDev`/`devServerUrlOverride` and a `templateSource` so no Node.js `fs` or `path` access is required — it runs on Cloudflare Workers, Vercel Edge, and Netlify Edge.
|
|
632
|
+
|
|
633
|
+
### Which versions of Node.js and Express are supported?
|
|
634
|
+
|
|
635
|
+
Node.js `>=18` (tested on 18, 20, 22, 25.8.x) and Express `^4.18 || ^5`. See the [compatibility matrix](#compatibility-matrix).
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
## Compatibility Matrix
|
|
640
|
+
|
|
641
|
+
The following versions are exercised by the integration suite. Test your application before upgrading a major version.
|
|
642
|
+
|
|
643
|
+
| Dependency | Supported | Tested |
|
|
644
|
+
|---|---|---|
|
|
645
|
+
| Node.js | >= 18 | 18, 20, 22, 25.8.x |
|
|
646
|
+
| Express | ^4.18 \|\| ^5 | 4.22.x |
|
|
647
|
+
| `@inertiajs/core` | 2.x | 2.3.27 |
|
|
648
|
+
| Vue | 3.x | 3.5.42 |
|
|
535
649
|
|
|
536
650
|
---
|
|
537
651
|
|
|
538
|
-
##
|
|
652
|
+
## Testing
|
|
539
653
|
|
|
540
654
|
```bash
|
|
541
655
|
npm test
|
|
542
656
|
```
|
|
543
657
|
|
|
544
|
-
Run the full test suite with Vitest. The suite includes Vue/Inertia protocol integration coverage and a real Vite production-manifest build.
|
|
658
|
+
Run the full test suite with [Vitest](https://vitest.dev). The suite includes Vue/Inertia protocol integration coverage and a real Vite production-manifest build.
|
|
545
659
|
|
|
546
|
-
|
|
660
|
+
```bash
|
|
661
|
+
npm run typecheck # TypeScript type checking
|
|
662
|
+
npm run lint # ESLint
|
|
663
|
+
npm run test:coverage # Coverage report (v8)
|
|
664
|
+
```
|
|
547
665
|
|
|
548
|
-
|
|
666
|
+
---
|
|
549
667
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
668
|
+
## Resources & Ecosystem
|
|
669
|
+
|
|
670
|
+
- [Inertia.js Documentation](https://inertiajs.com/docs) — the official protocol, client setup, and SSR guide
|
|
671
|
+
- [inertiajs/inertia](https://github.com/inertiajs/inertia) — the official client library monorepo (Vue, React, Svelte)
|
|
672
|
+
- [Express.js](https://expressjs.com) — the Node.js web framework this adapter targets
|
|
673
|
+
- [Vite](https://vite.dev) — the frontend build tool used for HMR and production bundles
|
|
674
|
+
- [CHANGELOG.md](./CHANGELOG.md) — release notes
|
|
675
|
+
- [CONTRIBUTING.md](./CONTRIBUTING.md) — how to contribute
|
|
676
|
+
|
|
677
|
+
Looking for Inertia.js on another framework? Official adapters exist for [Laravel](https://inertiajs.com/docs/getting-started) and [Rails](https://github.com/inertiajs/inertia-rails), and community adapters cover Fastify, Hono, and more.
|
|
556
678
|
|
|
557
679
|
---
|
|
558
680
|
|
|
559
|
-
##
|
|
681
|
+
## License
|
|
560
682
|
|
|
561
|
-
MIT © [Arpon](https://github.com/
|
|
683
|
+
MIT © [Arpon Ascension](https://github.com/arponascension/)
|