@reckona/mreact-router 0.0.202 → 0.0.204

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 (152) hide show
  1. package/README.md +58 -11
  2. package/dist/actions.d.ts +8 -3
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +125 -45
  5. package/dist/actions.js.map +1 -1
  6. package/dist/adapters/aws-lambda.d.ts +15 -4
  7. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  8. package/dist/adapters/aws-lambda.js +301 -63
  9. package/dist/adapters/aws-lambda.js.map +1 -1
  10. package/dist/adapters/cloudflare.d.ts +5 -2
  11. package/dist/adapters/cloudflare.d.ts.map +1 -1
  12. package/dist/adapters/cloudflare.js +35 -11
  13. package/dist/adapters/cloudflare.js.map +1 -1
  14. package/dist/adapters/edge.d.ts +1 -1
  15. package/dist/adapters/edge.d.ts.map +1 -1
  16. package/dist/adapters/edge.js.map +1 -1
  17. package/dist/adapters/node.d.ts +5 -3
  18. package/dist/adapters/node.d.ts.map +1 -1
  19. package/dist/adapters/node.js +2 -1
  20. package/dist/adapters/node.js.map +1 -1
  21. package/dist/adapters/static.d.ts.map +1 -1
  22. package/dist/adapters/static.js +24 -2
  23. package/dist/adapters/static.js.map +1 -1
  24. package/dist/build.d.ts +4 -1
  25. package/dist/build.d.ts.map +1 -1
  26. package/dist/build.js +185 -13
  27. package/dist/build.js.map +1 -1
  28. package/dist/built-runtime.d.ts +2 -0
  29. package/dist/built-runtime.d.ts.map +1 -1
  30. package/dist/built-runtime.js +16 -0
  31. package/dist/built-runtime.js.map +1 -1
  32. package/dist/cache.d.ts +4 -2
  33. package/dist/cache.d.ts.map +1 -1
  34. package/dist/cache.js +216 -17
  35. package/dist/cache.js.map +1 -1
  36. package/dist/cli-options.d.ts +3 -0
  37. package/dist/cli-options.d.ts.map +1 -1
  38. package/dist/cli-options.js +10 -2
  39. package/dist/cli-options.js.map +1 -1
  40. package/dist/cli.js +14 -3
  41. package/dist/cli.js.map +1 -1
  42. package/dist/client.d.ts +5 -0
  43. package/dist/client.d.ts.map +1 -1
  44. package/dist/client.js +43 -3
  45. package/dist/client.js.map +1 -1
  46. package/dist/config.d.ts +3 -0
  47. package/dist/config.d.ts.map +1 -1
  48. package/dist/config.js +21 -2
  49. package/dist/config.js.map +1 -1
  50. package/dist/csp.d.ts +1 -0
  51. package/dist/csp.d.ts.map +1 -1
  52. package/dist/csp.js +9 -0
  53. package/dist/csp.js.map +1 -1
  54. package/dist/dehydrate-policy.d.ts +3 -0
  55. package/dist/dehydrate-policy.d.ts.map +1 -0
  56. package/dist/dehydrate-policy.js +16 -0
  57. package/dist/dehydrate-policy.js.map +1 -0
  58. package/dist/dev-server.d.ts +7 -2
  59. package/dist/dev-server.d.ts.map +1 -1
  60. package/dist/dev-server.js +120 -11
  61. package/dist/dev-server.js.map +1 -1
  62. package/dist/http.d.ts +1 -1
  63. package/dist/http.d.ts.map +1 -1
  64. package/dist/http.js +147 -7
  65. package/dist/http.js.map +1 -1
  66. package/dist/index.d.ts +4 -3
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +1 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/logger.d.ts +22 -1
  71. package/dist/logger.d.ts.map +1 -1
  72. package/dist/logger.js +22 -8
  73. package/dist/logger.js.map +1 -1
  74. package/dist/metadata.d.ts +8 -0
  75. package/dist/metadata.d.ts.map +1 -1
  76. package/dist/metadata.js +60 -22
  77. package/dist/metadata.js.map +1 -1
  78. package/dist/navigation-marker.d.ts +2 -0
  79. package/dist/navigation-marker.d.ts.map +1 -0
  80. package/dist/navigation-marker.js +357 -0
  81. package/dist/navigation-marker.js.map +1 -0
  82. package/dist/navigation-runtime.d.ts +1 -1
  83. package/dist/navigation-runtime.d.ts.map +1 -1
  84. package/dist/navigation-runtime.js +1 -1
  85. package/dist/navigation-runtime.js.map +1 -1
  86. package/dist/navigation.d.ts +1 -0
  87. package/dist/navigation.d.ts.map +1 -1
  88. package/dist/navigation.js +1 -1
  89. package/dist/navigation.js.map +1 -1
  90. package/dist/node-server.d.ts +5 -2
  91. package/dist/node-server.d.ts.map +1 -1
  92. package/dist/node-server.js +75 -6
  93. package/dist/node-server.js.map +1 -1
  94. package/dist/prerender-entry.d.ts +17 -1
  95. package/dist/prerender-entry.d.ts.map +1 -1
  96. package/dist/prerender-entry.js +98 -3
  97. package/dist/prerender-entry.js.map +1 -1
  98. package/dist/render.d.ts +16 -2
  99. package/dist/render.d.ts.map +1 -1
  100. package/dist/render.js +142 -135
  101. package/dist/render.js.map +1 -1
  102. package/dist/serve.d.ts +11 -2
  103. package/dist/serve.d.ts.map +1 -1
  104. package/dist/serve.js +67 -41
  105. package/dist/serve.js.map +1 -1
  106. package/dist/session.d.ts.map +1 -1
  107. package/dist/session.js +52 -19
  108. package/dist/session.js.map +1 -1
  109. package/dist/upgrade.d.ts +59 -2
  110. package/dist/upgrade.d.ts.map +1 -1
  111. package/dist/upgrade.js +282 -1
  112. package/dist/upgrade.js.map +1 -1
  113. package/dist/vite-config.d.ts +2 -0
  114. package/dist/vite-config.d.ts.map +1 -1
  115. package/dist/vite-config.js +2 -0
  116. package/dist/vite-config.js.map +1 -1
  117. package/dist/vite.d.ts +4 -1
  118. package/dist/vite.d.ts.map +1 -1
  119. package/dist/vite.js +117 -6
  120. package/dist/vite.js.map +1 -1
  121. package/package.json +11 -11
  122. package/src/actions.ts +146 -60
  123. package/src/adapters/aws-lambda.ts +426 -115
  124. package/src/adapters/cloudflare.ts +58 -12
  125. package/src/adapters/edge.ts +2 -0
  126. package/src/adapters/node.ts +8 -10
  127. package/src/adapters/static.ts +31 -2
  128. package/src/build.ts +217 -13
  129. package/src/built-runtime.ts +28 -0
  130. package/src/cache.ts +272 -19
  131. package/src/cli-options.ts +15 -2
  132. package/src/cli.ts +16 -2
  133. package/src/client.ts +47 -3
  134. package/src/config.ts +43 -6
  135. package/src/csp.ts +11 -0
  136. package/src/dehydrate-policy.ts +23 -0
  137. package/src/dev-server.ts +188 -15
  138. package/src/http.ts +172 -6
  139. package/src/index.ts +14 -1
  140. package/src/logger.ts +47 -10
  141. package/src/metadata.ts +125 -60
  142. package/src/navigation-marker.ts +423 -0
  143. package/src/navigation-runtime.ts +1 -0
  144. package/src/navigation.ts +1 -1
  145. package/src/node-server.ts +128 -22
  146. package/src/prerender-entry.ts +127 -5
  147. package/src/render.ts +859 -749
  148. package/src/serve.ts +112 -82
  149. package/src/session.ts +56 -25
  150. package/src/upgrade.ts +402 -3
  151. package/src/vite-config.ts +6 -0
  152. package/src/vite.ts +157 -5
