@zerotal/inertia 1.13.3 → 1.13.4

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/inertia.ts +62 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/inertia",
3
- "version": "1.13.3",
3
+ "version": "1.13.4",
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.3"
37
+ "@zerotal/core": "1.13.4"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": "^18 || ^19",
package/src/inertia.ts CHANGED
@@ -439,6 +439,49 @@ 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
+ return (
468
+ injectHead(prefix, head) +
469
+ pageScript(pageObject) +
470
+ rootOpen(true) +
471
+ body +
472
+ ROOT_CLOSE +
473
+ suffix
474
+ );
475
+ } catch (error) {
476
+ console.warn(
477
+ `[Inertia] SSR render failed for "${component}", serving the client-rendered ` +
478
+ `document instead: ${(error as Error).message}`,
479
+ );
480
+ return `${prefix}${rootOpen(false)}${ROOT_CLOSE}
481
+ ${pageScript(pageObject)}${suffix}`;
482
+ }
483
+ }
484
+
442
485
  async function _inertia(component: string, props: Record<string, unknown>): Promise<void> {
443
486
  const ctx = RequestContext.get();
444
487
  const isInertiaRequest = ctx.request.headers.get("X-Inertia") === "true";
@@ -454,16 +497,25 @@ async function _inertia(component: string, props: Record<string, unknown>): Prom
454
497
  throw new InertiaTemplateNotLoadedError();
455
498
  }
456
499
 
457
- // Inject pageObject into the HTML template. The root is empty this path does
458
- // not server-render the component, so it is deliberately *not* marked
459
- // `data-server-rendered`: that flag tells the client to hydrate, and hydrating an
460
- // empty div is a mismatch on every page. See `inertiaStream()` for the rendered
461
- // form, and the "What a crawler sees" section of the Inertia docs for what this
462
- // response contains.
463
- const html = _bustAssets(_htmlTemplate).replace(
464
- "<!-- @inertia -->",
465
- `${rootOpen(false)}${ROOT_CLOSE}\n ${pageScript(pageObject)}`,
466
- );
500
+ // `inertia.ssr` renders the component here, on the first load, for every page.
501
+ //
502
+ // That is what the option is named for and what every Inertia adapter does with
503
+ // it and until 1.13.4 it did not: the flag registered `POST /__ssr` and nothing
504
+ // in the request path consulted it, so an app that set `ssr: true` and read the
505
+ // documentation got exactly the empty root it had before. Server rendering was
506
+ // reachable only by rewriting each route to `Inertia.stream()`, one call site at
507
+ // a time, which is not what a global switch means.
508
+ const html = _ssrEnabled()
509
+ ? await _renderedHtml(component, pageObject)
510
+ : _bustAssets(_htmlTemplate).replace(
511
+ "<!-- @inertia -->",
512
+ // The root is empty, so it is deliberately *not* marked
513
+ // `data-server-rendered`: that flag tells the client to hydrate, and
514
+ // hydrating an empty div is a mismatch on every page. See the "What a
515
+ // crawler sees" section of the Inertia docs for what this contains.
516
+ `${rootOpen(false)}${ROOT_CLOSE}
517
+ ${pageScript(pageObject)}`,
518
+ );
467
519
 
468
520
  ctx.response = new Response(html, {
469
521
  headers: {