@arponascension/express-inertia 1.0.0 → 1.5.0
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 +301 -137
- 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,34 +1,91 @@
|
|
|
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 adapter](https://inertiajs.com/docs/seeding-data) and [Raill adapter](https://inertiajs.com/docs/rails/getting-started). 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
|
-
npm install @arponascension/express-inertia ejs express
|
|
80
|
+
npm install @arponascension/express-inertia ejs express
|
|
81
|
+
|
|
82
|
+
# Choose one client adapter — Vue 3:
|
|
83
|
+
npm install @inertiajs/vue3 vue
|
|
31
84
|
npm install -D vite @vitejs/plugin-vue
|
|
85
|
+
|
|
86
|
+
# Or React:
|
|
87
|
+
npm install @inertiajs/react react react-dom
|
|
88
|
+
npm install -D vite @vitejs/plugin-react
|
|
32
89
|
```
|
|
33
90
|
|
|
34
91
|
Peer dependencies:
|
|
@@ -39,9 +96,9 @@ npm install express
|
|
|
39
96
|
|
|
40
97
|
---
|
|
41
98
|
|
|
42
|
-
##
|
|
99
|
+
## Quick Start: Express + Inertia + Vue 3
|
|
43
100
|
|
|
44
|
-
### 1. Configure Express
|
|
101
|
+
### 1. Configure the Express Inertia middleware
|
|
45
102
|
|
|
46
103
|
```ts
|
|
47
104
|
import express from 'express';
|
|
@@ -50,17 +107,17 @@ import { inertia, createInertiaEngine } from '@arponascension/express-inertia';
|
|
|
50
107
|
|
|
51
108
|
const app = express();
|
|
52
109
|
|
|
53
|
-
// Blade-compatible EJS
|
|
110
|
+
// Blade-compatible EJS view engine for Express
|
|
54
111
|
app.engine('ejs', createInertiaEngine());
|
|
55
112
|
app.set('view engine', 'ejs');
|
|
56
113
|
app.set('views', path.join(__dirname, 'views'));
|
|
57
114
|
|
|
58
|
-
// Parse request bodies
|
|
115
|
+
// Parse request bodies and serve static files
|
|
59
116
|
app.use(express.json());
|
|
60
117
|
app.use(express.urlencoded({ extended: true }));
|
|
61
118
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
62
119
|
|
|
63
|
-
// Inertia
|
|
120
|
+
// Inertia.js middleware for Express
|
|
64
121
|
app.use(
|
|
65
122
|
inertia({
|
|
66
123
|
rootView: 'base.ejs',
|
|
@@ -72,7 +129,7 @@ app.use(
|
|
|
72
129
|
})
|
|
73
130
|
);
|
|
74
131
|
|
|
75
|
-
//
|
|
132
|
+
// Classic server-side routes
|
|
76
133
|
app.get('/', (req, res) => {
|
|
77
134
|
res.inertia('Home', { title: 'Welcome Home' });
|
|
78
135
|
});
|
|
@@ -80,7 +137,7 @@ app.get('/', (req, res) => {
|
|
|
80
137
|
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
|
|
81
138
|
```
|
|
82
139
|
|
|
83
|
-
### 2. Create
|
|
140
|
+
### 2. Create the root view (`views/base.ejs`)
|
|
84
141
|
|
|
85
142
|
```html
|
|
86
143
|
<!DOCTYPE html>
|
|
@@ -100,7 +157,7 @@ app.listen(3000, () => console.log('Server running on http://localhost:3000'));
|
|
|
100
157
|
</html>
|
|
101
158
|
```
|
|
102
159
|
|
|
103
|
-
### 3. Configure the Vue client (`src/main.ts`)
|
|
160
|
+
### 3. Configure the Vue 3 client (`src/main.ts`)
|
|
104
161
|
|
|
105
162
|
```ts
|
|
106
163
|
import { createApp, h } from 'vue';
|
|
@@ -130,11 +187,48 @@ export default defineConfig({
|
|
|
130
187
|
|
|
131
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`.
|
|
132
189
|
|
|
190
|
+
## Using Inertia.js with React
|
|
191
|
+
|
|
192
|
+
Install the React client adapter, configure the React client (`src/main.tsx`):
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import { createRoot } from 'react-dom/client';
|
|
196
|
+
import { createInertiaApp } from '@inertiajs/react';
|
|
197
|
+
|
|
198
|
+
createInertiaApp({
|
|
199
|
+
resolve: (name) => import(`./Pages/${name}.tsx`),
|
|
200
|
+
setup({ el, App, props }) {
|
|
201
|
+
createRoot(el).render(<App {...props} />);
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
For React, use a `.tsx` entrypoint in the root view and include `@viteReactRefresh` before `@vite`:
|
|
207
|
+
|
|
208
|
+
```html
|
|
209
|
+
@viteReactRefresh
|
|
210
|
+
@vite('src/main.tsx')
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Replace the Vue plugin with `@vitejs/plugin-react` in `vite.config.ts` and keep the same `inertiaVitePlugin()`:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { defineConfig } from 'vite';
|
|
217
|
+
import react from '@vitejs/plugin-react';
|
|
218
|
+
import { inertiaVitePlugin } from '@arponascension/express-inertia/vite';
|
|
219
|
+
|
|
220
|
+
export default defineConfig({
|
|
221
|
+
plugins: [react(), inertiaVitePlugin()],
|
|
222
|
+
base: '/build/',
|
|
223
|
+
build: { manifest: true, outDir: 'public/build' },
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
133
227
|
---
|
|
134
228
|
|
|
135
|
-
##
|
|
229
|
+
## Blade-style EJS Directives for Express
|
|
136
230
|
|
|
137
|
-
Write Laravel Blade-style syntax inside EJS templates:
|
|
231
|
+
Write Laravel Blade-style syntax inside EJS templates, powered by a custom Express view engine:
|
|
138
232
|
|
|
139
233
|
| Directive | Component Tag | Output |
|
|
140
234
|
|---|---|---|
|
|
@@ -148,7 +242,7 @@ Write Laravel Blade-style syntax inside EJS templates:
|
|
|
148
242
|
| `@routes` | `<x-routes />` | Ziggy / route definitions |
|
|
149
243
|
| `@json(myVar)` | — | Safely stringifies a JS object |
|
|
150
244
|
|
|
151
|
-
### Custom
|
|
245
|
+
### Custom directives
|
|
152
246
|
|
|
153
247
|
```ts
|
|
154
248
|
import { registerDirective } from '@arponascension/express-inertia';
|
|
@@ -158,11 +252,87 @@ registerDirective('uppercase', (args) => `<%= (${args}).toUpperCase() %>`);
|
|
|
158
252
|
|
|
159
253
|
---
|
|
160
254
|
|
|
161
|
-
##
|
|
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()`
|
|
162
332
|
|
|
163
333
|
### `res.inertia(component, props?, viewData?)`
|
|
164
334
|
|
|
165
|
-
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:
|
|
166
336
|
|
|
167
337
|
```ts
|
|
168
338
|
app.get('/users', (req, res) => {
|
|
@@ -174,7 +344,7 @@ app.get('/users', (req, res) => {
|
|
|
174
344
|
});
|
|
175
345
|
```
|
|
176
346
|
|
|
177
|
-
### Form
|
|
347
|
+
### Form helpers
|
|
178
348
|
|
|
179
349
|
Semantic form submission helpers with automatic method semantics:
|
|
180
350
|
|
|
@@ -196,7 +366,7 @@ app.delete('/account', (req, res) => {
|
|
|
196
366
|
});
|
|
197
367
|
```
|
|
198
368
|
|
|
199
|
-
### Flash
|
|
369
|
+
### Flash messages
|
|
200
370
|
|
|
201
371
|
```ts
|
|
202
372
|
res.inertia
|
|
@@ -204,17 +374,17 @@ res.inertia
|
|
|
204
374
|
.inertia('Dashboard');
|
|
205
375
|
```
|
|
206
376
|
|
|
207
|
-
### Redirect
|
|
377
|
+
### Redirect helpers
|
|
208
378
|
|
|
209
379
|
```ts
|
|
210
|
-
// External redirect / full reload
|
|
380
|
+
// External redirect / full reload (409 + X-Inertia-Location)
|
|
211
381
|
res.inertia.location('https://stripe.com/checkout');
|
|
212
382
|
|
|
213
383
|
// Back to referrer (303 See Other)
|
|
214
384
|
res.inertia.back('/dashboard');
|
|
215
385
|
```
|
|
216
386
|
|
|
217
|
-
### History
|
|
387
|
+
### History encryption (Inertia v2)
|
|
218
388
|
|
|
219
389
|
```ts
|
|
220
390
|
res.inertia.encryptHistory(true);
|
|
@@ -224,7 +394,9 @@ res.inertia('SecretReport');
|
|
|
224
394
|
|
|
225
395
|
---
|
|
226
396
|
|
|
227
|
-
##
|
|
397
|
+
## Prop Helpers (Lazy, Deferred, Merge)
|
|
398
|
+
|
|
399
|
+
Fine-grained control over what props are sent to the client:
|
|
228
400
|
|
|
229
401
|
```ts
|
|
230
402
|
import { lazy, always, defer, merge, optional } from '@arponascension/express-inertia';
|
|
@@ -253,102 +425,41 @@ app.get('/dashboard', (req, res) => {
|
|
|
253
425
|
|
|
254
426
|
---
|
|
255
427
|
|
|
256
|
-
##
|
|
257
|
-
|
|
258
|
-
Auto-detects Vite dev server via hot file. Reads `manifest.json` in production.
|
|
259
|
-
|
|
260
|
-
```ts
|
|
261
|
-
app.use(
|
|
262
|
-
inertia({
|
|
263
|
-
vite: {
|
|
264
|
-
publicDir: 'public',
|
|
265
|
-
buildDir: 'build',
|
|
266
|
-
devServerUrl: 'http://localhost:5173',
|
|
267
|
-
hotFile: 'public/hot',
|
|
268
|
-
base: '/build/',
|
|
269
|
-
},
|
|
270
|
-
})
|
|
271
|
-
);
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### Prefetch Helpers
|
|
275
|
-
|
|
276
|
-
```ts
|
|
277
|
-
import { createPrefetchHelper } from '@arponascension/express-inertia';
|
|
278
|
-
|
|
279
|
-
const prefetch = createPrefetchHelper(viteHelper);
|
|
428
|
+
## Security Hardening
|
|
280
429
|
|
|
281
|
-
|
|
282
|
-
<%= prefetch.prefetch('src/Pages/Dashboard.vue') %>
|
|
283
|
-
// <link rel="prefetch" href="/build/assets/Dashboard.abc1234.js">
|
|
284
|
-
|
|
285
|
-
<%= prefetch.preload('src/main.ts') %>
|
|
286
|
-
// <link rel="preload" href="/build/assets/main.abc1234.js">
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
## 🌐 Server-Side Rendering (SSR)
|
|
292
|
-
|
|
293
|
-
With built-in resilience:
|
|
294
|
-
|
|
295
|
-
```ts
|
|
296
|
-
app.use(
|
|
297
|
-
inertia({
|
|
298
|
-
ssr: {
|
|
299
|
-
enabled: true,
|
|
300
|
-
url: 'http://127.0.0.1:13714/render',
|
|
301
|
-
timeout: 2000,
|
|
302
|
-
fallback: true,
|
|
303
|
-
retry: { maxRetries: 2, baseDelayMs: 200, maxDelayMs: 2000 },
|
|
304
|
-
circuitBreaker: { failureThreshold: 5, cooldownMs: 30000 },
|
|
305
|
-
},
|
|
306
|
-
})
|
|
307
|
-
);
|
|
308
|
-
```
|
|
430
|
+
### Component name validation
|
|
309
431
|
|
|
310
|
-
|
|
432
|
+
Prevents path traversal and injection attacks:
|
|
311
433
|
|
|
312
434
|
```ts
|
|
313
435
|
app.use(
|
|
314
436
|
inertia({
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
return {
|
|
319
|
-
head: [`<title inertia>${page.props.title}</title>`],
|
|
320
|
-
body: '<div id="app">...</div>',
|
|
321
|
-
};
|
|
322
|
-
},
|
|
437
|
+
security: {
|
|
438
|
+
validateComponentNames: true,
|
|
439
|
+
componentNamePattern: /^[\w\/-]+$/,
|
|
323
440
|
},
|
|
324
441
|
})
|
|
325
442
|
);
|
|
326
443
|
```
|
|
327
444
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
## 🔒 Security
|
|
445
|
+
### ViewData sanitization
|
|
331
446
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
Prevents path traversal and injection attacks:
|
|
447
|
+
Automatically strips functions and `undefined` values from template data. Independently configurable from component name validation via `sanitizeViewData`:
|
|
335
448
|
|
|
336
449
|
```ts
|
|
337
450
|
app.use(
|
|
338
451
|
inertia({
|
|
339
452
|
security: {
|
|
340
|
-
validateComponentNames:
|
|
341
|
-
|
|
453
|
+
validateComponentNames: false, // allow arbitrary component names
|
|
454
|
+
sanitizeViewData: true, // still sanitize view data (default)
|
|
342
455
|
},
|
|
343
456
|
})
|
|
344
457
|
);
|
|
345
458
|
```
|
|
346
459
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
Automatically strips functions and undefined values from template data.
|
|
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).
|
|
350
461
|
|
|
351
|
-
### SRI
|
|
462
|
+
### SRI hash generation
|
|
352
463
|
|
|
353
464
|
```ts
|
|
354
465
|
import { generateSriHash } from '@arponascension/express-inertia';
|
|
@@ -359,7 +470,7 @@ const hash = await generateSriHash(assetContent);
|
|
|
359
470
|
|
|
360
471
|
---
|
|
361
472
|
|
|
362
|
-
##
|
|
473
|
+
## Structured Logging
|
|
363
474
|
|
|
364
475
|
Replace `console.warn` with a pluggable logger:
|
|
365
476
|
|
|
@@ -370,7 +481,7 @@ import pino from 'pino';
|
|
|
370
481
|
setGlobalLogger(createLogger({ prefix: 'my-app', logger: pino() }));
|
|
371
482
|
```
|
|
372
483
|
|
|
373
|
-
### Request
|
|
484
|
+
### Request correlation IDs
|
|
374
485
|
|
|
375
486
|
```ts
|
|
376
487
|
import { requestIdMiddleware } from '@arponascension/express-inertia';
|
|
@@ -383,9 +494,9 @@ Correlates logs across the request lifecycle using `X-Request-ID` or auto-genera
|
|
|
383
494
|
|
|
384
495
|
---
|
|
385
496
|
|
|
386
|
-
##
|
|
497
|
+
## Edge Runtime Compatibility
|
|
387
498
|
|
|
388
|
-
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:
|
|
389
500
|
|
|
390
501
|
```ts
|
|
391
502
|
const viteHelper = createViteHelper({
|
|
@@ -402,9 +513,9 @@ app.engine('ejs', createInertiaEngine({
|
|
|
402
513
|
|
|
403
514
|
---
|
|
404
515
|
|
|
405
|
-
##
|
|
516
|
+
## Resilience: Circuit Breaker & Retry
|
|
406
517
|
|
|
407
|
-
### Circuit
|
|
518
|
+
### Circuit breaker (SSR)
|
|
408
519
|
|
|
409
520
|
Prevents cascade failures when the SSR endpoint is down:
|
|
410
521
|
|
|
@@ -418,7 +529,7 @@ const breaker = new CircuitBreaker({
|
|
|
418
529
|
});
|
|
419
530
|
```
|
|
420
531
|
|
|
421
|
-
### Retry with
|
|
532
|
+
### Retry with exponential backoff
|
|
422
533
|
|
|
423
534
|
```ts
|
|
424
535
|
import { calculateBackoff } from '@arponascension/express-inertia';
|
|
@@ -428,29 +539,29 @@ const delay = calculateBackoff(attempt, 200, 2000);
|
|
|
428
539
|
|
|
429
540
|
---
|
|
430
541
|
|
|
431
|
-
##
|
|
542
|
+
## Performance
|
|
432
543
|
|
|
433
|
-
- **Tree-shakeable**:
|
|
434
|
-
- **Template caching**:
|
|
435
|
-
- **Lazy props**:
|
|
436
|
-
- **Prefetching**:
|
|
437
|
-
- **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
|
|
438
549
|
|
|
439
550
|
---
|
|
440
551
|
|
|
441
|
-
##
|
|
552
|
+
## API Reference
|
|
442
553
|
|
|
443
554
|
### Middleware
|
|
444
555
|
|
|
445
556
|
| Export | Description |
|
|
446
557
|
|---|---|
|
|
447
|
-
| `inertia(options?)` | Express middleware factory |
|
|
558
|
+
| `inertia(options?)` | Inertia.js Express middleware factory |
|
|
448
559
|
| `createInertia(options?)` | Same as `inertia`, explicit name |
|
|
449
560
|
| `requestIdMiddleware()` | Assigns `req.id` for log correlation |
|
|
450
561
|
| `createInertiaEngine(options?)` | EJS view engine with Blade support |
|
|
451
562
|
| `inertiaEngine` | Default engine instance |
|
|
452
563
|
|
|
453
|
-
### Prop
|
|
564
|
+
### Prop helpers
|
|
454
565
|
|
|
455
566
|
| Export | Description |
|
|
456
567
|
|---|---|
|
|
@@ -478,7 +589,7 @@ const delay = calculateBackoff(attempt, 200, 2000);
|
|
|
478
589
|
|
|
479
590
|
| Export | Description |
|
|
480
591
|
|---|---|
|
|
481
|
-
| `CircuitBreaker` | CLOSED/OPEN/HALF_OPEN state machine |
|
|
592
|
+
| `CircuitBreaker` | CLOSED / OPEN / HALF_OPEN state machine |
|
|
482
593
|
| `shouldRetry(error, status, codes)` | Retry decision helper |
|
|
483
594
|
| `calculateBackoff(attempt, base, max)` | Exponential backoff with jitter |
|
|
484
595
|
| `resetAllCircuitBreakers()` | Bulk reset for testing |
|
|
@@ -488,32 +599,85 @@ const delay = calculateBackoff(attempt, 200, 2000);
|
|
|
488
599
|
| Export | Description |
|
|
489
600
|
|---|---|
|
|
490
601
|
| `createViteHelper(config?)` | Vite asset resolver |
|
|
491
|
-
| `inertiaVitePlugin(opts?)` | Auto write/remove hot file |
|
|
492
|
-
| `createPrefetchHelper(viteHelper)` | Prefetch/preload generator |
|
|
602
|
+
| `inertiaVitePlugin(opts?)` | Auto write/remove the Vite hot file |
|
|
603
|
+
| `createPrefetchHelper(viteHelper)` | Prefetch/preload tag generator |
|
|
493
604
|
|
|
494
605
|
---
|
|
495
606
|
|
|
496
|
-
##
|
|
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 |
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## Testing
|
|
497
653
|
|
|
498
654
|
```bash
|
|
499
655
|
npm test
|
|
500
656
|
```
|
|
501
657
|
|
|
502
|
-
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.
|
|
659
|
+
|
|
660
|
+
```bash
|
|
661
|
+
npm run typecheck # TypeScript type checking
|
|
662
|
+
npm run lint # ESLint
|
|
663
|
+
npm run test:coverage # Coverage report (v8)
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
---
|
|
503
667
|
|
|
504
|
-
##
|
|
668
|
+
## Resources & Ecosystem
|
|
505
669
|
|
|
506
|
-
|
|
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
|
|
507
676
|
|
|
508
|
-
|
|
509
|
-
|---|---|
|
|
510
|
-
| Node.js | 25.8.x |
|
|
511
|
-
| Express | 4.22.x |
|
|
512
|
-
| `@inertiajs/core` | 2.3.27 |
|
|
513
|
-
| Vue | 3.5.42 |
|
|
677
|
+
Looking for Inertia.js on another framework? Official adapters exist for [Laravel](https://inertiajs.com/docs/getting-started), and community adapters cover Fastify, Hono, and Rails.
|
|
514
678
|
|
|
515
679
|
---
|
|
516
680
|
|
|
517
|
-
##
|
|
681
|
+
## License
|
|
518
682
|
|
|
519
|
-
MIT © [Arpon](https://github.com/
|
|
683
|
+
MIT © [Arpon Ascension](https://github.com/arponascension/)
|
package/dist/engine.d.mts
CHANGED
package/dist/engine.d.ts
CHANGED