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.
- package/README.md +7 -6
- package/dist/config/next-config.d.ts +2 -0
- package/dist/config/next-config.js +4 -0
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.js +52 -4
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-browser-entry.js +3 -330
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.js +444 -1265
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-ssr-entry.js +4 -460
- package/dist/entries/app-ssr-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +8 -1
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +13 -0
- package/dist/entries/runtime-entry-module.js +27 -0
- package/dist/entries/runtime-entry-module.js.map +1 -0
- package/dist/index.js +302 -23
- package/dist/index.js.map +1 -1
- package/dist/plugins/optimize-imports.d.ts +38 -0
- package/dist/plugins/optimize-imports.js +557 -0
- package/dist/plugins/optimize-imports.js.map +1 -0
- package/dist/server/app-browser-entry.d.ts +1 -0
- package/dist/server/app-browser-entry.js +160 -0
- package/dist/server/app-browser-entry.js.map +1 -0
- package/dist/server/app-browser-stream.d.ts +33 -0
- package/dist/server/app-browser-stream.js +54 -0
- package/dist/server/app-browser-stream.js.map +1 -0
- package/dist/server/app-page-boundary-render.d.ts +63 -0
- package/dist/server/app-page-boundary-render.js +182 -0
- package/dist/server/app-page-boundary-render.js.map +1 -0
- package/dist/server/app-page-boundary.d.ts +57 -0
- package/dist/server/app-page-boundary.js +60 -0
- package/dist/server/app-page-boundary.js.map +1 -0
- package/dist/server/app-page-cache.d.ts +61 -0
- package/dist/server/app-page-cache.js +133 -0
- package/dist/server/app-page-cache.js.map +1 -0
- package/dist/server/app-page-execution.d.ts +46 -0
- package/dist/server/app-page-execution.js +109 -0
- package/dist/server/app-page-execution.js.map +1 -0
- package/dist/server/app-page-probe.d.ts +17 -0
- package/dist/server/app-page-probe.js +35 -0
- package/dist/server/app-page-probe.js.map +1 -0
- package/dist/server/app-page-render.d.ts +59 -0
- package/dist/server/app-page-render.js +174 -0
- package/dist/server/app-page-render.js.map +1 -0
- package/dist/server/app-page-request.d.ts +58 -0
- package/dist/server/app-page-request.js +79 -0
- package/dist/server/app-page-request.js.map +1 -0
- package/dist/server/app-page-response.d.ts +51 -0
- package/dist/server/app-page-response.js +90 -0
- package/dist/server/app-page-response.js.map +1 -0
- package/dist/server/app-page-stream.d.ts +55 -0
- package/dist/server/app-page-stream.js +65 -0
- package/dist/server/app-page-stream.js.map +1 -0
- package/dist/server/app-route-handler-cache.d.ts +42 -0
- package/dist/server/app-route-handler-cache.js +69 -0
- package/dist/server/app-route-handler-cache.js.map +1 -0
- package/dist/server/app-route-handler-execution.d.ts +64 -0
- package/dist/server/app-route-handler-execution.js +100 -0
- package/dist/server/app-route-handler-execution.js.map +1 -0
- package/dist/server/app-route-handler-policy.d.ts +51 -0
- package/dist/server/app-route-handler-policy.js +57 -0
- package/dist/server/app-route-handler-policy.js.map +1 -0
- package/dist/server/app-route-handler-response.d.ts +26 -0
- package/dist/server/app-route-handler-response.js +61 -0
- package/dist/server/app-route-handler-response.js.map +1 -0
- package/dist/server/app-route-handler-runtime.d.ts +27 -0
- package/dist/server/app-route-handler-runtime.js +99 -0
- package/dist/server/app-route-handler-runtime.js.map +1 -0
- package/dist/server/app-ssr-entry.d.ts +19 -0
- package/dist/server/app-ssr-entry.js +105 -0
- package/dist/server/app-ssr-entry.js.map +1 -0
- package/dist/server/app-ssr-stream.d.ts +30 -0
- package/dist/server/app-ssr-stream.js +116 -0
- package/dist/server/app-ssr-stream.js.map +1 -0
- package/dist/server/prod-server.d.ts +13 -1
- package/dist/server/prod-server.js +113 -19
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/worker-utils.d.ts +0 -6
- package/dist/server/worker-utils.js +41 -5
- package/dist/server/worker-utils.js.map +1 -1
- package/dist/shims/error-boundary.js +1 -1
- package/dist/shims/font-google-base.js +1 -1
- package/dist/shims/font-google-base.js.map +1 -1
- package/dist/shims/font-google.d.ts +2 -3
- package/dist/shims/font-google.js +2 -3
- package/dist/shims/metadata.js +3 -3
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/request-state-types.d.ts +2 -2
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/package.json +1 -1
- package/dist/shims/font-google.generated.d.ts +0 -1929
- package/dist/shims/font-google.generated.js +0 -1929
- 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
|
|
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
|
|
442
|
-
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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 =
|
|
2041
|
-
if (
|
|
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
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
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
|
-
|
|
2075
|
-
if (method === "OPTIONS" && typeof handler["OPTIONS"] !== "function") {
|
|
1873
|
+
if (shouldAutoRespondToOptions) {
|
|
2076
1874
|
setHeadersContext(null);
|
|
2077
1875
|
setNavigationContext(null);
|
|
2078
|
-
return
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
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
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
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
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
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
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
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
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
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
|
|
2298
|
-
|
|
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
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
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
|
-
|
|
2474
|
-
|
|
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
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
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
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
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
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
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
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
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
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
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
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
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
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
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
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
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) {
|