@arponascension/express-inertia 1.0.1 → 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 CHANGED
@@ -1,39 +1,89 @@
1
- # @arponascension/express-inertia 🚀
1
+ # Express-Inertia — Inertia.js Middleware, Adapter & SSR for Express.js
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@arponascension/express-inertia.svg)](https://npmjs.com/package/@arponascension/express-inertia)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
+ [![CI](https://github.com/arponascension/express-inertia/actions/workflows/ci.yml/badge.svg)](https://github.com/arponascension/express-inertia/actions)
6
7
  [![Bundle Size](https://img.shields.io/badge/bundle_minified-40KB-green.svg)](https://github.com/arponascension/express-inertia)
7
8
 
8
- **Next-generation Inertia.js adapter and middleware for Express.js** with Blade-style EJS directives, Vite integration, SSR resilience, security hardening, and first-class TypeScript support.
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
- ## ✨ Why express-inertia?
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
- | Feature | Benefit |
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 in `.ejs` templates |
17
- | Zero-config Vite | Auto HMR in dev, hashed `manifest.json` in production |
18
- | SSR Resilience | Circuit breaker, retries with backoff, graceful client-side fallback |
19
- | Security Hardened | Component name validation, viewData sanitization, SRI hash generation |
20
- | Form Helpers | Semantic `postForm`, `putForm`, `patchForm`, `deleteForm` with flash messages |
21
- | Prefetch Ready | Generate `<link rel="prefetch">` / `<link rel="preload">` from Vite manifest |
22
- | Edge Compatible | Works on Cloudflare Workers, Vercel Edge, Netlify Edge via injected manifest/config |
23
- | Structured Logging | Pluggable logger abstraction with request correlation IDs |
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
- ## 📦 Installation
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 use React:
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
- ## 🚀 Quick Start
99
+ ## Quick Start: Express + Inertia + Vue 3
50
100
 
51
- ### 1. Configure Express Server
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 View Engine
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 & serve static files
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 Middleware
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
- // Routes
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 Root View (`views/base.ejs`)
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 the React client (`src/main.tsx`) (alternative)
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
- ### 5. Configure Vite for Vue (`vite.config.ts`)
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
- ## 🏷️ Blade Directives & Components
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 Directives
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
- ## ⚡ Response API
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 Helpers
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 Messages
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 Helpers
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 Encryption (Inertia v2)
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
- ## 🔄 Prop Helpers
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
- ## ⚡ Vite Integration
428
+ ## Security Hardening
299
429
 
300
- Auto-detects Vite dev server via hot file. Reads `manifest.json` in production.
430
+ ### Component name validation
301
431
 
302
- ```ts
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
- ssr: {
358
- enabled: true,
359
- render: async (page) => {
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
- ### Component Name Validation
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: true,
383
- componentNamePattern: /^[\w\/-]+$/,
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
- ### ViewData Sanitization
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
- Automatically strips functions and undefined values from template data.
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
- ## 🧪 Structured Logging
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 Correlation IDs
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
- ## 🌍 Edge Runtime Compatibility
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
- ## 🛡️ Resilience
516
+ ## Resilience: Circuit Breaker & Retry
448
517
 
449
- ### Circuit Breaker (SSR)
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 Exponential Backoff
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
- ## 🎯 Performance
542
+ ## Performance
474
543
 
475
- - **Tree-shakeable**: Import only what you need
476
- - **Template caching**: Compiled EJS templates cached in development
477
- - **Lazy props**: Defer expensive computations until needed
478
- - **Prefetching**: Preload likely navigation targets
479
- - **Minified bundle**: ~40KB gzipped main entry
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
- ## 📚 API Reference
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 Helpers
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
- ## 🧪 Testing
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
- ## ✅ Compatibility
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
- The following versions are exercised by the integration suite. Test your application before upgrading a major version.
666
+ ---
549
667
 
550
- | Dependency | Tested versions |
551
- |---|---|
552
- | Node.js | 25.8.x |
553
- | Express | 4.22.x |
554
- | `@inertiajs/core` | 2.3.27 |
555
- | Vue | 3.5.42 |
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 community adapters cover Fastify, Hono, and Rails.
556
678
 
557
679
  ---
558
680
 
559
- ## 📄 License
681
+ ## License
560
682
 
561
- MIT © [Arpon](https://github.com/Arpon)
683
+ MIT © [Arpon Ascension](https://github.com/arponascension/)