@zerotal/inertia 1.13.0 → 1.13.2
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/CHANGELOG.md +15 -0
- package/package.json +2 -2
- package/src/inertia.ts +35 -10
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,21 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.13.2] — 2026-08-31
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`Inertia.stream()` honours `X-Inertia`.** It answered every request with
|
|
16
|
+
`text/html`, including the XHR a running Inertia client sends — so pointing a route at
|
|
17
|
+
it to get server rendering broke client-side navigation _to_ that route.
|
|
18
|
+
|
|
19
|
+
The shape of the failure is why it went unnoticed: the first load looks perfect, which
|
|
20
|
+
is what a person checks, and the second click does nothing. It fails for people already
|
|
21
|
+
in the app, silently, and only after the route works.
|
|
22
|
+
|
|
23
|
+
`render` and `stream` now share the branch that writes the page object, rather than one
|
|
24
|
+
of them having it — which is exactly how they came apart.
|
|
25
|
+
|
|
11
26
|
## [1.11.0] — 2026-08-31
|
|
12
27
|
|
|
13
28
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerotal/inertia",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"maturity": "stable",
|
|
6
6
|
"private": false,
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"typecheck": "tsc --noEmit"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@zerotal/core": "1.13.
|
|
37
|
+
"@zerotal/core": "1.13.2"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": "^18 || ^19",
|
package/src/inertia.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { config, RequestContext } from "@zerotal/core";
|
|
2
|
+
import type { HttpContext } from "@zerotal/core";
|
|
2
3
|
import { assetVersion as coreAssetVersion } from "@zerotal/core/assets";
|
|
3
4
|
import { statSync } from "node:fs";
|
|
4
5
|
import { InertiaTemplateNotLoadedError, InvalidComponentError } from "./errors.ts";
|
|
@@ -267,6 +268,15 @@ async function _inertiaStream(component: string, props: Record<string, unknown>)
|
|
|
267
268
|
|
|
268
269
|
const pageObject = await buildPageObject(component, props);
|
|
269
270
|
|
|
271
|
+
// A running Inertia client asks for a page object, not a document — server
|
|
272
|
+
// rendering is about the *first* arrival. Answering an `X-Inertia` XHR with HTML
|
|
273
|
+
// broke client-side navigation to any route that used this: the cold load looked
|
|
274
|
+
// right and the next click did nothing, silently, for someone already in the app.
|
|
275
|
+
if (ctx.request.headers.get("X-Inertia") === "true") {
|
|
276
|
+
_writeInertiaJson(ctx, pageObject);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
270
280
|
const [prefix = "", suffix = ""] = _bustAssets(_htmlTemplate).split("<!-- @inertia -->");
|
|
271
281
|
|
|
272
282
|
const { modPath, framework } = await resolvePageModule(_getPagesDir(), component);
|
|
@@ -405,6 +415,30 @@ export const inertia: PageRenderer = Object.assign(
|
|
|
405
415
|
},
|
|
406
416
|
);
|
|
407
417
|
|
|
418
|
+
/**
|
|
419
|
+
* The XHR half of the Inertia protocol: a page object as JSON.
|
|
420
|
+
*
|
|
421
|
+
* Shared by {@link inertia} and {@link inertiaStream} rather than written twice,
|
|
422
|
+
* because having it in only one of them is precisely the bug this exists to close.
|
|
423
|
+
* `stream` used to answer every request with `text/html` — the first load looked
|
|
424
|
+
* perfect, which is what a person checks, and the *second* click did nothing,
|
|
425
|
+
* because the running client got a document where it expected a page object.
|
|
426
|
+
*
|
|
427
|
+
* `Vary: X-Inertia` is critical: without it a browser caches the JSON as the HTML
|
|
428
|
+
* for the same URL and shows a raw page object on Back or Refresh.
|
|
429
|
+
*
|
|
430
|
+
* @internal
|
|
431
|
+
*/
|
|
432
|
+
function _writeInertiaJson(ctx: HttpContext, pageObject: unknown): void {
|
|
433
|
+
ctx.response = new Response(JSON.stringify(pageObject), {
|
|
434
|
+
headers: {
|
|
435
|
+
"Content-Type": "application/json",
|
|
436
|
+
"X-Inertia": "true",
|
|
437
|
+
Vary: "X-Inertia",
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
408
442
|
async function _inertia(component: string, props: Record<string, unknown>): Promise<void> {
|
|
409
443
|
const ctx = RequestContext.get();
|
|
410
444
|
const isInertiaRequest = ctx.request.headers.get("X-Inertia") === "true";
|
|
@@ -412,16 +446,7 @@ async function _inertia(component: string, props: Record<string, unknown>): Prom
|
|
|
412
446
|
const pageObject = await buildPageObject(component, props);
|
|
413
447
|
|
|
414
448
|
if (isInertiaRequest) {
|
|
415
|
-
|
|
416
|
-
// Vary: X-Inertia is CRITICAL — prevents browser from caching
|
|
417
|
-
// JSON as HTML, which would show raw JSON on browser Back/Refresh
|
|
418
|
-
ctx.response = new Response(JSON.stringify(pageObject), {
|
|
419
|
-
headers: {
|
|
420
|
-
"Content-Type": "application/json",
|
|
421
|
-
"X-Inertia": "true",
|
|
422
|
-
Vary: "X-Inertia",
|
|
423
|
-
},
|
|
424
|
-
});
|
|
449
|
+
_writeInertiaJson(ctx, pageObject);
|
|
425
450
|
return;
|
|
426
451
|
}
|
|
427
452
|
|