vinext 0.0.32 → 0.0.34

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 (95) hide show
  1. package/README.md +7 -6
  2. package/dist/config/next-config.d.ts +2 -0
  3. package/dist/config/next-config.js +4 -0
  4. package/dist/config/next-config.js.map +1 -1
  5. package/dist/deploy.js +52 -4
  6. package/dist/deploy.js.map +1 -1
  7. package/dist/entries/app-browser-entry.js +3 -330
  8. package/dist/entries/app-browser-entry.js.map +1 -1
  9. package/dist/entries/app-rsc-entry.js +444 -1265
  10. package/dist/entries/app-rsc-entry.js.map +1 -1
  11. package/dist/entries/app-ssr-entry.js +4 -460
  12. package/dist/entries/app-ssr-entry.js.map +1 -1
  13. package/dist/entries/pages-server-entry.js +8 -1
  14. package/dist/entries/pages-server-entry.js.map +1 -1
  15. package/dist/entries/runtime-entry-module.d.ts +13 -0
  16. package/dist/entries/runtime-entry-module.js +27 -0
  17. package/dist/entries/runtime-entry-module.js.map +1 -0
  18. package/dist/index.js +302 -23
  19. package/dist/index.js.map +1 -1
  20. package/dist/plugins/optimize-imports.d.ts +38 -0
  21. package/dist/plugins/optimize-imports.js +557 -0
  22. package/dist/plugins/optimize-imports.js.map +1 -0
  23. package/dist/server/app-browser-entry.d.ts +1 -0
  24. package/dist/server/app-browser-entry.js +160 -0
  25. package/dist/server/app-browser-entry.js.map +1 -0
  26. package/dist/server/app-browser-stream.d.ts +33 -0
  27. package/dist/server/app-browser-stream.js +54 -0
  28. package/dist/server/app-browser-stream.js.map +1 -0
  29. package/dist/server/app-page-boundary-render.d.ts +63 -0
  30. package/dist/server/app-page-boundary-render.js +182 -0
  31. package/dist/server/app-page-boundary-render.js.map +1 -0
  32. package/dist/server/app-page-boundary.d.ts +57 -0
  33. package/dist/server/app-page-boundary.js +60 -0
  34. package/dist/server/app-page-boundary.js.map +1 -0
  35. package/dist/server/app-page-cache.d.ts +61 -0
  36. package/dist/server/app-page-cache.js +133 -0
  37. package/dist/server/app-page-cache.js.map +1 -0
  38. package/dist/server/app-page-execution.d.ts +46 -0
  39. package/dist/server/app-page-execution.js +109 -0
  40. package/dist/server/app-page-execution.js.map +1 -0
  41. package/dist/server/app-page-probe.d.ts +17 -0
  42. package/dist/server/app-page-probe.js +35 -0
  43. package/dist/server/app-page-probe.js.map +1 -0
  44. package/dist/server/app-page-render.d.ts +59 -0
  45. package/dist/server/app-page-render.js +174 -0
  46. package/dist/server/app-page-render.js.map +1 -0
  47. package/dist/server/app-page-request.d.ts +58 -0
  48. package/dist/server/app-page-request.js +79 -0
  49. package/dist/server/app-page-request.js.map +1 -0
  50. package/dist/server/app-page-response.d.ts +51 -0
  51. package/dist/server/app-page-response.js +90 -0
  52. package/dist/server/app-page-response.js.map +1 -0
  53. package/dist/server/app-page-stream.d.ts +55 -0
  54. package/dist/server/app-page-stream.js +65 -0
  55. package/dist/server/app-page-stream.js.map +1 -0
  56. package/dist/server/app-route-handler-cache.d.ts +42 -0
  57. package/dist/server/app-route-handler-cache.js +69 -0
  58. package/dist/server/app-route-handler-cache.js.map +1 -0
  59. package/dist/server/app-route-handler-execution.d.ts +64 -0
  60. package/dist/server/app-route-handler-execution.js +100 -0
  61. package/dist/server/app-route-handler-execution.js.map +1 -0
  62. package/dist/server/app-route-handler-policy.d.ts +51 -0
  63. package/dist/server/app-route-handler-policy.js +57 -0
  64. package/dist/server/app-route-handler-policy.js.map +1 -0
  65. package/dist/server/app-route-handler-response.d.ts +26 -0
  66. package/dist/server/app-route-handler-response.js +61 -0
  67. package/dist/server/app-route-handler-response.js.map +1 -0
  68. package/dist/server/app-route-handler-runtime.d.ts +27 -0
  69. package/dist/server/app-route-handler-runtime.js +99 -0
  70. package/dist/server/app-route-handler-runtime.js.map +1 -0
  71. package/dist/server/app-ssr-entry.d.ts +19 -0
  72. package/dist/server/app-ssr-entry.js +105 -0
  73. package/dist/server/app-ssr-entry.js.map +1 -0
  74. package/dist/server/app-ssr-stream.d.ts +30 -0
  75. package/dist/server/app-ssr-stream.js +116 -0
  76. package/dist/server/app-ssr-stream.js.map +1 -0
  77. package/dist/server/prod-server.d.ts +13 -1
  78. package/dist/server/prod-server.js +113 -19
  79. package/dist/server/prod-server.js.map +1 -1
  80. package/dist/server/worker-utils.d.ts +0 -6
  81. package/dist/server/worker-utils.js +41 -5
  82. package/dist/server/worker-utils.js.map +1 -1
  83. package/dist/shims/error-boundary.js +1 -1
  84. package/dist/shims/font-google-base.js +1 -1
  85. package/dist/shims/font-google-base.js.map +1 -1
  86. package/dist/shims/font-google.d.ts +2 -3
  87. package/dist/shims/font-google.js +2 -3
  88. package/dist/shims/metadata.js +3 -3
  89. package/dist/shims/metadata.js.map +1 -1
  90. package/dist/shims/request-state-types.d.ts +2 -2
  91. package/dist/shims/unified-request-context.d.ts +1 -1
  92. package/package.json +1 -1
  93. package/dist/shims/font-google.generated.d.ts +0 -1929
  94. package/dist/shims/font-google.generated.js +0 -1929
  95. package/dist/shims/font-google.generated.js.map +0 -1
@@ -16,33 +16,17 @@ import { fileURLToPath } from "node:url";
16
16
  const configMatchersPath = fileURLToPath(new URL("../config/config-matchers.js", import.meta.url)).replace(/\\/g, "/");
17
17
  const requestPipelinePath = fileURLToPath(new URL("../server/request-pipeline.js", import.meta.url)).replace(/\\/g, "/");
18
18
  const requestContextShimPath = fileURLToPath(new URL("../shims/request-context.js", import.meta.url)).replace(/\\/g, "/");
19
+ const appRouteHandlerRuntimePath = fileURLToPath(new URL("../server/app-route-handler-runtime.js", import.meta.url)).replace(/\\/g, "/");
20
+ const appRouteHandlerPolicyPath = fileURLToPath(new URL("../server/app-route-handler-policy.js", import.meta.url)).replace(/\\/g, "/");
21
+ const appRouteHandlerExecutionPath = fileURLToPath(new URL("../server/app-route-handler-execution.js", import.meta.url)).replace(/\\/g, "/");
22
+ const appRouteHandlerCachePath = fileURLToPath(new URL("../server/app-route-handler-cache.js", import.meta.url)).replace(/\\/g, "/");
23
+ const appPageCachePath = fileURLToPath(new URL("../server/app-page-cache.js", import.meta.url)).replace(/\\/g, "/");
24
+ const appPageExecutionPath = fileURLToPath(new URL("../server/app-page-execution.js", import.meta.url)).replace(/\\/g, "/");
25
+ const appPageBoundaryRenderPath = fileURLToPath(new URL("../server/app-page-boundary-render.js", import.meta.url)).replace(/\\/g, "/");
26
+ const appPageRenderPath = fileURLToPath(new URL("../server/app-page-render.js", import.meta.url)).replace(/\\/g, "/");
27
+ const appPageRequestPath = fileURLToPath(new URL("../server/app-page-request.js", import.meta.url)).replace(/\\/g, "/");
28
+ const appRouteHandlerResponsePath = fileURLToPath(new URL("../server/app-route-handler-response.js", import.meta.url)).replace(/\\/g, "/");
19
29
  const routeTriePath = fileURLToPath(new URL("../routing/route-trie.js", import.meta.url)).replace(/\\/g, "/");
20
- const routeHandlerHelperCode = String.raw`
21
- // Duplicated from the build-time constant above via JSON.stringify.
22
- const ROUTE_HANDLER_HTTP_METHODS = ${JSON.stringify([
23
- "GET",
24
- "HEAD",
25
- "POST",
26
- "PUT",
27
- "DELETE",
28
- "PATCH",
29
- "OPTIONS"
30
- ])};
31
-
32
- function collectRouteHandlerMethods(handler) {
33
- const methods = ROUTE_HANDLER_HTTP_METHODS.filter((method) => typeof handler[method] === "function");
34
- if (methods.includes("GET") && !methods.includes("HEAD")) {
35
- methods.push("HEAD");
36
- }
37
- return methods;
38
- }
39
-
40
- function buildRouteHandlerAllowHeader(exportedMethods) {
41
- const allow = new Set(exportedMethods);
42
- allow.add("OPTIONS");
43
- return Array.from(allow).sort().join(", ");
44
- }
45
- `;
46
30
  /**
47
31
  * Generate the virtual RSC entry module.
48
32
  *
@@ -198,7 +182,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
198
182
  // Flight lines are newline-delimited, so we buffer partial lines across chunks
199
183
  // to guarantee the regex never sees a split hint.
200
184
  function renderToReadableStream(model, options) {
201
- const _hlFixRe = /(\\d+:HL\\[.*?),"stylesheet"(\\]|,)/g;
185
+ const _hlFixRe = /(\\d*:HL\\[.*?),"stylesheet"(\\]|,)/g;
202
186
  const stream = _renderToReadableStream(model, options);
203
187
  const decoder = new TextDecoder();
204
188
  const encoder = new TextEncoder();
@@ -232,6 +216,42 @@ ${instrumentationPath ? `import * as _instrumentation from ${JSON.stringify(inst
232
216
  ${effectiveMetaRoutes.length > 0 ? `import { sitemapToXml, robotsToText, manifestToJson } from ${JSON.stringify(fileURLToPath(new URL("../server/metadata-routes.js", import.meta.url)).replace(/\\/g, "/"))};` : ""}
233
217
  import { requestContextFromRequest, normalizeHost, matchRedirect, matchRewrite, matchHeaders, isExternalUrl, proxyExternalRequest, sanitizeDestination } from ${JSON.stringify(configMatchersPath)};
234
218
  import { validateCsrfOrigin, validateImageUrl, guardProtocolRelativeUrl, hasBasePath, stripBasePath, normalizeTrailingSlash, processMiddlewareHeaders } from ${JSON.stringify(requestPipelinePath)};
219
+ import {
220
+ isKnownDynamicAppRoute as __isKnownDynamicAppRoute,
221
+ } from ${JSON.stringify(appRouteHandlerRuntimePath)};
222
+ import {
223
+ getAppRouteHandlerRevalidateSeconds as __getAppRouteHandlerRevalidateSeconds,
224
+ hasAppRouteHandlerDefaultExport as __hasAppRouteHandlerDefaultExport,
225
+ resolveAppRouteHandlerMethod as __resolveAppRouteHandlerMethod,
226
+ shouldReadAppRouteHandlerCache as __shouldReadAppRouteHandlerCache,
227
+ } from ${JSON.stringify(appRouteHandlerPolicyPath)};
228
+ import {
229
+ executeAppRouteHandler as __executeAppRouteHandler,
230
+ } from ${JSON.stringify(appRouteHandlerExecutionPath)};
231
+ import { readAppRouteHandlerCacheResponse as __readAppRouteHandlerCacheResponse } from ${JSON.stringify(appRouteHandlerCachePath)};
232
+ import { readAppPageCacheResponse as __readAppPageCacheResponse } from ${JSON.stringify(appPageCachePath)};
233
+ import {
234
+ buildAppPageFontLinkHeader as __buildAppPageFontLinkHeader,
235
+ buildAppPageSpecialErrorResponse as __buildAppPageSpecialErrorResponse,
236
+ readAppPageTextStream as __readAppPageTextStream,
237
+ resolveAppPageSpecialError as __resolveAppPageSpecialError,
238
+ teeAppPageRscStreamForCapture as __teeAppPageRscStreamForCapture,
239
+ } from ${JSON.stringify(appPageExecutionPath)};
240
+ import {
241
+ renderAppPageErrorBoundary as __renderAppPageErrorBoundary,
242
+ renderAppPageHttpAccessFallback as __renderAppPageHttpAccessFallback,
243
+ } from ${JSON.stringify(appPageBoundaryRenderPath)};
244
+ import {
245
+ renderAppPageLifecycle as __renderAppPageLifecycle,
246
+ } from ${JSON.stringify(appPageRenderPath)};
247
+ import {
248
+ buildAppPageElement as __buildAppPageElement,
249
+ resolveAppPageIntercept as __resolveAppPageIntercept,
250
+ validateAppPageDynamicParams as __validateAppPageDynamicParams,
251
+ } from ${JSON.stringify(appPageRequestPath)};
252
+ import {
253
+ applyRouteHandlerMiddlewareContext as __applyRouteHandlerMiddlewareContext,
254
+ } from ${JSON.stringify(appRouteHandlerResponsePath)};
235
255
  import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache";
236
256
  import { getRequestExecutionContext as _getRequestExecutionContext } from ${JSON.stringify(requestContextShimPath)};
237
257
  import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache";
@@ -245,7 +265,6 @@ import { getSSRFontStyles as _getSSRFontStylesLocal, getSSRFontPreloads as _getS
245
265
  function _getSSRFontStyles() { return [..._getSSRFontStylesGoogle(), ..._getSSRFontStylesLocal()]; }
246
266
  function _getSSRFontPreloads() { return [..._getSSRFontPreloadsGoogle(), ..._getSSRFontPreloadsLocal()]; }
247
267
  ${hasPagesDir ? `// Note: pageRoutes loaded lazily via SSR env in /__vinext/prerender/pages-static-paths handler` : ""}
248
- ${routeHandlerHelperCode}
249
268
 
250
269
  // ALS used to suppress the expected "Invalid hook call" dev warning when
251
270
  // layout/page components are probed outside React's render cycle. Patching
@@ -438,15 +457,8 @@ function __errorDigest(str) {
438
457
  // unchanged since their digests are used for client-side routing.
439
458
  function __sanitizeErrorForClient(error) {
440
459
  // Navigation errors must pass through with their digest intact
441
- if (error && typeof error === "object" && "digest" in error) {
442
- const digest = String(error.digest);
443
- if (
444
- digest.startsWith("NEXT_REDIRECT;") ||
445
- digest === "NEXT_NOT_FOUND" ||
446
- digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")
447
- ) {
448
- return error;
449
- }
460
+ if (__resolveAppPageSpecialError(error)) {
461
+ return error;
450
462
  }
451
463
  // In development, pass through the original error for debugging
452
464
  if (process.env.NODE_ENV !== "production") {
@@ -604,139 +616,37 @@ const rootLayouts = [${rootLayoutVars.join(", ")}];
604
616
  * @param opts.layouts - Override the layouts to wrap with (for layout-level notFound, excludes the throwing layout)
605
617
  */
