@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 CHANGED
@@ -1,34 +1,91 @@
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
- npm install @arponascension/express-inertia ejs express @inertiajs/vue3 vue
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
- ## 🚀 Quick Start
99
+ ## Quick Start: Express + Inertia + Vue 3
43
100
 
44
- ### 1. Configure Express Server
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 View Engine
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 & serve static files
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 Middleware
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
- // Routes
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 Root View (`views/base.ejs`)
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
- ## 🏷️ Blade Directives & Components
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 Directives
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
- ## ⚡ 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()`
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 Helpers
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 Messages
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 Helpers
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 Encryption (Inertia v2)
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
- ## 🔄 Prop Helpers
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
- ## ⚡ Vite Integration
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
- // In your base template:
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
- Or custom render function:
432
+ Prevents path traversal and injection attacks:
311
433
 
312
434
  ```ts
313
435
  app.use(
314
436
  inertia({
315
- ssr: {
316
- enabled: true,
317
- render: async (page) => {
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
- ### Component Name Validation
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: true,
341
- componentNamePattern: /^[\w\/-]+$/,
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
- ### ViewData Sanitization
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 Hash Generation
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
- ## 🧪 Structured Logging
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 Correlation IDs
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
- ## 🌍 Edge Runtime Compatibility
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
- ## 🛡️ Resilience
516
+ ## Resilience: Circuit Breaker & Retry
406
517
 
407
- ### Circuit Breaker (SSR)
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 Exponential Backoff
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
- ## 🎯 Performance
542
+ ## Performance
432
543
 
433
- - **Tree-shakeable**: Import only what you need
434
- - **Template caching**: Compiled EJS templates cached in development
435
- - **Lazy props**: Defer expensive computations until needed
436
- - **Prefetching**: Preload likely navigation targets
437
- - **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
438
549
 
439
550
  ---
440
551
 
441
- ## 📚 API Reference
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 Helpers
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
- ## 🧪 Testing
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
- ## ✅ Compatibility
668
+ ## Resources & Ecosystem
505
669
 
506
- The following versions are exercised by the integration suite. Test your application before upgrading a major version.
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
- | Dependency | Tested versions |
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
- ## 📄 License
681
+ ## License
518
682
 
519
- MIT © [Arpon](https://github.com/Arpon)
683
+ MIT © [Arpon Ascension](https://github.com/arponascension/)
package/dist/engine.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { c as Page, d as SSRResult, B as BladeEngineOptions } from './types-Co2XESgs.mjs';
1
+ import { c as Page, e as SSRResult, B as BladeEngineOptions } from './types-DgBPobaU.mjs';
2
2
  import 'express';
3
3
 
4
4
  interface TemplateLocals extends Record<string, any> {
package/dist/engine.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { c as Page, d as SSRResult, B as BladeEngineOptions } from './types-Co2XESgs.js';
1
+ import { c as Page, e as SSRResult, B as BladeEngineOptions } from './types-DgBPobaU.js';
2
2
  import 'express';
3
3
 
4
4
  interface TemplateLocals extends Record<string, any> {