package/dist/render.js CHANGED
@@ -5,9 +5,9 @@ import { access, readFile, stat } from "node:fs/promises";
5
5
  import { dirname, join, relative, resolve, sep } from "node:path";
6
6
  import { formatDiagnostic, transform } from "@reckona/mreact-compiler";
7
7
  import { createQueryClient, dehydrate, __MREACT_QUERY_STATE_SCRIPT_ID, installQueryAsyncStorage, runWithQueryClient, } from "@reckona/mreact-query";
8
- import { createStringSink, renderAsyncBoundary, renderOutOfOrderReorderScript, renderReactNodeToString, renderToReadableStream, } from "@reckona/mreact-server";
8
+ import { createStringSink, renderOutOfOrderBoundary, renderOutOfOrderReorderScript, renderReactNodeToString, renderToReadableStream, } from "@reckona/mreact-server";
9
9
  import { createClientRouteInferenceCache, formatClientRouteInferenceDiagnostic, inferClientRouteModule, } from "./client-route-inference.js";
10
- import { hydrationMarkerParts, routeIdForPath, withHydrationMarkers, withRouteMarkers, } from "./navigation-runtime.js";
10
+ import { hydrationMarkerParts, routeMarkerParts, routeIdForPath, withHydrationMarkers, } from "./navigation-runtime.js";
11
11
  import { assetPath } from "./assets.js";
12
12
  import { escapeHtmlAttribute } from "@reckona/mreact-shared/html-escape";
13
13
  import { matchRoute, scanAppRoutes } from "./routes.js";