606
618
  async function renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, request, opts) {
607
- // Determine which boundary component to use based on status code
608
- let BoundaryComponent = opts?.boundaryComponent ?? null;
609
- if (!BoundaryComponent) {
610
- let boundaryModule;
611
- if (statusCode === 403) {
612
- boundaryModule = route?.forbidden ?? rootForbiddenModule;
613
- } else if (statusCode === 401) {
614
- boundaryModule = route?.unauthorized ?? rootUnauthorizedModule;
615
- } else {
616
- boundaryModule = route?.notFound ?? rootNotFoundModule;
617
- }
618
- BoundaryComponent = boundaryModule?.default ?? null;
619
- }
620
- const layouts = opts?.layouts ?? route?.layouts ?? rootLayouts;
621
- if (!BoundaryComponent) return null;
622
-
623
- // Resolve metadata and viewport from parent layouts so that not-found/error
624
- // pages inherit title, description, OG tags etc. — matching Next.js behavior.
625
- // Build the serial parent chain for layout metadata (same as buildPageElement).
626
- const _filteredLayouts = layouts.filter(Boolean);
627
- const _fallbackParams = opts?.matchedParams ?? route?.params ?? {};
628
- const _layoutMetaPromises = [];
629
- let _accumulatedMeta = Promise.resolve({});
630
- for (let _i = 0; _i < _filteredLayouts.length; _i++) {
631
- const _parentForLayout = _accumulatedMeta;
632
- const _metaP = resolveModuleMetadata(_filteredLayouts[_i], _fallbackParams, undefined, _parentForLayout)
633
- .catch((err) => { console.error("[vinext] Layout generateMetadata() failed:", err); return null; });
634
- _layoutMetaPromises.push(_metaP);
635
- _accumulatedMeta = _metaP.then(async (_r) =>
636
- _r ? mergeMetadata([await _parentForLayout, _r]) : await _parentForLayout
637
- );
638
- }
639
- const [_metaResults, _vpResults] = await Promise.all([
640
- Promise.all(_layoutMetaPromises),
641
- Promise.all(_filteredLayouts.map((mod) => resolveModuleViewport(mod, _fallbackParams).catch((err) => { console.error("[vinext] Layout generateViewport() failed:", err); return null; }))),
642
- ]);
643
- const metadataList = _metaResults.filter(Boolean);
644
- const viewportList = _vpResults.filter(Boolean);
645
- const resolvedMetadata = metadataList.length > 0 ? mergeMetadata(metadataList) : null;
646
- const resolvedViewport = mergeViewport(viewportList);
647
-
648
- // Build element: metadata head + noindex meta + boundary component wrapped in layouts
649
- // Always include charset and default viewport for parity with Next.js.
650
- const charsetMeta = createElement("meta", { charSet: "utf-8" });
651
- const noindexMeta = createElement("meta", { name: "robots", content: "noindex" });
652
- const headElements = [charsetMeta, noindexMeta];
653
- if (resolvedMetadata) headElements.push(createElement(MetadataHead, { metadata: resolvedMetadata }));
654
- headElements.push(createElement(ViewportHead, { viewport: resolvedViewport }));
655
- let element = createElement(Fragment, null, ...headElements, createElement(BoundaryComponent));
656
- if (isRscRequest) {
657
- // For RSC requests (client-side navigation), wrap the element with the same
658
- // component wrappers that buildPageElement() uses. Without these wrappers,
659
- // React's reconciliation would see a mismatched tree structure between the
660
- // old fiber tree (ErrorBoundary > LayoutSegmentProvider > html > body > NotFoundBoundary > ...)
661
- // and the new tree (html > body > ...), causing it to destroy and recreate
662
- // the entire DOM tree, resulting in a blank white page.
663
- //
664
- // We wrap each layout with LayoutSegmentProvider and add GlobalErrorBoundary
665
- // to match the wrapping order in buildPageElement(), ensuring smooth
666
- // client-side tree reconciliation.
667
- const _treePositions = route?.layoutTreePositions;
668
- const _routeSegs = route?.routeSegments || [];
669
- const _fallbackParams = opts?.matchedParams ?? route?.params ?? {};
670
- const _asyncFallbackParams = makeThenableParams(_fallbackParams);
671
- for (let i = layouts.length - 1; i >= 0; i--) {
672
- const LayoutComponent = layouts[i]?.default;
673
- if (LayoutComponent) {
674
- element = createElement(LayoutComponent, { children: element, params: _asyncFallbackParams });
675
- const _tp = _treePositions ? _treePositions[i] : 0;
676
- const _cs = __resolveChildSegments(_routeSegs, _tp, _fallbackParams);
677
- element = createElement(LayoutSegmentProvider, { childSegments: _cs }, element);
678
- }
679
- }
680
- ${globalErrorVar ? `
681
- const _GlobalErrorComponent = ${globalErrorVar}.default;
682
- if (_GlobalErrorComponent) {
683
- element = createElement(ErrorBoundary, {
684
- fallback: _GlobalErrorComponent,
685
- children: element,
686
- });
687
- }
688
- ` : ""}
689
- const _pathname = new URL(request.url).pathname;
690
- const onRenderError = createRscOnErrorHandler(
691
- request,
692
- _pathname,
693
- route?.pattern ?? _pathname,
694
- );
695
- const rscStream = renderToReadableStream(element, { onError: onRenderError });
696
- // Do NOT clear context here — the RSC stream is consumed lazily by the client.
697
- // Clearing context now would cause async server components (e.g. NextIntlClientProviderServer)
698
- // that run during stream consumption to see null headers/navigation context and throw,
699
- // resulting in missing provider context on the client (e.g. next-intl useTranslations fails
700
- // with "context from NextIntlClientProvider was not found").
701
- // Context is cleared naturally when the ALS scope from runWithRequestContext unwinds.
702
- return new Response(rscStream, {
703
- status: statusCode,
704
- headers: { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" },
705
- });
706
- }
707
- // For HTML (full page load) responses, wrap with layouts only (no client-side
708
- // wrappers needed since SSR generates the complete HTML document).
709
- const _fallbackParamsHtml = opts?.matchedParams ?? route?.params ?? {};
710
- const _asyncFallbackParamsHtml = makeThenableParams(_fallbackParamsHtml);
711
- for (let i = layouts.length - 1; i >= 0; i--) {
712
- const LayoutComponent = layouts[i]?.default;
713
- if (LayoutComponent) {
714
- element = createElement(LayoutComponent, { children: element, params: _asyncFallbackParamsHtml });
715
- }
716
- }
717
- const _pathname = new URL(request.url).pathname;
718
- const onRenderError = createRscOnErrorHandler(
719
- request,
720
- _pathname,
721
- route?.pattern ?? _pathname,
722
- );
723
- const rscStream = renderToReadableStream(element, { onError: onRenderError });
724
- // Collect font data from RSC environment
725
- const fontData = {
726
- links: _getSSRFontLinks(),
727
- styles: _getSSRFontStyles(),
728
- preloads: _getSSRFontPreloads(),
729
- };
730
- const ssrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
731
- const htmlStream = await ssrEntry.handleSsr(rscStream, _getNavigationContext(), fontData);
732
- setHeadersContext(null);
733
- setNavigationContext(null);
734
- const _respHeaders = { "Content-Type": "text/html; charset=utf-8", "Vary": "RSC, Accept" };
735
- const _linkParts = (fontData.preloads || []).map(function(p) { return "<" + p.href + ">; rel=preload; as=font; type=" + p.type + "; crossorigin"; });
736
- if (_linkParts.length > 0) _respHeaders["Link"] = _linkParts.join(", ");
737
- return new Response(htmlStream, {
738
- status: statusCode,
739
- headers: _respHeaders,
619
+ return __renderAppPageHttpAccessFallback({
620
+ boundaryComponent: opts?.boundaryComponent ?? null,
621
+ buildFontLinkHeader: __buildAppPageFontLinkHeader,
622
+ clearRequestContext() {
623
+ setHeadersContext(null);
624
+ setNavigationContext(null);
625
+ },
626
+ createRscOnErrorHandler(pathname, routePath) {
627
+ return createRscOnErrorHandler(request, pathname, routePath);
628
+ },
629
+ getFontLinks: _getSSRFontLinks,
630
+ getFontPreloads: _getSSRFontPreloads,
631
+ getFontStyles: _getSSRFontStyles,
632
+ getNavigationContext: _getNavigationContext,
633
+ globalErrorModule: ${globalErrorVar ? globalErrorVar : "null"},
634
+ isRscRequest,
635
+ layoutModules: opts?.layouts ?? null,
636
+ loadSsrHandler() {
637
+ return import.meta.viteRsc.loadModule("ssr", "index");
638
+ },
639
+ makeThenableParams,
640
+ matchedParams: opts?.matchedParams ?? route?.params ?? {},
641
+ requestUrl: request.url,
642
+ resolveChildSegments: __resolveChildSegments,
643
+ rootForbiddenModule: rootForbiddenModule,
644
+ rootLayouts: rootLayouts,
645
+ rootNotFoundModule: rootNotFoundModule,
646
+ rootUnauthorizedModule: rootUnauthorizedModule,
647
+ route,
648
+ renderToReadableStream,
649
+ statusCode,
740
650
  });
741
651
  }
742
652
 
@@ -753,122 +663,33 @@ async function renderNotFoundPage(route, isRscRequest, request, matchedParams) {
753
663
  * by the boundary). This matches that behavior intentionally.
754
664
  */
755
665
  async function renderErrorBoundaryPage(route, error, isRscRequest, request, matchedParams) {
756
- // Resolve the error boundary component: leaf error.tsx first, then walk per-layout
757
- // errors from innermost to outermost (matching ancestor inheritance), then global-error.tsx.
758
- let ErrorComponent = route?.error?.default ?? null;
759
- let _isGlobalError = false;
760
- if (!ErrorComponent && route?.errors) {
761
- for (let i = route.errors.length - 1; i >= 0; i--) {
762
- if (route.errors[i]?.default) {
763
- ErrorComponent = route.errors[i].default;
764
- break;
765
- }
766
- }
767
- }
768
- ${globalErrorVar ? `
769
- if (!ErrorComponent) {
770
- ErrorComponent = ${globalErrorVar}?.default ?? null;
771
- _isGlobalError = !!ErrorComponent;
772
- }
773
- ` : ""}
774
- if (!ErrorComponent) return null;
775
-
776
- const rawError = error instanceof Error ? error : new Error(String(error));
777
- // Sanitize the error in production to avoid leaking internal details
778
- // (database errors, file paths, stack traces) through error.tsx to the client.
779
- // In development, pass the original error for debugging.
780
- const errorObj = __sanitizeErrorForClient(rawError);
781
- // Only pass error — reset is a client-side concern (re-renders the segment) and
782
- // can't be serialized through RSC. The error.tsx component will receive reset=undefined
783
- // during SSR, which is fine — onClick={undefined} is harmless, and the real reset
784
- // function is only meaningful after hydration.
785
- let element = createElement(ErrorComponent, {
786
- error: errorObj,
787
- });
788
-
789
- // global-error.tsx provides its own <html> and <body> (it replaces the root
790
- // layout). Skip layout wrapping when rendering it to avoid double <html> tags.
791
- if (!_isGlobalError) {
792
- const layouts = route?.layouts ?? rootLayouts;
793
- if (isRscRequest) {
794
- // For RSC requests (client-side navigation), wrap with the same component
795
- // wrappers that buildPageElement() uses (LayoutSegmentProvider, GlobalErrorBoundary).
796
- // This ensures React can reconcile the tree without destroying the DOM.
797
- // Same rationale as renderHTTPAccessFallbackPage — see comment there.
798
- const _errTreePositions = route?.layoutTreePositions;
799
- const _errRouteSegs = route?.routeSegments || [];
800
- const _errParams = matchedParams ?? route?.params ?? {};
801
- const _asyncErrParams = makeThenableParams(_errParams);
802
- for (let i = layouts.length - 1; i >= 0; i--) {
803
- const LayoutComponent = layouts[i]?.default;
804
- if (LayoutComponent) {
805
- element = createElement(LayoutComponent, { children: element, params: _asyncErrParams });
806
- const _etp = _errTreePositions ? _errTreePositions[i] : 0;
807
- const _ecs = __resolveChildSegments(_errRouteSegs, _etp, _errParams);
808
- element = createElement(LayoutSegmentProvider, { childSegments: _ecs }, element);
809
- }
810
- }
811
- ${globalErrorVar ? `
812
- const _ErrGlobalComponent = ${globalErrorVar}.default;
813
- if (_ErrGlobalComponent) {
814
- element = createElement(ErrorBoundary, {
815
- fallback: _ErrGlobalComponent,
816
- children: element,
817
- });
818
- }
819
- ` : ""}
820
- } else {
821
- // For HTML (full page load) responses, wrap with layouts only.
822
- const _errParamsHtml = matchedParams ?? route?.params ?? {};
823
- const _asyncErrParamsHtml = makeThenableParams(_errParamsHtml);
824
- for (let i = layouts.length - 1; i >= 0; i--) {
825
- const LayoutComponent = layouts[i]?.default;
826
- if (LayoutComponent) {
827
- element = createElement(LayoutComponent, { children: element, params: _asyncErrParamsHtml });
828
- }
829
- }
830
- }
831
- }
832
-
833
- const _pathname = new URL(request.url).pathname;
834
- const onRenderError = createRscOnErrorHandler(
835
- request,
836
- _pathname,
837
- route?.pattern ?? _pathname,
838
- );
839
-
840
- if (isRscRequest) {
841
- const rscStream = renderToReadableStream(element, { onError: onRenderError });
842
- // Do NOT clear context here — the RSC stream is consumed lazily by the client.
843
- // Clearing context now would cause async server components (e.g. NextIntlClientProviderServer)
844
- // that run during stream consumption to see null headers/navigation context and throw,
845
- // resulting in missing provider context on the client (e.g. next-intl useTranslations fails
846
- // with "context from NextIntlClientProvider was not found").
847
- // Context is cleared naturally when the ALS scope from runWithRequestContext unwinds.
848
- return new Response(rscStream, {
849
- status: 200,
850
- headers: { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" },
851
- });
852
- }
853
-
854
- // HTML (full page load) response — render through RSC → SSR pipeline
855
- const rscStream = renderToReadableStream(element, { onError: onRenderError });
856
- // Collect font data from RSC environment so error pages include font styles
857
- const fontData = {
858
- links: _getSSRFontLinks(),
859
- styles: _getSSRFontStyles(),
860
- preloads: _getSSRFontPreloads(),
861
- };
862
- const ssrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
863
- const htmlStream = await ssrEntry.handleSsr(rscStream, _getNavigationContext(), fontData);
864
- setHeadersContext(null);
865
- setNavigationContext(null);
866
- const _errHeaders = { "Content-Type": "text/html; charset=utf-8", "Vary": "RSC, Accept" };
867
- const _errLinkParts = (fontData.preloads || []).map(function(p) { return "<" + p.href + ">; rel=preload; as=font; type=" + p.type + "; crossorigin"; });
868
- if (_errLinkParts.length > 0) _errHeaders["Link"] = _errLinkParts.join(", ");
869
- return new Response(htmlStream, {
870
- status: 200,
871
- headers: _errHeaders,
666
+ return __renderAppPageErrorBoundary({
667
+ buildFontLinkHeader: __buildAppPageFontLinkHeader,
668
+ clearRequestContext() {
669
+ setHeadersContext(null);
670
+ setNavigationContext(null);
671
+ },
672
+ createRscOnErrorHandler(pathname, routePath) {
673
+ return createRscOnErrorHandler(request, pathname, routePath);
674
+ },
675
+ error,
676
+ getFontLinks: _getSSRFontLinks,
677
+ getFontPreloads: _getSSRFontPreloads,
678
+ getFontStyles: _getSSRFontStyles,
679
+ getNavigationContext: _getNavigationContext,
680
+ globalErrorModule: ${globalErrorVar ? globalErrorVar : "null"},
681
+ isRscRequest,
682
+ loadSsrHandler() {
683
+ return import.meta.viteRsc.loadModule("ssr", "index");
684
+ },
685
+ makeThenableParams,
686
+ matchedParams: matchedParams ?? route?.params ?? {},
687
+ requestUrl: request.url,
688
+ resolveChildSegments: __resolveChildSegments,
689
+ rootLayouts: rootLayouts,
690
+ route,
691
+ renderToReadableStream,
692
+ sanitizeErrorForClient: __sanitizeErrorForClient,
872
693
  });
873
694
  }
874
695
 
@@ -1418,8 +1239,6 @@ export default async function handler(request, ctx) {
1418
1239
 
1419
1240
  async function _handleRequest(request, __reqCtx, _mwCtx) {
1420
1241
  const __reqStart = process.env.NODE_ENV !== "production" ? performance.now() : 0;
1421
- let __compileEnd;
1422
- let __renderEnd;
1423
1242
  // __reqStart is included in the timing header so the Node logging middleware
1424
1243
  // can compute true compile time as: handlerStart - middlewareStart.
1425
1244
  // Format: "handlerStart,compileMs,renderMs" - all as integers (ms). Dev-only.
@@ -2037,266 +1856,132 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2037
1856
  if (route.routeHandler) {
2038
1857
  const handler = route.routeHandler;
2039
1858
  const method = request.method.toUpperCase();
2040
- const revalidateSeconds = typeof handler.revalidate === "number" && handler.revalidate > 0 && handler.revalidate !== Infinity ? handler.revalidate : null;
2041
- if (typeof handler["default"] === "function" && process.env.NODE_ENV === "development") {
1859
+ const revalidateSeconds = __getAppRouteHandlerRevalidateSeconds(handler);
1860
+ if (__hasAppRouteHandlerDefaultExport(handler) && process.env.NODE_ENV === "development") {
2042
1861
  console.error(
2043
1862
  "[vinext] Detected default export in route handler " + route.pattern + ". Export a named export for each HTTP method instead.",
2044
1863
  );
2045
1864
  }
2046
1865
 
2047
- // Collect exported HTTP methods for OPTIONS auto-response and Allow header
2048
- const exportedMethods = collectRouteHandlerMethods(handler);
2049
- const allowHeaderForOptions = buildRouteHandlerAllowHeader(exportedMethods);
2050
-
2051
- // Route handlers need the same middleware header/status merge behavior as
2052
- // page responses. This keeps middleware response headers visible on API
2053
- // routes in Workers/dev, and preserves custom rewrite status overrides.
2054
- function attachRouteHandlerMiddlewareContext(response) {
2055
- // _mwCtx.headers is only set (non-null) when middleware actually ran and
2056
- // produced a continue/rewrite response. An empty Headers object (middleware
2057
- // ran but produced no response headers) is a harmless edge case: the early
2058
- // return is skipped, but the copy loop below is a no-op, so no incorrect
2059
- // headers are added. The allocation cost in that case is acceptable.
2060
- if (!_mwCtx.headers && _mwCtx.status == null) return response;
2061
- const responseHeaders = new Headers(response.headers);
2062
- if (_mwCtx.headers) {
2063
- for (const [key, value] of _mwCtx.headers) {
2064
- responseHeaders.append(key, value);
2065
- }
2066
- }
2067
- return new Response(response.body, {
2068
- status: _mwCtx.status ?? response.status,
2069
- statusText: response.statusText,
2070
- headers: responseHeaders,
2071
- });
2072
- }
1866
+ const {
1867
+ allowHeaderForOptions,
1868
+ handlerFn,
1869
+ isAutoHead,
1870
+ shouldAutoRespondToOptions,
1871
+ } = __resolveAppRouteHandlerMethod(handler, method);
2073
1872
 
2074
- // OPTIONS auto-implementation: respond with Allow header and 204
2075
- if (method === "OPTIONS" && typeof handler["OPTIONS"] !== "function") {
1873
+ if (shouldAutoRespondToOptions) {
2076
1874
  setHeadersContext(null);
2077
1875
  setNavigationContext(null);
2078
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2079
- status: 204,
2080
- headers: { "Allow": allowHeaderForOptions },
2081
- }));
2082
- }
2083
-
2084
- // HEAD auto-implementation: run GET handler and strip body
2085
- let handlerFn = handler[method];
2086
- let isAutoHead = false;
2087
- if (method === "HEAD" && typeof handler["HEAD"] !== "function" && typeof handler["GET"] === "function") {
2088
- handlerFn = handler["GET"];
2089
- isAutoHead = true;
1876
+ return __applyRouteHandlerMiddlewareContext(
1877
+ new Response(null, {
1878
+ status: 204,
1879
+ headers: { "Allow": allowHeaderForOptions },
1880
+ }),
1881
+ _mwCtx,
1882
+ );
2090
1883
  }
2091
1884
 
2092
1885
  // ISR cache read for route handlers (production only).
2093
1886
  // Only GET/HEAD (auto-HEAD) with finite revalidate > 0 are ISR-eligible.
2094
- // This runs before handler execution so a cache HIT skips the handler entirely.
1887
+ // Known-dynamic handlers skip the read entirely so stale cache entries
1888
+ // from earlier requests do not replay once the process has learned they
1889
+ // access request-specific data.
2095
1890
  if (
2096
- process.env.NODE_ENV === "production" &&
2097
- revalidateSeconds !== null &&
2098
- handler.dynamic !== "force-dynamic" &&
2099
- (method === "GET" || isAutoHead) &&
2100
- typeof handlerFn === "function"
1891
+ __shouldReadAppRouteHandlerCache({
1892
+ dynamicConfig: handler.dynamic,
1893
+ handlerFn,
1894
+ isAutoHead,
1895
+ isKnownDynamic: __isKnownDynamicAppRoute(route.pattern),
1896
+ isProduction: process.env.NODE_ENV === "production",
1897
+ method,
1898
+ revalidateSeconds,
1899
+ })
2101
1900
  ) {
2102
- const __routeKey = __isrRouteKey(cleanPathname);
2103
- try {
2104
- const __cached = await __isrGet(__routeKey);
2105
- if (__cached && !__cached.isStale && __cached.value.value && __cached.value.value.kind === "APP_ROUTE") {
2106
- // HIT — return cached response immediately
2107
- const __cv = __cached.value.value;
2108
- __isrDebug?.("HIT (route)", cleanPathname);
1901
+ const __cachedRouteResponse = await __readAppRouteHandlerCacheResponse({
1902
+ basePath: __basePath,
1903
+ buildPageCacheTags: __pageCacheTags,
1904
+ cleanPathname,
1905
+ clearRequestContext: function() {
2109
1906
  setHeadersContext(null);
2110
1907
  setNavigationContext(null);
2111
- const __hitHeaders = Object.assign({}, __cv.headers || {});
2112
- __hitHeaders["X-Vinext-Cache"] = "HIT";
2113
- __hitHeaders["Cache-Control"] = "s-maxage=" + revalidateSeconds + ", stale-while-revalidate";
2114
- if (isAutoHead) {
2115
- return attachRouteHandlerMiddlewareContext(new Response(null, { status: __cv.status, headers: __hitHeaders }));
2116
- }
2117
- return attachRouteHandlerMiddlewareContext(new Response(__cv.body, { status: __cv.status, headers: __hitHeaders }));
2118
- }
2119
- if (__cached && __cached.isStale && __cached.value.value && __cached.value.value.kind === "APP_ROUTE") {
2120
- // STALE — serve stale response, trigger background regeneration
2121
- const __sv = __cached.value.value;
2122
- const __revalSecs = revalidateSeconds;
2123
- const __revalHandlerFn = handlerFn;
2124
- const __revalParams = params;
2125
- const __revalUrl = request.url;
2126
- const __revalSearchParams = new URLSearchParams(url.searchParams);
2127
- __triggerBackgroundRegeneration(__routeKey, async function() {
2128
- const __revalHeadCtx = { headers: new Headers(), cookies: new Map() };
2129
- const __revalUCtx = _createUnifiedCtx({
2130
- headersContext: __revalHeadCtx,
2131
- executionContext: _getRequestExecutionContext(),
2132
- });
2133
- await _runWithUnifiedCtx(__revalUCtx, async () => {
2134
- _ensureFetchPatch();
2135
- setNavigationContext({ pathname: cleanPathname, searchParams: __revalSearchParams, params: __revalParams });
2136
- const __syntheticReq = new Request(__revalUrl, { method: "GET" });
2137
- const __revalResponse = await __revalHandlerFn(__syntheticReq, { params: __revalParams });
2138
- const __regenDynamic = consumeDynamicUsage();
2139
- setNavigationContext(null);
2140
- if (__regenDynamic) {
2141
- __isrDebug?.("route regen skipped (dynamic usage)", cleanPathname);
2142
- return;
2143
- }
2144
- const __freshBody = await __revalResponse.arrayBuffer();
2145
- const __freshHeaders = {};
2146
- __revalResponse.headers.forEach(function(v, k) {
2147
- if (k !== "x-vinext-cache" && k !== "cache-control") __freshHeaders[k] = v;
2148
- });
2149
- const __routeTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2150
- await __isrSet(__routeKey, { kind: "APP_ROUTE", body: __freshBody, status: __revalResponse.status, headers: __freshHeaders }, __revalSecs, __routeTags);
2151
- __isrDebug?.("route regen complete", __routeKey);
2152
- });
1908
+ },
1909
+ consumeDynamicUsage,
1910
+ getCollectedFetchTags,
1911
+ handlerFn,
1912
+ i18n: __i18nConfig,
1913
+ isAutoHead,
1914
+ isrDebug: __isrDebug,
1915
+ isrGet: __isrGet,
1916
+ isrRouteKey: __isrRouteKey,
1917
+ isrSet: __isrSet,
1918
+ markDynamicUsage,
1919
+ middlewareContext: _mwCtx,
1920
+ params,
1921
+ requestUrl: request.url,
1922
+ revalidateSearchParams: url.searchParams,
1923
+ revalidateSeconds,
1924
+ routePattern: route.pattern,
1925
+ runInRevalidationContext: async function(renderFn) {
1926
+ const __revalHeadCtx = { headers: new Headers(), cookies: new Map() };
1927
+ const __revalUCtx = _createUnifiedCtx({
1928
+ headersContext: __revalHeadCtx,
1929
+ executionContext: _getRequestExecutionContext(),
2153
1930
  });
2154
- __isrDebug?.("STALE (route)", cleanPathname);
2155
- setHeadersContext(null);
2156
- setNavigationContext(null);
2157
- const __staleHeaders = Object.assign({}, __sv.headers || {});
2158
- __staleHeaders["X-Vinext-Cache"] = "STALE";
2159
- __staleHeaders["Cache-Control"] = "s-maxage=0, stale-while-revalidate";
2160
- if (isAutoHead) {
2161
- return attachRouteHandlerMiddlewareContext(new Response(null, { status: __sv.status, headers: __staleHeaders }));
2162
- }
2163
- return attachRouteHandlerMiddlewareContext(new Response(__sv.body, { status: __sv.status, headers: __staleHeaders }));
2164
- }
2165
- } catch (__routeCacheErr) {
2166
- // Cache read failure — fall through to normal handler execution
2167
- console.error("[vinext] ISR route cache read error:", __routeCacheErr);
1931
+ await _runWithUnifiedCtx(__revalUCtx, async () => {
1932
+ _ensureFetchPatch();
1933
+ await renderFn();
1934
+ });
1935
+ },
1936
+ scheduleBackgroundRegeneration: __triggerBackgroundRegeneration,
1937
+ setNavigationContext,
1938
+ });
1939
+ if (__cachedRouteResponse) {
1940
+ return __cachedRouteResponse;
2168
1941
  }
2169
1942
  }
2170
1943
 
2171
1944
  if (typeof handlerFn === "function") {
2172
- const previousHeadersPhase = setHeadersAccessPhase("route-handler");
2173
- try {
2174
- const response = await handlerFn(request, { params });
2175
- const dynamicUsedInHandler = consumeDynamicUsage();
2176
- const handlerSetCacheControl = response.headers.has("cache-control");
2177
-
2178
- // Apply Cache-Control from route segment config (export const revalidate = N).
2179
- // Runtime request APIs like headers() / cookies() make GET handlers dynamic,
2180
- // so only attach ISR headers when the handler stayed static.
2181
- if (
2182
- revalidateSeconds !== null &&
2183
- !dynamicUsedInHandler &&
2184
- (method === "GET" || isAutoHead) &&
2185
- !handlerSetCacheControl
2186
- ) {
2187
- response.headers.set("cache-control", "s-maxage=" + revalidateSeconds + ", stale-while-revalidate");
2188
- }
2189
-
2190
- // ISR cache write for route handlers (production, MISS).
2191
- // Store the raw handler response before cookie/middleware transforms
2192
- // (those are request-specific and shouldn't be cached).
2193
- if (
2194
- process.env.NODE_ENV === "production" &&
2195
- revalidateSeconds !== null &&
2196
- handler.dynamic !== "force-dynamic" &&
2197
- !dynamicUsedInHandler &&
2198
- (method === "GET" || isAutoHead) &&
2199
- !handlerSetCacheControl
2200
- ) {
2201
- response.headers.set("X-Vinext-Cache", "MISS");
2202
- const __routeClone = response.clone();
2203
- const __routeKey = __isrRouteKey(cleanPathname);
2204
- const __revalSecs = revalidateSeconds;
2205
- const __routeTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2206
- const __routeWritePromise = (async () => {
2207
- try {
2208
- const __buf = await __routeClone.arrayBuffer();
2209
- const __hdrs = {};
2210
- __routeClone.headers.forEach(function(v, k) {
2211
- if (k !== "x-vinext-cache" && k !== "cache-control") __hdrs[k] = v;
2212
- });
2213
- await __isrSet(__routeKey, { kind: "APP_ROUTE", body: __buf, status: __routeClone.status, headers: __hdrs }, __revalSecs, __routeTags);
2214
- __isrDebug?.("route cache written", __routeKey);
2215
- } catch (__cacheErr) {
2216
- console.error("[vinext] ISR route cache write error:", __cacheErr);
2217
- }
2218
- })();
2219
- _getRequestExecutionContext()?.waitUntil(__routeWritePromise);
2220
- }
2221
-
2222
- // Collect any Set-Cookie headers from cookies().set()/delete() calls
2223
- const pendingCookies = getAndClearPendingCookies();
2224
- const draftCookie = getDraftModeCookieHeader();
2225
- setHeadersContext(null);
2226
- setNavigationContext(null);
2227
-
2228
- // If we have pending cookies, create a new response with them attached
2229
- if (pendingCookies.length > 0 || draftCookie) {
2230
- const newHeaders = new Headers(response.headers);
2231
- for (const cookie of pendingCookies) {
2232
- newHeaders.append("Set-Cookie", cookie);
2233
- }
2234
- if (draftCookie) newHeaders.append("Set-Cookie", draftCookie);
2235
-
2236
- if (isAutoHead) {
2237
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2238
- status: response.status,
2239
- statusText: response.statusText,
2240
- headers: newHeaders,
2241
- }));
2242
- }
2243
- return attachRouteHandlerMiddlewareContext(new Response(response.body, {
2244
- status: response.status,
2245
- statusText: response.statusText,
2246
- headers: newHeaders,
2247
- }));
2248
- }
2249
-
2250
- if (isAutoHead) {
2251
- // Strip body for auto-HEAD, preserve headers and status
2252
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2253
- status: response.status,
2254
- statusText: response.statusText,
2255
- headers: response.headers,
2256
- }));
2257
- }
2258
- return attachRouteHandlerMiddlewareContext(response);
2259
- } catch (err) {
2260
- getAndClearPendingCookies(); // Clear any pending cookies on error
2261
- // Catch redirect() / notFound() thrown from route handlers
2262
- if (err && typeof err === "object" && "digest" in err) {
2263
- const digest = String(err.digest);
2264
- if (digest.startsWith("NEXT_REDIRECT;")) {
2265
- const parts = digest.split(";");
2266
- const redirectUrl = decodeURIComponent(parts[2]);
2267
- const statusCode = parts[3] ? parseInt(parts[3], 10) : 307;
2268
- setHeadersContext(null);
2269
- setNavigationContext(null);
2270
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2271
- status: statusCode,
2272
- headers: { Location: new URL(redirectUrl, request.url).toString() },
2273
- }));
2274
- }
2275
- if (digest === "NEXT_NOT_FOUND" || digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")) {
2276
- const statusCode = digest === "NEXT_NOT_FOUND" ? 404 : parseInt(digest.split(";")[1], 10);
2277
- setHeadersContext(null);
2278
- setNavigationContext(null);
2279
- return attachRouteHandlerMiddlewareContext(new Response(null, { status: statusCode }));
2280
- }
2281
- }
2282
- setHeadersContext(null);
2283
- setNavigationContext(null);
2284
- console.error("[vinext] Route handler error:", err);
2285
- _reportRequestError(
2286
- err instanceof Error ? err : new Error(String(err)),
2287
- { path: cleanPathname, method: request.method, headers: Object.fromEntries(request.headers.entries()) },
2288
- { routerKind: "App Router", routePath: route.pattern, routeType: "route" },
2289
- );
2290
- return attachRouteHandlerMiddlewareContext(new Response(null, { status: 500 }));
2291
- } finally {
2292
- setHeadersAccessPhase(previousHeadersPhase);
2293
- }
1945
+ return __executeAppRouteHandler({
1946
+ basePath: __basePath,
1947
+ buildPageCacheTags: __pageCacheTags,
1948
+ cleanPathname,
1949
+ clearRequestContext: function() {
1950
+ setHeadersContext(null);
1951
+ setNavigationContext(null);
1952
+ },
1953
+ consumeDynamicUsage,
1954
+ executionContext: _getRequestExecutionContext(),
1955
+ getAndClearPendingCookies,
1956
+ getCollectedFetchTags,
1957
+ getDraftModeCookieHeader,
1958
+ handler,
1959
+ handlerFn,
1960
+ i18n: __i18nConfig,
1961
+ isAutoHead,
1962
+ isProduction: process.env.NODE_ENV === "production",
1963
+ isrDebug: __isrDebug,
1964
+ isrRouteKey: __isrRouteKey,
1965
+ isrSet: __isrSet,
1966
+ markDynamicUsage,
1967
+ method,
1968
+ middlewareContext: _mwCtx,
1969
+ params,
1970
+ reportRequestError: _reportRequestError,
1971
+ request,
1972
+ revalidateSeconds,
1973
+ routePattern: route.pattern,
1974
+ setHeadersAccessPhase,
1975
+ });
2294
1976
  }
2295
1977
  setHeadersContext(null);
2296
1978
  setNavigationContext(null);
2297
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2298
- status: 405,
2299
- }));
1979
+ return __applyRouteHandlerMiddlewareContext(
1980
+ new Response(null, {
1981
+ status: 405,
1982
+ }),
1983
+ _mwCtx,
1984
+ );
2300
1985
  }
