@zerotal/inertia 1.13.3 → 1.13.5
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 +25 -0
- package/package.json +2 -2
- package/src/inertia.ts +60 -10
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,31 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.13.5] — 2026-09-01
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`inertia.ssr: true` now actually server-renders.** The flag registered `POST /__ssr`
|
|
16
|
+
and nothing in the request path consulted it, so an app that set it — and read the
|
|
17
|
+
documentation, which said the server renders the component into the template — got
|
|
18
|
+
exactly the empty root it had before.
|
|
19
|
+
|
|
20
|
+
`Inertia.render()` renders the component into the root, injects the page's `<Head>` into
|
|
21
|
+
the served `<head>`, and marks the root `data-server-rendered`. The scaffolded `app.tsx`
|
|
22
|
+
already hydrated on that attribute, so the client half needed no change: an app turns SSR
|
|
23
|
+
on with one config line and nothing else.
|
|
24
|
+
|
|
25
|
+
Server rendering was previously reachable only by rewriting each route to
|
|
26
|
+
`Inertia.stream()`, one call site at a time, which is not what a global switch means. An
|
|
27
|
+
app that did that can go back to `render()`.
|
|
28
|
+
|
|
29
|
+
A component that fails to render falls back to the client-rendered document with a
|
|
30
|
+
warning rather than failing the route: the page still works in a browser, and taking a
|
|
31
|
+
route down because an _optimisation_ failed would make `ssr: true` a liability.
|
|
32
|
+
|
|
33
|
+
`POST /__ssr` is unchanged and stays documented for what it is — the contract for an
|
|
34
|
+
external renderer, not the in-process switch.
|
|
35
|
+
|
|
11
36
|
## [1.13.2] — 2026-08-31
|
|
12
37
|
|
|
13
38
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerotal/inertia",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.5",
|
|
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.5"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": "^18 || ^19",
|
package/src/inertia.ts
CHANGED
|
@@ -439,6 +439,47 @@ function _writeInertiaJson(ctx: HttpContext, pageObject: unknown): void {
|
|
|
439
439
|
});
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
/** Whether `inertia.ssr` is on. Read per request, so a config change needs no rebuild. */
|
|
443
|
+
function _ssrEnabled(): boolean {
|
|
444
|
+
return config.safe<boolean>("inertia.ssr", false) === true;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* The full document with the component rendered into the root.
|
|
449
|
+
*
|
|
450
|
+
* Buffered rather than streamed, because this is `render()`: the caller asked for a
|
|
451
|
+
* page, not for time-to-first-byte. {@link inertiaStream} is the streaming form and
|
|
452
|
+
* stays a per-route choice, since streaming trades TTFB against a shell that arrives
|
|
453
|
+
* in pieces — a decision that belongs to a route rather than to an application.
|
|
454
|
+
*
|
|
455
|
+
* Falls back to the un-rendered document when a component cannot be rendered. A page
|
|
456
|
+
* that fails to server-render still works in the browser, so taking the route down
|
|
457
|
+
* because an *optimisation* failed would make `ssr: true` a liability rather than an
|
|
458
|
+
* improvement. The failure is logged rather than swallowed.
|
|
459
|
+
*/
|
|
460
|
+
async function _renderedHtml(component: string, pageObject: PageObject): Promise<string> {
|
|
461
|
+
const [prefix = "", suffix = ""] = _bustAssets(_htmlTemplate).split("<!-- @inertia -->");
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const { modPath, framework } = await resolvePageModule(_getPagesDir(), component);
|
|
465
|
+
const { body, head } = await renderInertiaPage(pageObject, modPath, framework);
|
|
466
|
+
|
|
467
|
+
// `body` is already the whole mount root — `renderInertiaPage` returns
|
|
468
|
+
// `pageScript + rootOpen + html + ROOT_CLOSE` for React, and Vue's SSR result
|
|
469
|
+
// carries its own root too. Wrapping it again emits two `<div id="app">` and
|
|
470
|
+
// two `data-page` scripts, and the client hydrates against the wrong one.
|
|
471
|
+
// Composed exactly as `inertiaStream` composes it, so the two cannot drift.
|
|
472
|
+
return injectHead(prefix, head) + body + suffix;
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.warn(
|
|
475
|
+
`[Inertia] SSR render failed for "${component}", serving the client-rendered ` +
|
|
476
|
+
`document instead: ${(error as Error).message}`,
|
|
477
|
+
);
|
|
478
|
+
return `${prefix}${rootOpen(false)}${ROOT_CLOSE}
|
|
479
|
+
${pageScript(pageObject)}${suffix}`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
442
483
|
async function _inertia(component: string, props: Record<string, unknown>): Promise<void> {
|
|
443
484
|
const ctx = RequestContext.get();
|
|
444
485
|
const isInertiaRequest = ctx.request.headers.get("X-Inertia") === "true";
|
|
@@ -454,16 +495,25 @@ async function _inertia(component: string, props: Record<string, unknown>): Prom
|
|
|
454
495
|
throw new InertiaTemplateNotLoadedError();
|
|
455
496
|
}
|
|
456
497
|
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
498
|
+
// `inertia.ssr` renders the component here, on the first load, for every page.
|
|
499
|
+
//
|
|
500
|
+
// That is what the option is named for and what every Inertia adapter does with
|
|
501
|
+
// it — and until 1.13.4 it did not: the flag registered `POST /__ssr` and nothing
|
|
502
|
+
// in the request path consulted it, so an app that set `ssr: true` and read the
|
|
503
|
+
// documentation got exactly the empty root it had before. Server rendering was
|
|
504
|
+
// reachable only by rewriting each route to `Inertia.stream()`, one call site at
|
|
505
|
+
// a time, which is not what a global switch means.
|
|
506
|
+
const html = _ssrEnabled()
|
|
507
|
+
? await _renderedHtml(component, pageObject)
|
|
508
|
+
: _bustAssets(_htmlTemplate).replace(
|
|
509
|
+
"<!-- @inertia -->",
|
|
510
|
+
// The root is empty, so it is deliberately *not* marked
|
|
511
|
+
// `data-server-rendered`: that flag tells the client to hydrate, and
|
|
512
|
+
// hydrating an empty div is a mismatch on every page. See the "What a
|
|
513
|
+
// crawler sees" section of the Inertia docs for what this contains.
|
|
514
|
+
`${rootOpen(false)}${ROOT_CLOSE}
|
|
515
|
+
${pageScript(pageObject)}`,
|
|
516
|
+
);
|
|
467
517
|
|
|
468
518
|
ctx.response = new Response(html, {
|
|
469
519
|
headers: {
|