@@ -16,8 +16,9 @@ import { dispatchServerActionRequest, prepareRouteServerActionPlaceholders, prep
16
16
  import { serverActionCookie } from "./csrf.js";
17
17
  import { activeRouteCacheContext, withRouteCacheContext, cachedRouteResponse, cacheRouteResponse, routeCacheKey, routeCachePolicyFromSource, } from "./cache.js";
18
18
  import { trackRequestHeaderReads, withTrackedRequest, } from "./request-header-tracking.js";
19
- import { configuredHstsHeader, hasInvalidConfiguredHsts, } from "./security-headers.js";
19
+ import { configuredHstsHeader, hasInvalidConfiguredHsts } from "./security-headers.js";
20
20
  import { resolveRouterCacheLimit } from "./cache-config.js";
21
+ import { protectNonceBearingResponse } from "./prerender-entry.js";
21
22
  import { importAppRouterBuiltFileModule, importAppRouterFileModule, importAppRouterSourceModule, fileImportMetaUrlPlugin, rewriteCompatVendorPlaceholderImportsForRunner, } from "./module-runner.js";
22
23
  import { bytesResponse, htmlResponse } from "./http.js";
23
24
  import { isNotFoundError, isRedirectError, rewriteLocation } from "./navigation.js";
@@ -30,7 +31,7 @@ import { bundleRouterModule } from "./bundle-pipeline.js";
30
31
  import { viteDefineCacheKey, vitePluginsCacheKey } from "./vite-plugin-cache-key.js";
31
32
  import { invokeRouterInstrumentation, traceContextFromRequest, } from "./trace.js";
32
33
  import { mergeRouteMiddlewareControls, middlewareMatches, parseRouteMiddlewareControl, parseStaticMiddlewareConfig, shouldSkipMiddleware, validateRouteMiddlewareControl, } from "./middleware.js";
33
- import { applyFileConventionMetadata, injectHeadMetadata, mergeRouteMetadata, responseHeadersForMetadata, serializeRobots, serializeSitemap, validateRouteMetadata, } from "./metadata.js";
34
+ import { applyFileConventionMetadataTemplate, fileConventionMetadataTemplate, injectHeadMetadata, mergeRouteMetadata, responseHeadersForMetadata, serializeRobots, serializeSitemap, validateRouteMetadata, } from "./metadata.js";
34
35
  import { createSlotRenderContext, markShellBoundary, shellBoundaryId, splitLayoutSlot, warnUnconsumedRouteSlots, } from "./layout-composer.js";
35
36
  import { importPolicyCacheKey, memoizedHashText, prebuiltRequestModuleArtifact, prebuiltRouteLoaderModuleArtifact, prebuiltServerComponentModuleCode, prebuiltServerModuleOutputMatches, prebuiltServerModuleOutputOptionsMatch, } from "./route-module-loader.js";
36
37
  import { loadRouteDataFromModule, } from "./route-loader-runtime.js";
@@ -272,7 +273,7 @@ export async function renderAppRequest(options) {
272
273
  };
273
274
  invokeRouterInstrumentation(options.instrumentation?.onRequestStart, requestEvent);
274
275
  const response = await renderAppRequestInternal({ ...options, requestUrl: url });
275
- const finalResponse = await applyAppRouterResponseHook(response, options);
276
+ const finalResponse = protectNonceBearingResponse(await applyAppRouterResponseHook(response, options));
276
277
  invokeRouterInstrumentation(options.instrumentation?.onRequestEnd, {
277
278
  ...requestEvent,
278
279
  status: finalResponse.status,
@@ -291,31 +292,14 @@ function finishRenderTimingPhase(timing, startedAt, phaseName) {
291
292
  }
292
293
  timing.phases[phaseName] = logDurationMs(startedAt);
293
294
  }
294
- async function readSettledPromiseAtNextTask(promise) {
295
- return await Promise.race([
296
- promise.then((value) => ({ settled: true, value })),
297
- new Promise((resolve) => {
298
- setTimeout(() => resolve({ settled: false }), 0);
299
- }),
300
- ]);
301
- }
302
- function createDeferredSignal() {
303
- let resolve;
304
- const promise = new Promise((innerResolve) => {
305
- resolve = innerResolve;
295
+ function observePromiseSettlement(promise) {
296
+ let settlement = { status: "pending" };
297
+ void promise.then((value) => {
298
+ settlement = { status: "fulfilled", value };
299
+ }, (error) => {
300
+ settlement = { error, status: "rejected" };
306
301
  });
307
- return {
308
- promise,
309
- resolve() {
310
- resolve?.();
311
- },
312
- };
313
- }
314
- async function waitForLoaderReadyOrSettled(dataPromise, loaderReady) {
315
- if (loaderReady === undefined) {
316
- return;
317
- }
318
- await Promise.race([loaderReady, dataPromise.then(() => undefined, () => undefined)]);
302
+ return () => settlement;
319
303
  }
320
304
  function addRenderTimingPhaseDuration(timing, startedAt, phaseName) {
321
305
  if (timing === undefined || startedAt === undefined) {
@@ -323,11 +307,6 @@ function addRenderTimingPhaseDuration(timing, startedAt, phaseName) {
323
307
  }
324
308
  timing.phases[phaseName] = (timing.phases[phaseName] ?? 0) + logDurationMs(startedAt);
325
309
  }
326
- function routeLoaderMayReturnControlResponse(code) {
327
- return (/\b(?:redirect|redirectExternal|rewrite|notFound|throwNotFound)\s*\(/.test(code) ||
328
- /\bnew\s+Response\s*\(/.test(code) ||
329
- /\bResponse\.(?:redirect|json)\s*\(/.test(code));
330
- }
331
310
  async function waitForRenderPreload(options, timing) {
332
311
  if (options.preload?.wait !== "before-render") {
333
312
  return;
@@ -493,6 +472,7 @@ async function renderAppRequestInternal(options) {
493
472
  let trackedRequest;
494
473
  let sourceUsesRequestInput = true;
495
474
  let invalidConfiguredHsts = false;
475
+ let renderedStrictTransportSecurity;
496
476
  return (await withRouteCacheContext(options.routeCache, async () => {
497
477
  try {
498
478
  if (matched.route.kind === "asset") {
@@ -561,7 +541,8 @@ async function renderAppRequestInternal(options) {
561
541
  finishRenderTimingPhase(timing, phaseStartedAt, "sourceAnalysisMs");
562
542
  const cachePolicy = originalAnalysis.cachePolicy;
563
543
  const navigationScript = options.navigationScripts?.get(matched.route.path);
564
- const cacheKey = routeCacheKey(options.appDir, matched.route.path, url);
544
+ const navigationRequest = isNavigationRequest(options.request);
545
+ const cacheKey = routeCacheKey(options.appDir, matched.route.path, url, navigationRequest ? "navigation" : "document");
565
546
  const mayUseRouteCache = !sourceUsesRequestInput &&
566
547
  (cachePolicy === undefined
567
548
  ? originalAnalysis.usesRuntimeCacheControl
@@ -625,7 +606,6 @@ async function renderAppRequestInternal(options) {
625
606
  const clientInference = routeAnalysis.clientInference;
626
607
  const clientRoute = clientInference.client;
627
608
  phaseStartedAt = renderTimingPhaseStartedAt(timing);
628
- const loaderReady = routeAnalysis.hasLoader ? createDeferredSignal() : undefined;
629
609
  const dataPromise = routeAnalysis.hasLoader
630
610
  ? loadRouteDataWithInstrumentation({
631
611
  appDir: options.appDir,
@@ -647,15 +627,18 @@ async function renderAppRequestInternal(options) {
647
627
  define: options.define,
648
628
  vitePlugins: options.vitePlugins,
649
629
  timing,
650
- onLoaderReady: loaderReady?.resolve,
651
630
  })
652
631
  : undefined;
632
+ // Route analysis and loading-boundary discovery can continue before the loader is awaited.
633
+ // Observe an early rejection immediately so strict unhandled-rejection runtimes do not
634
+ // terminate before the request-level control/error handler receives the same promise.
635
+ void dataPromise?.catch(() => { });
653
636
  finishRenderTimingPhase(timing, phaseStartedAt, "loaderStartMs");
654
637
  recoveryRoute = {
655
638
  clientRoute,
656
639
  props: {
657
640
  params: matched.params,
658
- request: { url: options.request.url },
641
+ request: { url: url.pathname },
659
642
  },
660
643
  routePath: matched.route.path,
661
644
  script: clientScript,
@@ -728,16 +711,11 @@ async function renderAppRequestInternal(options) {
728
711
  script: clientScript,
729
712
  props: {
730
713
  params: matched.params,
731
- request: { url: options.request.url },
714
+ request: { url: url.pathname },
732
715
  data,
733
716
  },
734
717
  })
735
- : isNavigationRequest(options.request)
736
- ? withRouteMarkers({
737
- html: pageHtml,
738
- routePath: matched.route.path,
739
- })
740
- : pageHtml;
718
+ : withServerOnlyRouteMarkers(pageHtml, matched.route.path, options.request, options.prerenderVariantCapture === true);
741
719
  let html = await runWithQueryClient(queryClient, () => applyLayouts({
742
720
  appDir: options.appDir,
743
721
  pageFile: matched.route.file,
@@ -782,7 +760,7 @@ async function renderAppRequestInternal(options) {
782
760
  serverModuleCacheVersion: options.serverModuleCacheVersion,
783
761
  });
784
762
  html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
785
- html = injectQueryState(html, dehydrate(queryClient));
763
+ html = injectQueryState(html, dehydrate(queryClient, options.dehydrateOptions));
786
764
  const response = withOptionalActionCookie(htmlResponse(applyActionHtmlReplacements(`<!DOCTYPE html>${clientNavigationHeadTags({
787
765
  assetBaseUrl: options.assetBaseUrl,
788
766
  currentStyleSheets: clientStyleSheets,
@@ -794,43 +772,24 @@ async function renderAppRequestInternal(options) {
794
772
  "x-mreact-stream": "1",
795
773
  }),
796
774
  }), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
775
+ appendNavigationVary(response.headers);
797
776
  emitRenderTiming(options, timing, response.status);
798
777
  return response;
799
778
  }
800
779
  let streamData;
801
- let streamDataPromise = dataPromise ?? Promise.resolve(undefined);
802
780
  phaseStartedAt = renderTimingPhaseStartedAt(timing);
803
- if (loadingFile === undefined || routeLoaderMayReturnControlResponse(originalCode)) {
804
- try {
805
- streamData = dataPromise === undefined ? undefined : await dataPromise;
806
- }
807
- finally {
808
- finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
809
- }
810
- if (streamData instanceof Response) {
811
- emitRenderTiming(options, timing, streamData.status);
812
- return streamData;
813
- }
814
- streamDataPromise = Promise.resolve(streamData);
781
+ try {
782
+ streamData = dataPromise === undefined ? undefined : await dataPromise;
815
783
  }
816
- else {
817
- try {
818
- const settledData = await readSettledPromiseAtNextTask(streamDataPromise);
819
- if (settledData.settled) {
820
- if (settledData.value instanceof Response) {
821
- emitRenderTiming(options, timing, settledData.value.status);
822
- return settledData.value;
823
- }
824
- streamDataPromise = Promise.resolve(settledData.value);
825
- }
826
- else {
827
- await waitForLoaderReadyOrSettled(streamDataPromise, loaderReady?.promise);
828
- }
829
- }
830
- finally {
831
- finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
832
- }
784
+ finally {
785
+ finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
833
786
  }
787
+ if (streamData instanceof Response) {
788
+ emitRenderTiming(options, timing, streamData.status);
789
+ return streamData;
790
+ }
791
+ const streamDataPromise = Promise.resolve(streamData);
792
+ const streamDataSettlement = loadingFile === undefined ? undefined : observePromiseSettlement(streamDataPromise);
834
793
  await waitForRenderPreload(options, timing);
835
794
  await loadServerRenderArtifacts(options, matched.route.file, timing);
836
795
  phaseStartedAt = renderTimingPhaseStartedAt(timing);
@@ -851,19 +810,31 @@ async function renderAppRequestInternal(options) {
851
810
  headers: { "content-type": "text/plain; charset=utf-8" },
852
811
  });
853
812
  }
854
- if (loadingFile !== undefined) {
813
+ if (loadingFile !== undefined && streamDataSettlement !== undefined) {
855
814
  phaseStartedAt = renderTimingPhaseStartedAt(timing);
856
- const stream = await runServerStreamModuleWithLoading(output.code, {
815
+ const errorFile = await nearestExistingBoundaryFileForPage({
816
+ appDir: options.appDir,
817
+ filename: "error.mreact.tsx",
818
+ pageFile: matched.route.file,
819
+ serverSourceFiles: options.serverSourceFiles,
820
+ });
821
+ const streamResult = await runServerStreamModuleWithLoading(output.code, {
857
822
  appDir: options.appDir,
858
823
  assetBaseUrl: options.assetBaseUrl,
859
824
  clientRoute,
860
825
  data: streamDataPromise,
826
+ dataSettlement: streamDataSettlement,
861
827
  define: options.define,
828
+ errorFile,
862
829
  loadingFile,
830
+ markerRequest: options.request,
831
+ onRenderError: options.onRenderError,
832
+ prerenderVariantCapture: options.prerenderVariantCapture,
863
833
  pageFile: matched.route.file,
864
834
  params: matched.params,
865
835
  queryClient,
866
836
  request: appRequest,
837
+ requestUrl: url.pathname,
867
838
  trackedRequest,
868
839
  routePath: matched.route.path,
869
840
  routeScripts: options.clientScripts,
@@ -877,9 +848,14 @@ async function renderAppRequestInternal(options) {
877
848
  clientReferenceManifest: output.metadata.clientReferenceManifest,
878
849
  });
879
850
  finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
880
- const response = withOptionalActionCookie(new Response(stream, {
851
+ if (streamResult instanceof Response) {
852
+ emitRenderTiming(options, timing, streamResult.status);
853
+ return streamResult;
854
+ }
855
+ const response = withOptionalActionCookie(new Response(streamResult, {
881
856
  headers: streamShellResponseHeaders,
882
857
  }), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
858
+ appendNavigationVary(response.headers);
883
859
  emitRenderTiming(options, timing, response.status);
884
860
  return response;
885
861
  }
@@ -894,7 +870,10 @@ async function renderAppRequestInternal(options) {
894
870
  appDir: options.appDir,
895
871
  assetBaseUrl: options.assetBaseUrl,
896
872
  pageFile: matched.route.file,
873
+ markerRequest: options.request,
874
+ prerenderVariantCapture: options.prerenderVariantCapture,
897
875
  props,
876
+ requestUrl: url.pathname,
898
877
  routePath: matched.route.path,
899
878
  routeScripts: options.clientScripts,
900
879
  serverModules: options.serverModules,
@@ -912,6 +891,7 @@ async function renderAppRequestInternal(options) {
912
891
  const response = withOptionalActionCookie(new Response(stream, {
913
892
  headers: streamShellResponseHeaders,
914
893
  }), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
894
+ appendNavigationVary(response.headers);
915
895
  emitRenderTiming(options, timing, response.status);
916
896
  return response;
917
897
  }
@@ -974,16 +954,11 @@ async function renderAppRequestInternal(options) {
974
954
  script: clientScript,
975
955
  props: {
976
956
  params: matched.params,
977
- request: { url: options.request.url },
957
+ request: { url: url.pathname },
978
958
  data,
979
959
  },
980
960
  })
981
- : isNavigationRequest(options.request)
982
- ? withRouteMarkers({
983
- html: pageHtml,
984
- routePath: matched.route.path,
985
- })
986
- : pageHtml;
961
+ : withServerOnlyRouteMarkers(pageHtml, matched.route.path, options.request, options.prerenderVariantCapture === true);
987
962
  phaseStartedAt = renderTimingPhaseStartedAt(timing);
988
963
  let html = await runWithQueryClient(queryClient, () => applyLayouts({
989
964
  appDir: options.appDir,
@@ -1032,7 +1007,7 @@ async function renderAppRequestInternal(options) {
1032
1007
  serverModuleCacheVersion: options.serverModuleCacheVersion,
1033
1008
  });
1034
1009
  html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
1035
- html = injectQueryState(html, dehydrate(queryClient));
1010
+ html = injectQueryState(html, dehydrate(queryClient, options.dehydrateOptions));
1036
1011
  const response = withOptionalActionCookie(htmlResponse(applyActionHtmlReplacements(`<!DOCTYPE html>${clientNavigationHeadTags({
1037
1012
  assetBaseUrl: options.assetBaseUrl,
1038
1013
  currentStyleSheets: clientStyleSheets,
@@ -1042,8 +1017,10 @@ async function renderAppRequestInternal(options) {
1042
1017
  })}${html}`, preparedActions.htmlReplacements), {
1043
1018
  headers: responseHeadersForMetadata(metadata, options.request),
1044
1019
  }), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
1020
+ appendNavigationVary(response.headers);
1045
1021
  const effectiveCachePolicy = cachePolicy ?? activeRouteCacheContext()?.cachePolicy;
1046
1022
  const configuredHsts = configuredHstsHeader(metadata?.security);
1023
+ renderedStrictTransportSecurity = configuredHsts;
1047
1024
  invalidConfiguredHsts = hasInvalidConfiguredHsts(metadata?.security);
1048
1025
  const finalResponse = preparedActions.hasFormActions
1049
1026
  ? withRouteCacheHeader(response, effectiveCachePolicy)
@@ -1136,7 +1113,10 @@ async function renderAppRequestInternal(options) {
1136
1113
  // after draining the body.
1137
1114
  if (options.renderSignals !== undefined) {
1138
1115
  const tracked = trackedRequest;
1139
- options.renderSignals.headerDependent = () => sourceUsesRequestInput || invalidConfiguredHsts || (tracked?.requestDependent() ?? true);
1116
+ options.renderSignals.headerDependent = () => sourceUsesRequestInput ||
1117
+ invalidConfiguredHsts ||
1118
+ (tracked?.requestDependent() ?? true);
1119
+ options.renderSignals.strictTransportSecurity = () => renderedStrictTransportSecurity;
1140
1120
  }
1141
1121
  }
1142
1122
  })).value;
@@ -1274,6 +1254,30 @@ function routePrefetchManifestScript(routeScripts, assetBaseUrl) {
1274
1254
  function isNavigationRequest(request) {
1275
1255
  return request.headers.get("x-mreact-navigation") === "1";
1276
1256
  }
1257
+ function withServerOnlyRouteMarkers(html, routePath, request, prerenderVariantCapture) {
1258
+ const marker = serverOnlyRouteMarkerParts(routePath, request, prerenderVariantCapture);
1259
+ return marker === undefined ? html : `${marker.prefix}${html}${marker.suffix}`;
1260
+ }
1261
+ function serverOnlyRouteMarkerParts(routePath, request, prerenderVariantCapture) {
1262
+ if (prerenderVariantCapture) {
1263
+ return prerenderVariantMarkerParts(routePath);
1264
+ }
1265
+ return isNavigationRequest(request) ? routeMarkerParts(routePath) : undefined;
1266
+ }
1267
+ export function prerenderVariantMarkerParts(routePath) {
1268
+ const routeId = routeIdForPath(routePath);
1269
+ return {
1270
+ prefix: `<!--mreact-route-variant-start:${routeId}-->`,
1271
+ suffix: `<!--mreact-route-variant-end:${routeId}-->`,
1272
+ };
1273
+ }
1274
+ function appendNavigationVary(headers) {
1275
+ const vary = headers.get("vary");
1276
+ if (vary?.split(",").some((value) => value.trim().toLowerCase() === "x-mreact-navigation") === true) {
1277
+ return;
1278
+ }
1279
+ headers.append("vary", "x-mreact-navigation");
1280
+ }
1277
1281
  function isNavigationRouteCacheReloadRequest(request) {
1278
1282
  return (isNavigationRequest(request) && request.headers.get("x-mreact-navigation-cache") === "reload");
1279
1283
  }
@@ -1387,15 +1391,7 @@ async function renderSpecialRoute(options) {
1387
1391
  }
1388
1392
  }
1389
1393
  const props = {
1390
- data: undefined,
1391
- debug: errorDebugContext(options.error, options.routePath),
1392
- error: normalizeErrorForProps(options.error),
1393
- params: {},
1394
- queryClient: createQueryClient(),
1395
- request: options.request,
1396
- requestId: requestIdForErrorContext(options.request),
1397
- routeId: routeIdForPath(options.routePath ?? new URL(options.request.url).pathname),
1398
- traceId: traceContextFromRequest(options.request)?.traceId,
1394
+ ...errorBoundaryProps(options.error, options.request, options.routePath),
1399
1395
  };
1400
1396
  const pageHtml = await renderServerFileToHtml(options.routeFile, props, options.serverModules, options.serverModuleCacheVersion, options.serverSourceFiles, options.define, options.vitePlugins);
1401
1397
  const pageHtmlForLayout = options.navigation?.clientRoute === true
@@ -1429,6 +1425,19 @@ async function renderSpecialRoute(options) {
1429
1425
  status: options.status,
1430
1426
  });
1431
1427
  }
1428
+ function errorBoundaryProps(error, request, routePath) {
1429
+ return {
1430
+ data: undefined,
1431
+ debug: errorDebugContext(error, routePath),
1432
+ error: normalizeErrorForProps(error),
1433
+ params: {},
1434
+ queryClient: createQueryClient(),
1435
+ request,
1436
+ requestId: requestIdForErrorContext(request),
1437
+ routeId: routeIdForPath(routePath ?? new URL(request.url).pathname),
1438
+ traceId: traceContextFromRequest(request)?.traceId,
1439
+ };
1440
+ }
1432
1441
  async function renderServerFileToHtml(file, props, serverModules, serverModuleCacheVersion, serverSourceFiles, define, vitePlugins) {
1433
1442
  const code = await readServerSourceFile(file, serverModuleCacheVersion, serverSourceFiles);
1434
1443
  const output = transformServerModule({
@@ -2184,11 +2193,11 @@ function runServerStreamModule(code, options) {
2184
2193
  script: options.script,
2185
2194
  props: {
2186
2195
  params: options.props.params,
2187
- request: { url: options.props.request.url },
2196
+ request: { url: options.requestUrl },
2188
2197
  data: options.props.data,
2189
2198
  },
2190
2199
  })
2191
- : undefined;
2200
+ : serverOnlyRouteMarkerParts(options.routePath, options.markerRequest, options.prerenderVariantCapture === true);
2192
2201
  sink.append("<!DOCTYPE html>");
2193
2202
  sink.append(clientNavigationHeadTags({
2194
2203
  assetBaseUrl: options.assetBaseUrl,
@@ -2317,10 +2326,17 @@ async function runServerStreamModuleWithLoading(code, options) {
2317
2326
  script: options.script,
2318
2327
  props: {
2319
2328
  params: options.params,
2320
- request: { url: options.request.url },
2329
+ request: { url: options.requestUrl },
2321
2330
  },
2322
2331
  })
2323
- : undefined;
2332
+ : serverOnlyRouteMarkerParts(options.routePath, options.markerRequest, options.prerenderVariantCapture === true);
2333
+ const dataSettlement = options.dataSettlement();
2334
+ if (dataSettlement.status === "rejected") {
2335
+ throw dataSettlement.error;
2336
+ }
2337
+ if (dataSettlement.status === "fulfilled" && dataSettlement.value instanceof Response) {
2338
+ return dataSettlement.value;
2339
+ }
2324
2340
  return renderToReadableStream((sink) => {
2325
2341
  sink.append("<!DOCTYPE html>");
2326
2342
  sink.append(clientNavigationHeadTags({
@@ -2332,13 +2348,31 @@ async function runServerStreamModuleWithLoading(code, options) {
2332
2348
  sink.append(shell.prefix);
2333
2349
  }
2334
2350
  sink.append(marker?.prefix ?? "");
2335
- renderVisibleOutOfOrderBoundary(sink, "mreact-route", options.data, async (boundarySink, data) => {
2351
+ renderOutOfOrderBoundary(sink, "mreact-route", options.data, async (boundarySink, data) => {
2336
2352
  await appendServerStreamModule(code, boundarySink, withTrackedRequest({
2337
2353
  data,
2338
2354
  params: options.params,
2339
2355
  queryClient: options.queryClient,
2340
2356
  }, options.request, options.trackedRequest), options.pageFile, options.serverModules, options.serverModuleCacheVersion, options.define, options.vitePlugins, options.importPolicy);
2341
2357
  }, {
2358
+ async catch(boundarySink, error) {
2359
+ try {
2360
+ options.onRenderError?.(error);
2361
+ }
2362
+ catch {
2363
+ // Error reporting is best-effort and must not truncate a response that already flushed.
2364
+ }
2365
+ if (options.errorFile === undefined) {
2366
+ boundarySink.append("Internal Server Error");
2367
+ return;
2368
+ }
2369
+ try {
2370
+ boundarySink.append(await renderServerFileToHtml(options.errorFile, errorBoundaryProps(error, options.request, options.routePath), options.serverModules, options.serverModuleCacheVersion, options.serverSourceFiles, options.define, options.vitePlugins));
2371
+ }
2372
+ catch {
2373
+ boundarySink.append("Internal Server Error");
2374
+ }
2375
+ },
2342
2376
  placeholder(boundarySink) {
2343
2377
  boundarySink.append(loadingHtml);
2344
2378
  },
@@ -2351,35 +2385,6 @@ async function runServerStreamModuleWithLoading(code, options) {
2351
2385
  renderOutOfOrderReorderScript(sink);
2352
2386
  });
2353
2387
  }
2354
- function renderVisibleOutOfOrderBoundary(sink, id, value, render, options = {}) {
2355
- const placeholderSink = createStringSink();
2356
- void options.placeholder?.(placeholderSink);
2357
- const placeholderTag = normalizeVisibleOutOfOrderPlaceholderTag(options.placeholderTag);
2358
- sink.append(`<${placeholderTag} data-mreact-oob-placeholder="${escapeHtmlAttribute(id)}">${placeholderSink.toString()}</${placeholderTag}>`);
2359
- const task = renderVisibleOutOfOrderFragment(sink, id, value, render, options);
2360
- if (sink.defer === undefined) {
2361
- void task;
2362
- return;
2363
- }
2364
- sink.defer(task);
2365
- }
2366
- function normalizeVisibleOutOfOrderPlaceholderTag(tag) {
2367
- if (typeof tag !== "string") {
2368
- return "span";
2369
- }
2370
- const normalized = tag.trim().toLowerCase();
2371
- return /^[a-z][a-z0-9-]*$/.test(normalized) ? normalized : "span";
2372
- }
2373
- async function renderVisibleOutOfOrderFragment(sink, id, value, render, options) {
2374
- const fragmentSink = createStringSink();
2375
- await renderAsyncBoundary(fragmentSink, value, render, options.catch === undefined ? {} : { catch: options.catch });
2376
- await fragmentSink.drain();
2377
- sink.append(renderVisibleOutOfOrderFragmentHtml(id, fragmentSink.toString()));
2378
- }
2379
- function renderVisibleOutOfOrderFragmentHtml(id, html) {
2380
- const escapedId = escapeHtmlAttribute(id);
2381
- return `<template data-mreact-oob-fragment="${escapedId}">${html}</template><mreact-oob-complete hidden data-mreact-oob-complete="${escapedId}"></mreact-oob-complete>`;
2382
- }
2383
2388
  async function appendServerStreamModule(code, sink, props, sourcefile, serverModules, serverModuleCacheVersion, define, vitePlugins, importPolicy) {
2384
2389
  const module = await loadServerStreamModule(code, sourcefile, serverModules, serverModuleCacheVersion, define, vitePlugins, importPolicy);
2385
2390
  const component = selectStreamComponent(module);
@@ -3089,15 +3094,16 @@ async function loadComposedRouteMetadata(options) {
3089
3094
  if (cacheKey !== undefined) {
3090
3095
  const cached = readRouterRuntimeCacheEntry(composedRouteMetadataCache, cacheKey, composedRouteMetadataCacheCounters);
3091
3096
  if (cached !== undefined) {
3092
- return cached;
3097
+ const entry = await cached;
3098
+ return applyFileConventionMetadataTemplate(entry.metadata, entry.template, options.context.params);
3093
3099
  }
3094
3100
  }
3095
3101
  try {
3096
3102
  const loaded = await loadComposedRouteMetadataUncached(options);
3097
3103
  if (cacheKey !== undefined && !loaded.dynamic) {
3098
- setBoundedCacheEntry(composedRouteMetadataCache, cacheKey, Promise.resolve(loaded.metadata), maxComposedRouteMetadataCacheEntries, composedRouteMetadataCacheCounters);
3104
+ setBoundedCacheEntry(composedRouteMetadataCache, cacheKey, Promise.resolve({ metadata: loaded.metadata, template: loaded.template }), maxComposedRouteMetadataCacheEntries, composedRouteMetadataCacheCounters);
3099
3105
  }
3100
- return loaded.metadata;
3106
+ return applyFileConventionMetadataTemplate(loaded.metadata, loaded.template, options.context.params);
3101
3107
  }
3102
3108
  catch (error) {
3103
3109
  if (cacheKey !== undefined) {
@@ -3163,7 +3169,8 @@ async function loadComposedRouteMetadataUncached(options) {
3163
3169
  }
3164
3170
  return {
3165
3171
  dynamic,
3166
- metadata: applyFileConventionMetadata(mergeRouteMetadata(metadata), options.routes, options.filename, options.context.params),
3172
+ metadata: mergeRouteMetadata(metadata),
3173
+ template: fileConventionMetadataTemplate(options.routes, options.filename),
3167
3174
  };
3168
3175
  }
3169
3176
  function hasMetadataExport(code) {