2301
1986
 
2302
1987
  // Build the component tree: layouts wrapping the page
@@ -2366,776 +2051,270 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2366
2051
  !isForceDynamic &&
2367
2052
  revalidateSeconds !== null && revalidateSeconds > 0 && revalidateSeconds !== Infinity
2368
2053
  ) {
2369
- const __isrKey = isRscRequest ? __isrRscKey(cleanPathname) : __isrHtmlKey(cleanPathname);
2370
- try {
2371
- const __cached = await __isrGet(__isrKey);
2372
- if (__cached && !__cached.isStale && __cached.value.value && __cached.value.value.kind === "APP_PAGE") {
2373
- const __cachedValue = __cached.value.value;
2374
- const __hasRsc = !!__cachedValue.rscData;
2375
- const __hasHtml = typeof __cachedValue.html === "string" && __cachedValue.html.length > 0;
2376
- if (isRscRequest && __hasRsc) {
2377
- __isrDebug?.("HIT (RSC)", cleanPathname);
2378
- setHeadersContext(null);
2379
- setNavigationContext(null);
2380
- return new Response(__cachedValue.rscData, {
2381
- status: __cachedValue.status || 200,
2382
- headers: {
2383
- "Content-Type": "text/x-component; charset=utf-8",
2384
- "Cache-Control": "s-maxage=" + revalidateSeconds + ", stale-while-revalidate",
2385
- "Vary": "RSC, Accept",
2386
- "X-Vinext-Cache": "HIT",
2387
- },
2388
- });
2389
- }
2390
- if (!isRscRequest && __hasHtml) {
2391
- __isrDebug?.("HIT (HTML)", cleanPathname);
2392
- setHeadersContext(null);
2393
- setNavigationContext(null);
2394
- return new Response(__cachedValue.html, {
2395
- status: __cachedValue.status || 200,
2396
- headers: {
2397
- "Content-Type": "text/html; charset=utf-8",
2398
- "Cache-Control": "s-maxage=" + revalidateSeconds + ", stale-while-revalidate",
2399
- "Vary": "RSC, Accept",
2400
- "X-Vinext-Cache": "HIT",
2401
- },
2402
- });
2403
- }
2404
- __isrDebug?.("MISS (empty cached entry)", cleanPathname);
2405
- }
2406
- if (__cached && __cached.isStale && __cached.value.value && __cached.value.value.kind === "APP_PAGE") {
2407
- // Stale cache hit — serve stale immediately, trigger background regeneration.
2408
- // Regen writes both keys independently so neither path blocks on the other.
2409
- const __staleValue = __cached.value.value;
2410
- const __staleStatus = __staleValue.status || 200;
2411
- const __revalSecs = revalidateSeconds;
2412
- __triggerBackgroundRegeneration(cleanPathname, async function() {
2413
- // Re-render the page to produce fresh HTML + RSC data for the cache
2414
- // Use an empty headers context for background regeneration — not the original
2415
- // user request — to prevent user-specific cookies/auth headers from leaking
2416
- // into content that is cached and served to all subsequent users.
2417
- const __revalHeadCtx = { headers: new Headers(), cookies: new Map() };
2418
- const __revalUCtx = _createUnifiedCtx({
2419
- headersContext: __revalHeadCtx,
2420
- executionContext: _getRequestExecutionContext(),
2421
- });
2422
- const __revalResult = await _runWithUnifiedCtx(__revalUCtx, async () => {
2423
- _ensureFetchPatch();
2424
- setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params });
2425
- const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams());
2426
- const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern);
2427
- const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError });
2428
- // Tee RSC stream: one for SSR, one to capture rscData
2429
- const [__revalRscForSsr, __revalRscForCapture] = __revalRscStream.tee();
2430
- // Capture rscData bytes in parallel with SSR
2431
- const __rscDataPromise = (async () => {
2432
- const __rscReader = __revalRscForCapture.getReader();
2433
- const __rscChunks = [];
2434
- let __rscTotal = 0;
2435
- for (;;) {
2436
- const { done, value } = await __rscReader.read();
2437
- if (done) break;
2438
- __rscChunks.push(value);
2439
- __rscTotal += value.byteLength;
2440
- }
2441
- const __rscBuf = new Uint8Array(__rscTotal);
2442
- let __rscOff = 0;
2443
- for (const c of __rscChunks) { __rscBuf.set(c, __rscOff); __rscOff += c.byteLength; }
2444
- return __rscBuf.buffer;
2445
- })();
2446
- const __revalFontData = { links: _getSSRFontLinks(), styles: _getSSRFontStyles(), preloads: _getSSRFontPreloads() };
2447
- const __revalSsrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
2448
- const __revalHtmlStream = await __revalSsrEntry.handleSsr(__revalRscForSsr, _getNavigationContext(), __revalFontData);
2449
- setHeadersContext(null);
2450
- setNavigationContext(null);
2451
- // Collect the full HTML string from the stream
2452
- const __revalReader = __revalHtmlStream.getReader();
2453
- const __revalDecoder = new TextDecoder();
2454
- const __revalChunks = [];
2455
- for (;;) {
2456
- const { done, value } = await __revalReader.read();
2457
- if (done) break;
2458
- __revalChunks.push(__revalDecoder.decode(value, { stream: true }));
2459
- }
2460
- __revalChunks.push(__revalDecoder.decode());
2461
- const __freshHtml = __revalChunks.join("");
2462
- const __freshRscData = await __rscDataPromise;
2463
- const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2464
- return { html: __freshHtml, rscData: __freshRscData, tags: __pageTags };
2465
- });
2466
- // Write HTML and RSC to their own keys independently — no races
2467
- await Promise.all([
2468
- __isrSet(__isrHtmlKey(cleanPathname), { kind: "APP_PAGE", html: __revalResult.html, rscData: undefined, headers: undefined, postponed: undefined, status: 200 }, __revalSecs, __revalResult.tags),
2469
- __isrSet(__isrRscKey(cleanPathname), { kind: "APP_PAGE", html: "", rscData: __revalResult.rscData, headers: undefined, postponed: undefined, status: 200 }, __revalSecs, __revalResult.tags),
2470
- ]);
2471
- __isrDebug?.("regen complete", cleanPathname);
2054
+ const __cachedPageResponse = await __readAppPageCacheResponse({
2055
+ cleanPathname,
2056
+ clearRequestContext: function() {
2057
+ setHeadersContext(null);
2058
+ setNavigationContext(null);
2059
+ },
2060
+ isRscRequest,
2061
+ isrDebug: __isrDebug,
2062
+ isrGet: __isrGet,
2063
+ isrHtmlKey: __isrHtmlKey,
2064
+ isrRscKey: __isrRscKey,
2065
+ isrSet: __isrSet,
2066
+ revalidateSeconds,
2067
+ renderFreshPageForCache: async function() {
2068
+ // Re-render the page to produce fresh HTML + RSC data for the cache
2069
+ // Use an empty headers context for background regeneration — not the original
2070
+ // user request — to prevent user-specific cookies/auth headers from leaking
2071
+ // into content that is cached and served to all subsequent users.
2072
+ const __revalHeadCtx = { headers: new Headers(), cookies: new Map() };
2073
+ const __revalUCtx = _createUnifiedCtx({
2074
+ headersContext: __revalHeadCtx,
2075
+ executionContext: _getRequestExecutionContext(),
2472
2076
  });
2473
- if (isRscRequest && __staleValue.rscData) {
2474
- __isrDebug?.("STALE (RSC)", cleanPathname);
2077
+ return _runWithUnifiedCtx(__revalUCtx, async () => {
2078
+ _ensureFetchPatch();
2079
+ setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params });
2080
+ const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams());
2081
+ const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern);
2082
+ const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError });
2083
+ const __revalRscCapture = __teeAppPageRscStreamForCapture(__revalRscStream, true);
2084
+ const __revalFontData = { links: _getSSRFontLinks(), styles: _getSSRFontStyles(), preloads: _getSSRFontPreloads() };
2085
+ const __revalSsrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
2086
+ const __revalHtmlStream = await __revalSsrEntry.handleSsr(
2087
+ __revalRscCapture.responseStream,
2088
+ _getNavigationContext(),
2089
+ __revalFontData,
2090
+ );
2475
2091
  setHeadersContext(null);
2476
2092
  setNavigationContext(null);
2477
- return new Response(__staleValue.rscData, {
2478
- status: __staleStatus,
2479
- headers: {
2480
- "Content-Type": "text/x-component; charset=utf-8",
2481
- "Cache-Control": "s-maxage=0, stale-while-revalidate",
2482
- "Vary": "RSC, Accept",
2483
- "X-Vinext-Cache": "STALE",
2484
- },
2485
- });
2486
- }
2487
- if (!isRscRequest && typeof __staleValue.html === "string" && __staleValue.html.length > 0) {
2488
- __isrDebug?.("STALE (HTML)", cleanPathname);
2489
- setHeadersContext(null);
2490
- setNavigationContext(null);
2491
- return new Response(__staleValue.html, {
2492
- status: __staleStatus,
2493
- headers: {
2494
- "Content-Type": "text/html; charset=utf-8",
2495
- "Cache-Control": "s-maxage=0, stale-while-revalidate",
2496
- "Vary": "RSC, Accept",
2497
- "X-Vinext-Cache": "STALE",
2498
- },
2499
- });
2500
- }
2501
- // Stale entry exists but is empty for this request type — fall through to render
2502
- __isrDebug?.("STALE MISS (empty stale entry)", cleanPathname);
2503
- }
2504
- if (!__cached) {
2505
- __isrDebug?.("MISS (no cache entry)", cleanPathname);
2506
- }
2507
- } catch (__isrReadErr) {
2508
- // Cache read failure — fall through to normal rendering
2509
- console.error("[vinext] ISR cache read error:", __isrReadErr);
2093
+ const __freshHtml = await __readAppPageTextStream(__revalHtmlStream);
2094
+ const __freshRscData = await __revalRscCapture.capturedRscDataPromise;
2095
+ const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2096
+ return { html: __freshHtml, rscData: __freshRscData, tags: __pageTags };
2097
+ });
2098
+ },
2099
+ scheduleBackgroundRegeneration: __triggerBackgroundRegeneration,
2100
+ });
2101
+ if (__cachedPageResponse) {
2102
+ return __cachedPageResponse;
2510
2103
  }
2511
2104
  }
2512
2105
 
2513
2106
  // dynamicParams = false: only params from generateStaticParams are allowed.
2514
2107
  // This runs AFTER the ISR cache read so that a cache hit skips this work entirely.
2515
- if (dynamicParamsConfig === false && route.isDynamic && typeof route.page?.generateStaticParams === "function") {
2516
- try {
2517
- // Pass parent params to generateStaticParams (Next.js top-down params passing).
2518
- // Parent params = all matched params that DON'T belong to the leaf page's own dynamic segments.
2519
- // We pass the full matched params; the function uses only what it needs.
2520
- const staticParams = await route.page.generateStaticParams({ params });
2521
- if (Array.isArray(staticParams)) {
2522
- const paramKeys = Object.keys(params);
2523
- const isAllowed = staticParams.some(sp =>
2524
- paramKeys.every(key => {
2525
- const val = params[key];
2526
- const staticVal = sp[key];
2527
- // Allow parent params to not be in the returned set (they're inherited)
2528
- if (staticVal === undefined) return true;
2529
- if (Array.isArray(val)) return JSON.stringify(val) === JSON.stringify(staticVal);
2530
- return String(val) === String(staticVal);
2531
- })
2532
- );
2533
- if (!isAllowed) {
2534
- setHeadersContext(null);
2535
- setNavigationContext(null);
2536
- return new Response("Not Found", { status: 404 });
2537
- }
2538
- }
2539
- } catch (err) {
2108
+ const __dynamicParamsResponse = await __validateAppPageDynamicParams({
2109
+ clearRequestContext() {
2110
+ setHeadersContext(null);
2111
+ setNavigationContext(null);
2112
+ },
2113
+ enforceStaticParamsOnly: dynamicParamsConfig === false,
2114
+ generateStaticParams: route.page?.generateStaticParams,
2115
+ isDynamicRoute: route.isDynamic,
2116
+ logGenerateStaticParamsError(err) {
2540
2117
  console.error("[vinext] generateStaticParams error:", err);
2541
- }
2118
+ },
2119
+ params,
2120
+ });
2121
+ if (__dynamicParamsResponse) {
2122
+ return __dynamicParamsResponse;
2542
2123
  }
2543
2124
 
2544
2125
  // Check for intercepting routes on RSC requests (client-side navigation).
2545
2126
  // If the target URL matches an intercepting route in a parallel slot,
2546
2127
  // render the source route with the intercepting page in the slot.
2547
- let interceptOpts = undefined;
2548
- if (isRscRequest) {
2549
- const intercept = findIntercept(cleanPathname);
2550
- if (intercept) {
2551
- const sourceRoute = routes[intercept.sourceRouteIndex];
2552
- if (sourceRoute && sourceRoute !== route) {
2553
- // Render the source route (e.g. /feed) with the intercepting page in the slot
2554
- const sourceMatch = matchRoute(sourceRoute.pattern);
2555
- const sourceParams = sourceMatch ? sourceMatch.params : {};
2556
- setNavigationContext({
2557
- pathname: cleanPathname,
2558
- searchParams: url.searchParams,
2559
- params: intercept.matchedParams,
2560
- });
2561
- const interceptElement = await buildPageElement(sourceRoute, sourceParams, {
2562
- interceptSlot: intercept.slotName,
2563
- interceptPage: intercept.page,
2564
- interceptParams: intercept.matchedParams,
2565
- }, url.searchParams);
2566
- const interceptOnError = createRscOnErrorHandler(
2567
- request,
2568
- cleanPathname,
2569
- sourceRoute.pattern,
2570
- );
2571
- const interceptStream = renderToReadableStream(interceptElement, { onError: interceptOnError });
2572
- // Do NOT clear headers/navigation context here the RSC stream is consumed lazily
2573
- // by the client, and async server components that run during consumption need the
2574
- // context to still be live. The AsyncLocalStorage scope from runWithRequestContext
2575
- // handles cleanup naturally when all async continuations complete.
2576
- return new Response(interceptStream, {
2577
- headers: { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" },
2578
- });
2579
- }
2580
- // If sourceRoute === route, apply intercept opts to the normal render
2581
- interceptOpts = {
2128
+ const __interceptResult = await __resolveAppPageIntercept({
2129
+ buildPageElement,
2130
+ cleanPathname,
2131
+ currentRoute: route,
2132
+ findIntercept,
2133
+ getRoutePattern(sourceRoute) {
2134
+ return sourceRoute.pattern;
2135
+ },
2136
+ getSourceRoute(sourceRouteIndex) {
2137
+ return routes[sourceRouteIndex];
2138
+ },
2139
+ isRscRequest,
2140
+ matchSourceRouteParams(pattern) {
2141
+ return matchRoute(pattern)?.params ?? {};
2142
+ },
2143
+ renderInterceptResponse(sourceRoute, interceptElement) {
2144
+ const interceptOnError = createRscOnErrorHandler(
2145
+ request,
2146
+ cleanPathname,
2147
+ sourceRoute.pattern,
2148
+ );
2149
+ const interceptStream = renderToReadableStream(interceptElement, {
2150
+ onError: interceptOnError,
2151
+ });
2152
+ // Do NOT clear headers/navigation context here — the RSC stream is consumed lazily
2153
+ // by the client, and async server components that run during consumption need the
2154
+ // context to still be live. The AsyncLocalStorage scope from runWithRequestContext
2155
+ // handles cleanup naturally when all async continuations complete.
2156
+ return new Response(interceptStream, {
2157
+ headers: { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" },
2158
+ });
2159
+ },
2160
+ searchParams: url.searchParams,
2161
+ setNavigationContext,
2162
+ toInterceptOpts(intercept) {
2163
+ return {
2582
2164
  interceptSlot: intercept.slotName,
2583
2165
  interceptPage: intercept.page,
2584
2166
  interceptParams: intercept.matchedParams,
2585
2167
  };
2586
- }
2168
+ },
2169
+ });
2170
+ if (__interceptResult.response) {
2171
+ return __interceptResult.response;
2587
2172
  }
2173
+ const interceptOpts = __interceptResult.interceptOpts;
2588
2174
 
2589
- let element;
2590
- try {
2591
- element = await buildPageElement(route, params, interceptOpts, url.searchParams);
2592
- } catch (buildErr) {
2593
- // Check for redirect/notFound/forbidden/unauthorized thrown during metadata resolution or async components
2594
- if (buildErr && typeof buildErr === "object" && "digest" in buildErr) {
2595
- const digest = String(buildErr.digest);
2596
- if (digest.startsWith("NEXT_REDIRECT;")) {
2597
- const parts = digest.split(";");
2598
- const redirectUrl = decodeURIComponent(parts[2]);
2599
- const statusCode = parts[3] ? parseInt(parts[3], 10) : 307;
2600
- setHeadersContext(null);
2601
- setNavigationContext(null);
2602
- return Response.redirect(new URL(redirectUrl, request.url), statusCode);
2603
- }
2604
- if (digest === "NEXT_NOT_FOUND" || digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")) {
2605
- const statusCode = digest === "NEXT_NOT_FOUND" ? 404 : parseInt(digest.split(";")[1], 10);
2606
- const fallbackResp = await renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, request, { matchedParams: params });
2607
- if (fallbackResp) return fallbackResp;
2608
- setHeadersContext(null);
2609
- setNavigationContext(null);
2610
- const statusText = statusCode === 403 ? "Forbidden" : statusCode === 401 ? "Unauthorized" : "Not Found";
2611
- return new Response(statusText, { status: statusCode });
2612
- }
2613
- }
2614
- // Non-special error (e.g. generateMetadata() threw) — render error.tsx if available
2615
- const errorBoundaryResp = await renderErrorBoundaryPage(route, buildErr, isRscRequest, request, params);
2616
- if (errorBoundaryResp) return errorBoundaryResp;
2617
- throw buildErr;
2175
+ const __pageBuildResult = await __buildAppPageElement({
2176
+ buildPageElement() {
2177
+ return buildPageElement(route, params, interceptOpts, url.searchParams);
2178
+ },
2179
+ renderErrorBoundaryPage(buildErr) {
2180
+ return renderErrorBoundaryPage(route, buildErr, isRscRequest, request, params);
2181
+ },
2182
+ renderSpecialError(__buildSpecialError) {
2183
+ return __buildAppPageSpecialErrorResponse({
2184
+ clearRequestContext() {
2185
+ setHeadersContext(null);
2186
+ setNavigationContext(null);
2187
+ },
2188
+ renderFallbackPage(statusCode) {
2189
+ return renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, request, {
2190
+ matchedParams: params,
2191
+ });
2192
+ },
2193
+ requestUrl: request.url,
2194
+ specialError: __buildSpecialError,
2195
+ });
2196
+ },
2197
+ resolveSpecialError: __resolveAppPageSpecialError,
2198
+ });
2199
+ if (__pageBuildResult.response) {
2200
+ return __pageBuildResult.response;
2618
2201
  }
2202
+ const element = __pageBuildResult.element;
2619
2203
 
2620
2204
  // Note: CSS is automatically injected by @vitejs/plugin-rsc's
2621
2205
  // rscCssTransform — no manual loadCss() call needed.
2622
-
2623
- // Helper: check if an error is a redirect/notFound/forbidden/unauthorized thrown by the navigation shim
2624
- async function handleRenderError(err) {
2625
- if (err && typeof err === "object" && "digest" in err) {
2626
- const digest = String(err.digest);
2627
- if (digest.startsWith("NEXT_REDIRECT;")) {
2628
- const parts = digest.split(";");
2629
- const redirectUrl = decodeURIComponent(parts[2]);
2630
- const statusCode = parts[3] ? parseInt(parts[3], 10) : 307;
2631
- setHeadersContext(null);
2632
- setNavigationContext(null);
2633
- return Response.redirect(new URL(redirectUrl, request.url), statusCode);
2634
- }
2635
- if (digest === "NEXT_NOT_FOUND" || digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")) {
2636
- const statusCode = digest === "NEXT_NOT_FOUND" ? 404 : parseInt(digest.split(";")[1], 10);
2637
- const fallbackResp = await renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, request, { matchedParams: params });
2638
- if (fallbackResp) return fallbackResp;
2639
- setHeadersContext(null);
2640
- setNavigationContext(null);
2641
- const statusText = statusCode === 403 ? "Forbidden" : statusCode === 401 ? "Unauthorized" : "Not Found";
2642
- return new Response(statusText, { status: statusCode });
2643
- }
2644
- }
2645
- return null;
2646
- }
2647
-
2648
- // Pre-render layout components to catch notFound()/redirect() thrown from layouts.
2649
- // In Next.js, each layout level has its own NotFoundBoundary. When a layout throws
2650
- // notFound(), the parent layout's boundary catches it and renders the parent's
2651
- // not-found.tsx. Since React Flight doesn't activate client error boundaries during
2652
- // RSC rendering, we catch layout-level throws here and render the appropriate
2653
- // fallback page with only the layouts above the throwing one.
2654
- //
2655
- // IMPORTANT: Layout pre-render runs BEFORE page pre-render. In Next.js, layouts
2656
- // render before their children — if a layout throws notFound(), the page never
2657
- // executes. By checking layouts first, we avoid a bug where the page's notFound()
2658
- // triggers renderHTTPAccessFallbackPage with ALL route layouts, but one of those
2659
- // layouts itself throws notFound() during the fallback rendering (causing a 500).
2660
- if (route.layouts && route.layouts.length > 0) {
2661
- const asyncParams = makeThenableParams(params);
2662
- // Run inside ALS context so the module-level console.error patch suppresses
2663
- // "Invalid hook call" only for this request's probe — concurrent requests
2664
- // each have their own ALS store and are unaffected.
2665
- const _layoutProbeResult = await _suppressHookWarningAls.run(true, async () => {
2666
- for (let li = route.layouts.length - 1; li >= 0; li--) {
2667
- const LayoutComp = route.layouts[li]?.default;
2668
- if (!LayoutComp) continue;
2669
- try {
2670
- const lr = LayoutComp({ params: asyncParams, children: null });
2671
- if (lr && typeof lr === "object" && typeof lr.then === "function") await lr;
2672
- } catch (layoutErr) {
2673
- if (layoutErr && typeof layoutErr === "object" && "digest" in layoutErr) {
2674
- const digest = String(layoutErr.digest);
2675
- if (digest.startsWith("NEXT_REDIRECT;")) {
2676
- const parts = digest.split(";");
2677
- const redirectUrl = decodeURIComponent(parts[2]);
2678
- const statusCode = parts[3] ? parseInt(parts[3], 10) : 307;
2679
- setHeadersContext(null);
2680
- setNavigationContext(null);
2681
- return Response.redirect(new URL(redirectUrl, request.url), statusCode);
2682
- }
2683
- if (digest === "NEXT_NOT_FOUND" || digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")) {
2684
- const statusCode = digest === "NEXT_NOT_FOUND" ? 404 : parseInt(digest.split(";")[1], 10);
2685
- // Find the not-found component from the parent level (the boundary that
2686
- // would catch this in Next.js). Walk up from the throwing layout to find
2687
- // the nearest not-found at a parent layout's directory.
2688
- let parentNotFound = null;
2689
- if (route.notFounds) {
2690
- for (let pi = li - 1; pi >= 0; pi--) {
2691
- if (route.notFounds[pi]?.default) {
2692
- parentNotFound = route.notFounds[pi].default;
2693
- break;
2694
- }
2695
- }
2206
+ const _hasLoadingBoundary = !!(route.loading && route.loading.default);
2207
+ const _asyncLayoutParams = makeThenableParams(params);
2208
+ return __renderAppPageLifecycle({
2209
+ cleanPathname,
2210
+ clearRequestContext() {
2211
+ setHeadersContext(null);
2212
+ setNavigationContext(null);
2213
+ },
2214
+ consumeDynamicUsage,
2215
+ createRscOnErrorHandler(pathname, routePath) {
2216
+ return createRscOnErrorHandler(request, pathname, routePath);
2217
+ },
2218
+ element,
2219
+ getDraftModeCookieHeader,
2220
+ getFontLinks: _getSSRFontLinks,
2221
+ getFontPreloads: _getSSRFontPreloads,
2222
+ getFontStyles: _getSSRFontStyles,
2223
+ getNavigationContext: _getNavigationContext,
2224
+ getPageTags() {
2225
+ return __pageCacheTags(cleanPathname, getCollectedFetchTags());
2226
+ },
2227
+ getRequestCacheLife() {
2228
+ return _consumeRequestScopedCacheLife();
2229
+ },
2230
+ handlerStart: __reqStart,
2231
+ hasLoadingBoundary: _hasLoadingBoundary,
2232
+ isDynamicError,
2233
+ isForceDynamic,
2234
+ isForceStatic,
2235
+ isProduction: process.env.NODE_ENV === "production",
2236
+ isRscRequest,
2237
+ isrDebug: __isrDebug,
2238
+ isrHtmlKey: __isrHtmlKey,
2239
+ isrRscKey: __isrRscKey,
2240
+ isrSet: __isrSet,
2241
+ layoutCount: route.layouts?.length ?? 0,
2242
+ loadSsrHandler() {
2243
+ return import.meta.viteRsc.loadModule("ssr", "index");
2244
+ },
2245
+ middlewareContext: _mwCtx,
2246
+ params,
2247
+ probeLayoutAt(li) {
2248
+ const LayoutComp = route.layouts[li]?.default;
2249
+ if (!LayoutComp) return null;
2250
+ return LayoutComp({ params: _asyncLayoutParams, children: null });
2251
+ },
2252
+ probePage() {
2253
+ return PageComponent({ params });
2254
+ },
2255
+ revalidateSeconds,
2256
+ renderErrorBoundaryResponse(renderErr) {
2257
+ return renderErrorBoundaryPage(route, renderErr, isRscRequest, request, params);
2258
+ },
2259
+ async renderLayoutSpecialError(__layoutSpecialError, li) {
2260
+ return __buildAppPageSpecialErrorResponse({
2261
+ clearRequestContext() {
2262
+ setHeadersContext(null);
2263
+ setNavigationContext(null);
2264
+ },
2265
+ renderFallbackPage(statusCode) {
2266
+ // Find the not-found component from the parent level (the boundary that
2267
+ // would catch this in Next.js). Walk up from the throwing layout to find
2268
+ // the nearest not-found at a parent layout's directory.
2269
+ let parentNotFound = null;
2270
+ if (route.notFounds) {
2271
+ for (let pi = li - 1; pi >= 0; pi--) {
2272
+ if (route.notFounds[pi]?.default) {
2273
+ parentNotFound = route.notFounds[pi].default;
2274
+ break;
2696
2275
  }
2697
- if (!parentNotFound) parentNotFound = ${rootNotFoundVar ? `${rootNotFoundVar}?.default` : "null"};
2698
- // Wrap in only the layouts above the throwing one
2699
- const parentLayouts = route.layouts.slice(0, li);
2700
- const fallbackResp = await renderHTTPAccessFallbackPage(
2701
- route, statusCode, isRscRequest, request,
2702
- { boundaryComponent: parentNotFound, layouts: parentLayouts, matchedParams: params }
2703
- );
2704
- if (fallbackResp) return fallbackResp;
2705
- setHeadersContext(null);
2706
- setNavigationContext(null);
2707
- const statusText = statusCode === 403 ? "Forbidden" : statusCode === 401 ? "Unauthorized" : "Not Found";
2708
- return new Response(statusText, { status: statusCode });
2709
2276
  }
2710
2277
  }
2711
- // Not a special error let it propagate through normal RSC rendering
2712
- }
2713
- }
2714
- return null;
2715
- });
2716
- if (_layoutProbeResult instanceof Response) return _layoutProbeResult;
2717
- }
2718
-
2719
- // Pre-render the page component to catch redirect()/notFound() thrown synchronously.
2720
- // Server Components are just functions — we can call PageComponent directly to detect
2721
- // these special throws before starting the RSC stream.
2722
- //
2723
- // For routes with a loading.tsx Suspense boundary, we skip awaiting async components.
2724
- // The Suspense boundary + rscOnError will handle redirect/notFound thrown during
2725
- // streaming, and blocking here would defeat streaming (the slow component's delay
2726
- // would be hit before the RSC stream even starts).
2727
- //
2728
- // Because this calls the component outside React's render cycle, hooks like use()
2729
- // trigger "Invalid hook call" console.error in dev. The module-level ALS patch
2730
- // suppresses the warning only within this request's execution context.
2731
- const _hasLoadingBoundary = !!(route.loading && route.loading.default);
2732
- const _pageProbeResult = await _suppressHookWarningAls.run(true, async () => {
2733
- try {
2734
- const testResult = PageComponent({ params });
2735
- // If it's a promise (async component), only await if there's no loading boundary.
2736
- // With a loading boundary, the Suspense streaming pipeline handles async resolution
2737
- // and any redirect/notFound errors via rscOnError.
2738
- if (testResult && typeof testResult === "object" && typeof testResult.then === "function") {
2739
- if (!_hasLoadingBoundary) {
2740
- await testResult;
2741
- } else {
2742
- // Suppress unhandled promise rejection — with a loading boundary,
2743
- // redirect/notFound errors are handled by rscOnError during streaming.
2744
- testResult.catch(() => {});
2745
- }
2746
- }
2747
- } catch (preRenderErr) {
2748
- const specialResponse = await handleRenderError(preRenderErr);
2749
- if (specialResponse) return specialResponse;
2750
- // Non-special errors from the pre-render test are expected (e.g. use() hook
2751
- // fails outside React's render cycle, client references can't execute on server).
2752
- // Only redirect/notFound/forbidden/unauthorized are actionable here — other
2753
- // errors will be properly caught during actual RSC/SSR rendering below.
2754
- }
2755
- return null;
2756
- });
2757
- if (_pageProbeResult instanceof Response) return _pageProbeResult;
2758
-
2759
- // Mark end of compile phase: route matching, middleware, tree building are done.
2760
- if (process.env.NODE_ENV !== "production") __compileEnd = performance.now();
2761
-
2762
- // Render to RSC stream.
2763
- // Track non-navigation RSC errors so we can detect when the in-tree global
2764
- // ErrorBoundary catches during SSR (producing double <html>/<body>) and
2765
- // re-render with renderErrorBoundaryPage (which skips layouts for global-error).
2766
- let _rscErrorForRerender = null;
2767
- const _baseOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern);
2768
- const onRenderError = function(error, requestInfo, errorContext) {
2769
- if (!(error && typeof error === "object" && "digest" in error)) {
2770
- _rscErrorForRerender = error;
2771
- }
2772
- return _baseOnError(error, requestInfo, errorContext);
2773
- };
2774
- const rscStream = renderToReadableStream(element, { onError: onRenderError });
2775
-
2776
- // For ISR pages in production: tee the RSC stream immediately after creation so we
2777
- // can capture rscData for BOTH RSC requests (client-side nav/prefetch) and HTML
2778
- // requests. The tee must happen here — before the isRscRequest branch — so both
2779
- // paths can use the captured bytes when writing to the ISR cache.
2780
- // __rscForResponse → sent to the client (RSC response) or to SSR (HTML response)
2781
- // __isrRscDataPromise → resolves to ArrayBuffer of captured RSC wire bytes
2782
- let __rscForResponse = rscStream;
2783
- let __isrRscDataPromise = null;
2784
- if (process.env.NODE_ENV === "production" && revalidateSeconds !== null && revalidateSeconds > 0 && revalidateSeconds !== Infinity && !isForceDynamic) {
2785
- const [__rscA, __rscB] = rscStream.tee();
2786
- __rscForResponse = __rscA;
2787
- __isrRscDataPromise = (async () => {
2788
- const __rscReader = __rscB.getReader();
2789
- const __rscChunks = [];
2790
- let __rscTotal = 0;
2791
- for (;;) {
2792
- const { done, value } = await __rscReader.read();
2793
- if (done) break;
2794
- __rscChunks.push(value);
2795
- __rscTotal += value.byteLength;
2796
- }
2797
- const __rscBuf = new Uint8Array(__rscTotal);
2798
- let __rscOff = 0;
2799
- for (const c of __rscChunks) { __rscBuf.set(c, __rscOff); __rscOff += c.byteLength; }
2800
- return __rscBuf.buffer;
2801
- })();
2802
- }
2803
-
2804
- if (isRscRequest) {
2805
- // Direct RSC stream response (for client-side navigation)
2806
- // NOTE: Do NOT clear headers/navigation context here!
2807
- // The RSC stream is consumed lazily - components render when chunks are read.
2808
- // If we clear context now, headers()/cookies() will fail during rendering.
2809
- // Context will be cleared when the next request starts (via runWithRequestContext).
2810
- const responseHeaders = { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" };
2811
- // Include matched route params so the client can hydrate useParams()
2812
- if (params && Object.keys(params).length > 0) {
2813
- responseHeaders["X-Vinext-Params"] = JSON.stringify(params);
2814
- }
2815
- if (isForceDynamic) {
2816
- responseHeaders["Cache-Control"] = "no-store, must-revalidate";
2817
- } else if ((isForceStatic || isDynamicError) && !revalidateSeconds) {
2818
- responseHeaders["Cache-Control"] = "s-maxage=31536000, stale-while-revalidate";
2819
- responseHeaders["X-Vinext-Cache"] = "STATIC";
2820
- } else if (revalidateSeconds === Infinity) {
2821
- responseHeaders["Cache-Control"] = "s-maxage=31536000, stale-while-revalidate";
2822
- responseHeaders["X-Vinext-Cache"] = "STATIC";
2823
- } else if (revalidateSeconds) {
2824
- responseHeaders["Cache-Control"] = "s-maxage=" + revalidateSeconds + ", stale-while-revalidate";
2825
- }
2826
- // Merge middleware response headers into the RSC response.
2827
- // set-cookie and vary are accumulated to preserve existing values
2828
- // (e.g. "Vary: RSC, Accept" set above); all other keys use plain
2829
- // assignment so middleware headers win over config headers, which
2830
- // the outer handler applies afterward and skips keys already present.
2831
- if (_mwCtx.headers) {
2832
- for (const [key, value] of _mwCtx.headers) {
2833
- const lk = key.toLowerCase();
2834
- if (lk === "set-cookie") {
2835
- const existing = responseHeaders[lk];
2836
- if (Array.isArray(existing)) {
2837
- existing.push(value);
2838
- } else if (existing) {
2839
- responseHeaders[lk] = [existing, value];
2840
- } else {
2841
- responseHeaders[lk] = [value];
2842
- }
2843
- } else if (lk === "vary") {
2844
- // Accumulate Vary values to preserve the existing "RSC, Accept" entry.
2845
- const existing = responseHeaders["Vary"] ?? responseHeaders["vary"];
2846
- if (existing) {
2847
- responseHeaders["Vary"] = existing + ", " + value;
2848
- if (responseHeaders["vary"] !== undefined) delete responseHeaders["vary"];
2849
- } else {
2850
- responseHeaders[key] = value;
2851
- }
2852
- } else {
2853
- responseHeaders[key] = value;
2854
- }
2855
- }
2856
- }
2857
- // Attach internal timing header so the dev server middleware can log it.
2858
- // Format: "handlerStart,compileMs,renderMs"
2859
- // handlerStart - absolute performance.now() when _handleRequest began,
2860
- // used by the logging middleware to compute true compile
2861
- // time as (handlerStart - middlewareReqStart).
2862
- // compileMs - time inside the handler before renderToReadableStream.
2863
- // -1 sentinel means compile time is not measured.
2864
- // renderMs - -1 sentinel for RSC-only (soft-nav) responses, since
2865
- // rendering is handled asynchronously by the client. The
2866
- // logging middleware computes render time as totalMs - compileMs.
2867
- if (process.env.NODE_ENV !== "production") {
2868
- const handlerStart = Math.round(__reqStart);
2869
- const compileMs = __compileEnd !== undefined ? Math.round(__compileEnd - __reqStart) : -1;
2870
- responseHeaders["x-vinext-timing"] = handlerStart + "," + compileMs + ",-1";
2871
- }
2872
- // For ISR-eligible RSC requests in production: write rscData to its own key.
2873
- // HTML is stored under a separate key (written by the HTML path below) so
2874
- // these writes never race or clobber each other.
2875
- if (process.env.NODE_ENV === "production" && __isrRscDataPromise) {
2876
- responseHeaders["X-Vinext-Cache"] = "MISS";
2877
- const __isrKeyRsc = __isrRscKey(cleanPathname);
2878
- const __revalSecsRsc = revalidateSeconds;
2879
- const __rscWritePromise = (async () => {
2880
- try {
2881
- const __rscDataForCache = await __isrRscDataPromise;
2882
- const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2883
- await __isrSet(__isrKeyRsc, { kind: "APP_PAGE", html: "", rscData: __rscDataForCache, headers: undefined, postponed: undefined, status: 200 }, __revalSecsRsc, __pageTags);
2884
- __isrDebug?.("RSC cache written", __isrKeyRsc);
2885
- } catch (__rscWriteErr) {
2886
- console.error("[vinext] ISR RSC cache write error:", __rscWriteErr);
2887
- }
2888
- })();
2889
- _getRequestExecutionContext()?.waitUntil(__rscWritePromise);
2890
- }
2891
- return new Response(__rscForResponse, { status: _mwCtx.status || 200, headers: responseHeaders });
2892
- }
2893
-
2894
- // Collect font data from RSC environment before passing to SSR
2895
- // (Fonts are loaded during RSC rendering when layout.tsx calls Geist() etc.)
2896
- const fontData = {
2897
- links: _getSSRFontLinks(),
2898
- styles: _getSSRFontStyles(),
2899
- preloads: _getSSRFontPreloads(),
2900
- };
2901
-
2902
- // Build HTTP Link header for font preloading.
2903
- // This lets the browser (and CDN) start fetching font files before parsing HTML,
2904
- // eliminating the CSS → woff2 download waterfall.
2905
- const fontPreloads = fontData.preloads || [];
2906
- const fontLinkHeaderParts = [];
2907
- for (const preload of fontPreloads) {
2908
- fontLinkHeaderParts.push("<" + preload.href + ">; rel=preload; as=font; type=" + preload.type + "; crossorigin");
2909
- }
2910
- const fontLinkHeader = fontLinkHeaderParts.length > 0 ? fontLinkHeaderParts.join(", ") : "";
2911
-
2912
- // __rscForResponse was already teed above (before isRscRequest) for ISR pages in
2913
- // production. For non-ISR or dev, __rscForResponse === rscStream (no tee).
2914
- // __isrRscDataPromise resolves to rscData bytes used by the RSC write path above;
2915
- // the HTML write path below uses its own separate key and does not need rscData.
2916
-
2917
- // Delegate to SSR environment for HTML rendering
2918
- let htmlStream;
2919
- try {
2920
- const ssrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
2921
- htmlStream = await ssrEntry.handleSsr(__rscForResponse, _getNavigationContext(), fontData);
2922
- // Shell render complete; Suspense boundaries stream asynchronously
2923
- if (process.env.NODE_ENV !== "production") __renderEnd = performance.now();
2924
- } catch (ssrErr) {
2925
- const specialResponse = await handleRenderError(ssrErr);
2926
- if (specialResponse) return specialResponse;
2927
- // Non-special error during SSR — render error.tsx if available
2928
- const errorBoundaryResp = await renderErrorBoundaryPage(route, ssrErr, isRscRequest, request, params);
2929
- if (errorBoundaryResp) return errorBoundaryResp;
2930
- throw ssrErr;
2931
- }
2932
-
2933
- // If an RSC error was caught by the in-tree global ErrorBoundary during SSR,
2934
- // the HTML output has double <html>/<body> (root layout + global-error.tsx).
2935
- // Discard it and re-render using renderErrorBoundaryPage which skips layouts
2936
- // when the error falls through to global-error.tsx.
2937
- ${globalErrorVar ? `
2938
- if (_rscErrorForRerender && !isRscRequest) {
2939
- const _hasLocalBoundary = !!(route?.error?.default) || !!(route?.errors && route.errors.some(function(e) { return e?.default; }));
2940
- if (!_hasLocalBoundary) {
2941
- const cleanResp = await renderErrorBoundaryPage(route, _rscErrorForRerender, false, request, params);
2942
- if (cleanResp) return cleanResp;
2943
- }
2944
- }
2945
- ` : ""}
2946
-
2947
- // Check for draftMode Set-Cookie header (from draftMode().enable()/disable())
2948
- const draftCookie = getDraftModeCookieHeader();
2949
-
2950
- setHeadersContext(null);
2951
- setNavigationContext(null);
2952
-
2953
- // Helper to attach draftMode cookie, middleware headers, font Link header, and rewrite status to a response
2954
- function attachMiddlewareContext(response) {
2955
- if (draftCookie) {
2956
- response.headers.append("Set-Cookie", draftCookie);
2957
- }
2958
- // Set HTTP Link header for font preloading
2959
- if (fontLinkHeader) {
2960
- response.headers.set("Link", fontLinkHeader);
2961
- }
2962
- // Merge middleware response headers into the final response.
2963
- // The response is freshly constructed above (new Response(htmlStream, {...})),
2964
- // so set() and append() are equivalent — there are no same-key conflicts yet.
2965
- // Precedence over config headers is handled by the outer handler, which
2966
- // skips config keys that middleware already placed on the response.
2967
- if (_mwCtx.headers) {
2968
- for (const [key, value] of _mwCtx.headers) {
2969
- response.headers.append(key, value);
2970
- }
2971
- }
2972
- // Attach internal timing header so the dev server middleware can log it.
2973
- // Format: "handlerStart,compileMs,renderMs"
2974
- // handlerStart - absolute performance.now() when _handleRequest began,
2975
- // used by the logging middleware to compute true compile
2976
- // time as (handlerStart - middlewareReqStart).
2977
- // compileMs - time inside the handler before renderToReadableStream.
2978
- // renderMs - time from renderToReadableStream to handleSsr completion,
2979
- // or -1 sentinel if not measured (falls back to totalMs - compileMs).
2980
- if (process.env.NODE_ENV !== "production") {
2981
- const handlerStart = Math.round(__reqStart);
2982
- const compileMs = __compileEnd !== undefined ? Math.round(__compileEnd - __reqStart) : -1;
2983
- const renderMs = __renderEnd !== undefined && __compileEnd !== undefined
2984
- ? Math.round(__renderEnd - __compileEnd)
2985
- : -1;
2986
- response.headers.set("x-vinext-timing", handlerStart + "," + compileMs + "," + renderMs);
2987
- }
2988
- // Apply custom status code from middleware rewrite
2989
- if (_mwCtx.status) {
2990
- return new Response(response.body, {
2991
- status: _mwCtx.status,
2992
- headers: response.headers,
2278
+ if (!parentNotFound) parentNotFound = ${rootNotFoundVar ? `${rootNotFoundVar}?.default` : "null"};
2279
+ const parentLayouts = route.layouts.slice(0, li);
2280
+ return renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, request, {
2281
+ boundaryComponent: parentNotFound,
2282
+ layouts: parentLayouts,
2283
+ matchedParams: params,
2284
+ });
2285
+ },
2286
+ requestUrl: request.url,
2287
+ specialError: __layoutSpecialError,
2993
2288
  });
2994
- }
2995
- return response;
2996
- }
2997
-
2998
- // Check if any component called connection(), cookies(), headers(), or noStore()
2999
- // during rendering. If so, treat as dynamic (skip ISR, set no-store).
3000
- const dynamicUsedDuringRender = consumeDynamicUsage();
3001
-
3002
- // Check if cacheLife() was called during rendering (e.g., page with file-level "use cache").
3003
- // If so, use its revalidation period for the Cache-Control header.
3004
- const requestCacheLife = _consumeRequestScopedCacheLife();
3005
- if (requestCacheLife && requestCacheLife.revalidate !== undefined && revalidateSeconds === null) {
3006
- revalidateSeconds = requestCacheLife.revalidate;
3007
- }
3008
-
3009
- // force-dynamic: always return no-store (highest priority)
3010
- if (isForceDynamic) {
3011
- return attachMiddlewareContext(new Response(htmlStream, {
3012
- headers: {
3013
- "Content-Type": "text/html; charset=utf-8",
3014
- "Cache-Control": "no-store, must-revalidate",
3015
- "Vary": "RSC, Accept",
3016
- },
3017
- }));
3018
- }
3019
-
3020
- // force-static / error: treat as static regardless of dynamic usage.
3021
- // force-static intentionally provides empty headers/cookies context so
3022
- // dynamic APIs return safe defaults; we ignore the dynamic usage signal.
3023
- // dynamic='error' should have already thrown via the request API accessError
3024
- // trap if user code touched a dynamic API, so reaching here means rendering succeeded.
3025
- if ((isForceStatic || isDynamicError) && (revalidateSeconds === null || revalidateSeconds === 0)) {
3026
- return attachMiddlewareContext(new Response(htmlStream, {
3027
- headers: {
3028
- "Content-Type": "text/html; charset=utf-8",
3029
- "Cache-Control": "s-maxage=31536000, stale-while-revalidate",
3030
- "X-Vinext-Cache": "STATIC",
3031
- "Vary": "RSC, Accept",
3032
- },
3033
- }));
3034
- }
3035
-
3036
- // auto mode: dynamic API usage (headers(), cookies(), connection(), noStore(),
3037
- // searchParams access) opts the page into dynamic rendering with no-store.
3038
- if (dynamicUsedDuringRender) {
3039
- return attachMiddlewareContext(new Response(htmlStream, {
3040
- headers: {
3041
- "Content-Type": "text/html; charset=utf-8",
3042
- "Cache-Control": "no-store, must-revalidate",
3043
- "Vary": "RSC, Accept",
3044
- },
3045
- }));
3046
- }
3047
-
3048
- // Emit Cache-Control for ISR pages and write to ISR cache on MISS (production only).
3049
- // revalidate=Infinity means "cache forever" (no periodic revalidation) — treated as
3050
- // static here so we emit s-maxage=31536000 but skip ISR cache management.
3051
- if (revalidateSeconds !== null && revalidateSeconds > 0 && revalidateSeconds !== Infinity) {
3052
- // In production, tee the HTML response body to simultaneously stream to the
3053
- // client and collect the full HTML string for the ISR cache. rscData was
3054
- // already captured above by teeing the RSC stream before SSR.
3055
- // In dev, skip the tee and the X-Vinext-Cache header — every request renders
3056
- // fresh (no cache reads or writes in dev mode).
3057
- if (process.env.NODE_ENV === "production") {
3058
- const __isrResponseProd = attachMiddlewareContext(new Response(htmlStream, {
3059
- headers: {
3060
- "Content-Type": "text/html; charset=utf-8",
3061
- "Cache-Control": "s-maxage=" + revalidateSeconds + ", stale-while-revalidate",
3062
- "Vary": "RSC, Accept",
3063
- "X-Vinext-Cache": "MISS",
2289
+ },
2290
+ async renderPageSpecialError(specialError) {
2291
+ return __buildAppPageSpecialErrorResponse({
2292
+ clearRequestContext() {
2293
+ setHeadersContext(null);
2294
+ setNavigationContext(null);
3064
2295
  },
3065
- }));
3066
- if (__isrResponseProd.body) {
3067
- const [__streamForClient, __streamForCache] = __isrResponseProd.body.tee();
3068
- const __isrKey = __isrHtmlKey(cleanPathname);
3069
- const __isrKeyRscFromHtml = __isrRscKey(cleanPathname);
3070
- const __revalSecs = revalidateSeconds;
3071
- const __capturedRscDataPromise = __isrRscDataPromise;
3072
- const __cachePromise = (async () => {
3073
- try {
3074
- const __reader = __streamForCache.getReader();
3075
- const __decoder = new TextDecoder();
3076
- const __chunks = [];
3077
- for (;;) {
3078
- const { done, value } = await __reader.read();
3079
- if (done) break;
3080
- __chunks.push(__decoder.decode(value, { stream: true }));
3081
- }
3082
- __chunks.push(__decoder.decode());
3083
- const __fullHtml = __chunks.join("");
3084
- const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
3085
- // Write HTML and RSC to their own keys independently.
3086
- // RSC data was captured by the tee above (before isRscRequest branch)
3087
- // so an initial browser visit (HTML request) also populates the RSC key,
3088
- // ensuring the first client-side navigation after a direct visit is a
3089
- // cache hit rather than a miss.
3090
- const __writes = [
3091
- __isrSet(__isrKey, { kind: "APP_PAGE", html: __fullHtml, rscData: undefined, headers: undefined, postponed: undefined, status: 200 }, __revalSecs, __pageTags),
3092
- ];
3093
- if (__capturedRscDataPromise) {
3094
- __writes.push(
3095
- __capturedRscDataPromise.then((__rscBuf) =>
3096
- __isrSet(__isrKeyRscFromHtml, { kind: "APP_PAGE", html: "", rscData: __rscBuf, headers: undefined, postponed: undefined, status: 200 }, __revalSecs, __pageTags)
3097
- )
3098
- );
3099
- }
3100
- await Promise.all(__writes);
3101
- __isrDebug?.("HTML cache written", __isrKey);
3102
- } catch (__cacheErr) {
3103
- console.error("[vinext] ISR cache write error:", __cacheErr);
3104
- }
3105
- })();
3106
- // Register with ExecutionContext (from ALS) so the Workers runtime keeps
3107
- // the isolate alive until the cache write finishes, even after the response is sent.
3108
- _getRequestExecutionContext()?.waitUntil(__cachePromise);
3109
- return new Response(__streamForClient, { status: __isrResponseProd.status, headers: __isrResponseProd.headers });
3110
- }
3111
- return __isrResponseProd;
3112
- }
3113
- // Dev mode: return Cache-Control header but no X-Vinext-Cache (no cache read/write)
3114
- return attachMiddlewareContext(new Response(htmlStream, {
3115
- headers: {
3116
- "Content-Type": "text/html; charset=utf-8",
3117
- "Cache-Control": "s-maxage=" + revalidateSeconds + ", stale-while-revalidate",
3118
- "Vary": "RSC, Accept",
3119
- },
3120
- }));
3121
- }
3122
-
3123
- // revalidate=Infinity (or false, which Next.js normalises to false/0): treat as
3124
- // permanent static — emit the longest safe s-maxage but skip ISR cache management.
3125
- if (revalidateSeconds === Infinity) {
3126
- return attachMiddlewareContext(new Response(htmlStream, {
3127
- headers: {
3128
- "Content-Type": "text/html; charset=utf-8",
3129
- "Cache-Control": "s-maxage=31536000, stale-while-revalidate",
3130
- "X-Vinext-Cache": "STATIC",
3131
- "Vary": "RSC, Accept",
3132
- },
3133
- }));
3134
- }
3135
-
3136
- return attachMiddlewareContext(new Response(htmlStream, {
3137
- headers: { "Content-Type": "text/html; charset=utf-8", "Vary": "RSC, Accept" },
3138
- }));
2296
+ renderFallbackPage(statusCode) {
2297
+ return renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, request, {
2298
+ matchedParams: params,
2299
+ });
2300
+ },
2301
+ requestUrl: request.url,
2302
+ specialError,
2303
+ });
2304
+ },
2305
+ renderToReadableStream,
2306
+ routeHasLocalBoundary: !!(route?.error?.default) || !!(route?.errors && route.errors.some(function(e) { return e?.default; })),
2307
+ routePattern: route.pattern,
2308
+ runWithSuppressedHookWarning(probe) {
2309
+ // Run inside ALS context so the module-level console.error patch suppresses
2310
+ // "Invalid hook call" only for this request's probe — concurrent requests
2311
+ // each have their own ALS store and are unaffected.
2312
+ return _suppressHookWarningAls.run(true, probe);
2313
+ },
2314
+ waitUntil(__cachePromise) {
2315
+ _getRequestExecutionContext()?.waitUntil(__cachePromise);
2316
+ },
2317
+ });
3139
2318
  }
3140
2319
 
3141
2320
  if (import.meta.hot) {