vinext 0.0.39 → 0.0.40
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/dist/build/standalone.js +7 -0
- package/dist/build/standalone.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +2 -1
- package/dist/entries/app-rsc-entry.js +131 -245
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.js +80 -6
- package/dist/index.js.map +1 -1
- package/dist/plugins/server-externals-manifest.d.ts +11 -1
- package/dist/plugins/server-externals-manifest.js +10 -3
- package/dist/plugins/server-externals-manifest.js.map +1 -1
- package/dist/routing/app-router.d.ts +10 -2
- package/dist/routing/app-router.js +37 -22
- package/dist/routing/app-router.js.map +1 -1
- package/dist/server/app-page-response.d.ts +12 -1
- package/dist/server/app-page-response.js +26 -7
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +79 -0
- package/dist/server/app-page-route-wiring.js +165 -0
- package/dist/server/app-page-route-wiring.js.map +1 -0
- package/dist/server/app-page-stream.js +3 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-route-handler-response.js +4 -1
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-router-entry.d.ts +6 -1
- package/dist/server/app-router-entry.js +9 -2
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/prod-server.d.ts +1 -1
- package/dist/server/prod-server.js +37 -11
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/worker-utils.d.ts +4 -1
- package/dist/server/worker-utils.js +31 -1
- package/dist/server/worker-utils.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +13 -4
- package/dist/shims/error-boundary.js +23 -3
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/navigation.d.ts +16 -1
- package/dist/shims/navigation.js +18 -3
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.js +127 -38
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.d.ts +17 -4
- package/dist/shims/server.js +91 -73
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/slot.d.ts +28 -0
- package/dist/shims/slot.js +49 -0
- package/dist/shims/slot.js.map +1 -0
- package/package.json +1 -2
|
@@ -24,7 +24,9 @@ const appRouteHandlerCachePath = resolveEntryPath("../server/app-route-handler-c
|
|
|
24
24
|
const appPageCachePath = resolveEntryPath("../server/app-page-cache.js", import.meta.url);
|
|
25
25
|
const appPageExecutionPath = resolveEntryPath("../server/app-page-execution.js", import.meta.url);
|
|
26
26
|
const appPageBoundaryRenderPath = resolveEntryPath("../server/app-page-boundary-render.js", import.meta.url);
|
|
27
|
+
const appPageRouteWiringPath = resolveEntryPath("../server/app-page-route-wiring.js", import.meta.url);
|
|
27
28
|
const appPageRenderPath = resolveEntryPath("../server/app-page-render.js", import.meta.url);
|
|
29
|
+
const appPageResponsePath = resolveEntryPath("../server/app-page-response.js", import.meta.url);
|
|
28
30
|
const appPageRequestPath = resolveEntryPath("../server/app-page-request.js", import.meta.url);
|
|
29
31
|
const appRouteHandlerResponsePath = resolveEntryPath("../server/app-route-handler-response.js", import.meta.url);
|
|
30
32
|
const routeTriePath = resolveEntryPath("../routing/route-trie.js", import.meta.url);
|
|
@@ -50,6 +52,7 @@ function generateRscEntry(appDir, routes, middlewarePath, metadataRoutes, global
|
|
|
50
52
|
const bodySizeLimit = config?.bodySizeLimit ?? 1 * 1024 * 1024;
|
|
51
53
|
const i18nConfig = config?.i18n ?? null;
|
|
52
54
|
const hasPagesDir = config?.hasPagesDir ?? false;
|
|
55
|
+
const publicFiles = config?.publicFiles ?? [];
|
|
53
56
|
const imports = [];
|
|
54
57
|
const importMap = /* @__PURE__ */ new Map();
|
|
55
58
|
let importIdx = 0;
|
|
@@ -65,7 +68,7 @@ function generateRscEntry(appDir, routes, middlewarePath, metadataRoutes, global
|
|
|
65
68
|
if (route.pagePath) getImportVar(route.pagePath);
|
|
66
69
|
if (route.routePath) getImportVar(route.routePath);
|
|
67
70
|
for (const layout of route.layouts) getImportVar(layout);
|
|
68
|
-
for (const tmpl of route.templates) getImportVar(tmpl);
|
|
71
|
+
for (const tmpl of route.templates) if (tmpl) getImportVar(tmpl);
|
|
69
72
|
if (route.loadingPath) getImportVar(route.loadingPath);
|
|
70
73
|
if (route.errorPath) getImportVar(route.errorPath);
|
|
71
74
|
if (route.layoutErrorPaths) {
|
|
@@ -86,7 +89,7 @@ function generateRscEntry(appDir, routes, middlewarePath, metadataRoutes, global
|
|
|
86
89
|
}
|
|
87
90
|
const routeEntries = routes.map((route) => {
|
|
88
91
|
const layoutVars = route.layouts.map((l) => getImportVar(l));
|
|
89
|
-
const templateVars = route.templates.map((t) => getImportVar(t));
|
|
92
|
+
const templateVars = route.templates.map((t) => t ? getImportVar(t) : "null");
|
|
90
93
|
const notFoundVars = (route.notFoundPaths || []).map((nf) => nf ? getImportVar(nf) : "null");
|
|
91
94
|
const slotEntries = route.parallelSlots.map((slot) => {
|
|
92
95
|
const interceptEntries = slot.interceptingRoutes.map((ir) => ` {
|
|
@@ -102,6 +105,7 @@ function generateRscEntry(appDir, routes, middlewarePath, metadataRoutes, global
|
|
|
102
105
|
loading: ${slot.loadingPath ? getImportVar(slot.loadingPath) : "null"},
|
|
103
106
|
error: ${slot.errorPath ? getImportVar(slot.errorPath) : "null"},
|
|
104
107
|
layoutIndex: ${slot.layoutIndex},
|
|
108
|
+
routeSegments: ${JSON.stringify(slot.routeSegments)},
|
|
105
109
|
intercepts: [
|
|
106
110
|
${interceptEntries.join(",\n")}
|
|
107
111
|
],
|
|
@@ -204,13 +208,11 @@ function renderToReadableStream(model, options) {
|
|
|
204
208
|
}
|
|
205
209
|
}));
|
|
206
210
|
}
|
|
207
|
-
import { createElement
|
|
211
|
+
import { createElement } from "react";
|
|
208
212
|
import { setNavigationContext as _setNavigationContextOrig, getNavigationContext as _getNavigationContext } from "next/navigation";
|
|
209
213
|
import { setHeadersContext, headersContextFromRequest, getDraftModeCookieHeader, getAndClearPendingCookies, consumeDynamicUsage, markDynamicUsage, applyMiddlewareRequestHeaders, getHeadersContext, setHeadersAccessPhase } from "next/headers";
|
|
210
214
|
import { NextRequest, NextFetchEvent } from "next/server";
|
|
211
|
-
import {
|
|
212
|
-
import { LayoutSegmentProvider } from "vinext/layout-segment-context";
|
|
213
|
-
import { MetadataHead, mergeMetadata, resolveModuleMetadata, ViewportHead, mergeViewport, resolveModuleViewport } from "vinext/metadata";
|
|
215
|
+
import { mergeMetadata, resolveModuleMetadata, mergeViewport, resolveModuleViewport } from "vinext/metadata";
|
|
214
216
|
${middlewarePath ? `import * as middlewareModule from ${JSON.stringify(middlewarePath.replace(/\\/g, "/"))};` : ""}
|
|
215
217
|
${instrumentationPath ? `import * as _instrumentation from ${JSON.stringify(instrumentationPath.replace(/\\/g, "/"))};` : ""}
|
|
216
218
|
${effectiveMetaRoutes.length > 0 ? `import { sitemapToXml, robotsToText, manifestToJson } from ${JSON.stringify(metadataRoutesPath)};` : ""}
|
|
@@ -242,9 +244,16 @@ import {
|
|
|
242
244
|
renderAppPageErrorBoundary as __renderAppPageErrorBoundary,
|
|
243
245
|
renderAppPageHttpAccessFallback as __renderAppPageHttpAccessFallback,
|
|
244
246
|
} from ${JSON.stringify(appPageBoundaryRenderPath)};
|
|
247
|
+
import {
|
|
248
|
+
buildAppPageRouteElement as __buildAppPageRouteElement,
|
|
249
|
+
resolveAppPageChildSegments as __resolveAppPageChildSegments,
|
|
250
|
+
} from ${JSON.stringify(appPageRouteWiringPath)};
|
|
245
251
|
import {
|
|
246
252
|
renderAppPageLifecycle as __renderAppPageLifecycle,
|
|
247
253
|
} from ${JSON.stringify(appPageRenderPath)};
|
|
254
|
+
import {
|
|
255
|
+
mergeMiddlewareResponseHeaders as __mergeMiddlewareResponseHeaders,
|
|
256
|
+
} from ${JSON.stringify(appPageResponsePath)};
|
|
248
257
|
import {
|
|
249
258
|
buildAppPageElement as __buildAppPageElement,
|
|
250
259
|
resolveAppPageIntercept as __resolveAppPageIntercept,
|
|
@@ -409,38 +418,6 @@ function makeThenableParams(obj) {
|
|
|
409
418
|
return Object.assign(Promise.resolve(plain), plain);
|
|
410
419
|
}
|
|
411
420
|
|
|
412
|
-
// Resolve route tree segments to actual values using matched params.
|
|
413
|
-
// Dynamic segments like [id] are replaced with param values, catch-all
|
|
414
|
-
// segments like [...slug] are joined with "/", and route groups are kept as-is.
|
|
415
|
-
function __resolveChildSegments(routeSegments, treePosition, params) {
|
|
416
|
-
var raw = routeSegments.slice(treePosition);
|
|
417
|
-
var result = [];
|
|
418
|
-
for (var j = 0; j < raw.length; j++) {
|
|
419
|
-
var seg = raw[j];
|
|
420
|
-
// Optional catch-all: [[...param]]
|
|
421
|
-
if (seg.indexOf("[[...") === 0 && seg.charAt(seg.length - 1) === "]" && seg.charAt(seg.length - 2) === "]") {
|
|
422
|
-
var pn = seg.slice(5, -2);
|
|
423
|
-
var v = params[pn];
|
|
424
|
-
// Skip empty optional catch-all (e.g., visiting /blog on [[...slug]] route)
|
|
425
|
-
if (Array.isArray(v) && v.length === 0) continue;
|
|
426
|
-
if (v == null) continue;
|
|
427
|
-
result.push(Array.isArray(v) ? v.join("/") : v);
|
|
428
|
-
// Catch-all: [...param]
|
|
429
|
-
} else if (seg.indexOf("[...") === 0 && seg.charAt(seg.length - 1) === "]") {
|
|
430
|
-
var pn2 = seg.slice(4, -1);
|
|
431
|
-
var v2 = params[pn2];
|
|
432
|
-
result.push(Array.isArray(v2) ? v2.join("/") : (v2 || seg));
|
|
433
|
-
// Dynamic: [param]
|
|
434
|
-
} else if (seg.charAt(0) === "[" && seg.charAt(seg.length - 1) === "]" && seg.indexOf(".") === -1) {
|
|
435
|
-
var pn3 = seg.slice(1, -1);
|
|
436
|
-
result.push(params[pn3] || seg);
|
|
437
|
-
} else {
|
|
438
|
-
result.push(seg);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return result;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
421
|
// djb2 hash — matches Next.js's stringHash for digest generation.
|
|
445
422
|
// Produces a stable numeric string from error message + stack.
|
|
446
423
|
function __errorDigest(str) {
|
|
@@ -640,7 +617,7 @@ async function renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, req
|
|
|
640
617
|
makeThenableParams,
|
|
641
618
|
matchedParams: opts?.matchedParams ?? route?.params ?? {},
|
|
642
619
|
requestUrl: request.url,
|
|
643
|
-
resolveChildSegments:
|
|
620
|
+
resolveChildSegments: __resolveAppPageChildSegments,
|
|
644
621
|
rootForbiddenModule: rootForbiddenModule,
|
|
645
622
|
rootLayouts: rootLayouts,
|
|
646
623
|
rootNotFoundModule: rootNotFoundModule,
|
|
@@ -686,7 +663,7 @@ async function renderErrorBoundaryPage(route, error, isRscRequest, request, matc
|
|
|
686
663
|
makeThenableParams,
|
|
687
664
|
matchedParams: matchedParams ?? route?.params ?? {},
|
|
688
665
|
requestUrl: request.url,
|
|
689
|
-
resolveChildSegments:
|
|
666
|
+
resolveChildSegments: __resolveAppPageChildSegments,
|
|
690
667
|
rootLayouts: rootLayouts,
|
|
691
668
|
route,
|
|
692
669
|
renderToReadableStream,
|
|
@@ -704,6 +681,21 @@ function matchRoute(url) {
|
|
|
704
681
|
return _trieMatch(_routeTrie, urlParts);
|
|
705
682
|
}
|
|
706
683
|
|
|
684
|
+
function __createStaticFileSignal(pathname, _mwCtx) {
|
|
685
|
+
const headers = new Headers({
|
|
686
|
+
"x-vinext-static-file": encodeURIComponent(pathname),
|
|
687
|
+
});
|
|
688
|
+
if (_mwCtx.headers) {
|
|
689
|
+
for (const [key, value] of _mwCtx.headers) {
|
|
690
|
+
headers.append(key, value);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return new Response(null, {
|
|
694
|
+
status: _mwCtx.status ?? 200,
|
|
695
|
+
headers,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
707
699
|
// matchPattern is kept for findIntercept (linear scan over small interceptLookup array).
|
|
708
700
|
function matchPattern(urlParts, patternParts) {
|
|
709
701
|
const params = Object.create(null);
|
|
@@ -852,12 +844,10 @@ async function buildPageElement(route, params, opts, searchParams) {
|
|
|
852
844
|
const resolvedMetadata = metadataList.length > 0 ? mergeMetadata(metadataList) : null;
|
|
853
845
|
const resolvedViewport = mergeViewport(viewportList);
|
|
854
846
|
|
|
855
|
-
// Build
|
|
856
|
-
//
|
|
857
|
-
//
|
|
858
|
-
|
|
859
|
-
const asyncParams = makeThenableParams(params);
|
|
860
|
-
const pageProps = { params: asyncParams };
|
|
847
|
+
// Build the route tree from the leaf page, then delegate the boundary/layout/
|
|
848
|
+
// template/segment wiring to a typed runtime helper so the generated entry
|
|
849
|
+
// stays thin and the wiring logic can be unit tested directly.
|
|
850
|
+
const pageProps = { params: makeThenableParams(params) };
|
|
861
851
|
if (searchParams) {
|
|
862
852
|
// Always provide searchParams prop when the URL object is available, even
|
|
863
853
|
// when the query string is empty -- pages that do "await searchParams" need
|
|
@@ -873,192 +863,25 @@ async function buildPageElement(route, params, opts, searchParams) {
|
|
|
873
863
|
// dynamic, and this avoids false positives from React internals.
|
|
874
864
|
if (hasSearchParams) markDynamicUsage();
|
|
875
865
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// Wrap with loading.tsx Suspense if present
|
|
895
|
-
if (route.loading?.default) {
|
|
896
|
-
element = createElement(
|
|
897
|
-
Suspense,
|
|
898
|
-
{ fallback: createElement(route.loading.default) },
|
|
899
|
-
element,
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// Wrap with the leaf's error.tsx ErrorBoundary if it's not already covered
|
|
904
|
-
// by a per-layout error boundary (i.e., the leaf has error.tsx but no layout).
|
|
905
|
-
// Per-layout error boundaries are interleaved with layouts below.
|
|
906
|
-
{
|
|
907
|
-
const lastLayoutError = route.errors ? route.errors[route.errors.length - 1] : null;
|
|
908
|
-
if (route.error?.default && route.error !== lastLayoutError) {
|
|
909
|
-
element = createElement(ErrorBoundary, {
|
|
910
|
-
fallback: route.error.default,
|
|
911
|
-
children: element,
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// Wrap with NotFoundBoundary so client-side notFound() renders not-found.tsx
|
|
917
|
-
// instead of crashing the React tree. Must be above ErrorBoundary since
|
|
918
|
-
// ErrorBoundary re-throws notFound errors.
|
|
919
|
-
// Pre-render the not-found component as a React element since it may be a
|
|
920
|
-
// server component (not a client reference) and can't be passed as a function prop.
|
|
921
|
-
{
|
|
922
|
-
const NotFoundComponent = route.notFound?.default ?? ${rootNotFoundVar ? `${rootNotFoundVar}?.default` : "null"};
|
|
923
|
-
if (NotFoundComponent) {
|
|
924
|
-
element = createElement(NotFoundBoundary, {
|
|
925
|
-
fallback: createElement(NotFoundComponent),
|
|
926
|
-
children: element,
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
// Wrap with templates (innermost first, then outer)
|
|
932
|
-
// Templates are like layouts but re-mount on navigation (client-side concern).
|
|
933
|
-
// On the server, they just wrap the content like layouts do.
|
|
934
|
-
if (route.templates) {
|
|
935
|
-
for (let i = route.templates.length - 1; i >= 0; i--) {
|
|
936
|
-
const TemplateComponent = route.templates[i]?.default;
|
|
937
|
-
if (TemplateComponent) {
|
|
938
|
-
element = createElement(TemplateComponent, { children: element, params });
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Wrap with layouts (innermost first, then outer).
|
|
944
|
-
// At each layout level, first wrap with that level's error boundary (if any)
|
|
945
|
-
// so the boundary is inside the layout and catches errors from children.
|
|
946
|
-
// This matches Next.js behavior: Layout > ErrorBoundary > children.
|
|
947
|
-
// Parallel slots are passed as named props to the innermost layout
|
|
948
|
-
// (the layout at the same directory level as the page/slots)
|
|
949
|
-
for (let i = route.layouts.length - 1; i >= 0; i--) {
|
|
950
|
-
// Wrap with per-layout error boundary before wrapping with layout.
|
|
951
|
-
// This places the ErrorBoundary inside the layout, catching errors
|
|
952
|
-
// from child segments (matching Next.js per-segment error handling).
|
|
953
|
-
if (route.errors && route.errors[i]?.default) {
|
|
954
|
-
element = createElement(ErrorBoundary, {
|
|
955
|
-
fallback: route.errors[i].default,
|
|
956
|
-
children: element,
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
const LayoutComponent = route.layouts[i]?.default;
|
|
961
|
-
if (LayoutComponent) {
|
|
962
|
-
// Per-layout NotFoundBoundary: wraps this layout's children so that
|
|
963
|
-
// notFound() thrown from a child layout is caught here.
|
|
964
|
-
// Matches Next.js behavior where each segment has its own boundary.
|
|
965
|
-
// The boundary at level N catches errors from Layout[N+1] and below,
|
|
966
|
-
// but NOT from Layout[N] itself (which propagates to level N-1).
|
|
967
|
-
{
|
|
968
|
-
const LayoutNotFound = route.notFounds?.[i]?.default;
|
|
969
|
-
if (LayoutNotFound) {
|
|
970
|
-
element = createElement(NotFoundBoundary, {
|
|
971
|
-
fallback: createElement(LayoutNotFound),
|
|
972
|
-
children: element,
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
const layoutProps = { children: element, params: makeThenableParams(params) };
|
|
978
|
-
|
|
979
|
-
// Add parallel slot elements to the layout that defines them.
|
|
980
|
-
// Each slot has a layoutIndex indicating which layout it belongs to.
|
|
981
|
-
if (route.slots) {
|
|
982
|
-
for (const [slotName, slotMod] of Object.entries(route.slots)) {
|
|
983
|
-
// Attach slot to the layout at its layoutIndex, or to the innermost layout if -1
|
|
984
|
-
const targetIdx = slotMod.layoutIndex >= 0 ? slotMod.layoutIndex : route.layouts.length - 1;
|
|
985
|
-
if (i !== targetIdx) continue;
|
|
986
|
-
// Check if this slot has an intercepting route that should activate
|
|
987
|
-
let SlotPage = null;
|
|
988
|
-
let slotParams = params;
|
|
989
|
-
|
|
990
|
-
if (opts && opts.interceptSlot === slotName && opts.interceptPage) {
|
|
991
|
-
// Use the intercepting route's page component
|
|
992
|
-
SlotPage = opts.interceptPage.default;
|
|
993
|
-
slotParams = opts.interceptParams || params;
|
|
994
|
-
} else {
|
|
995
|
-
SlotPage = slotMod.page?.default || slotMod.default?.default;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
if (SlotPage) {
|
|
999
|
-
let slotElement = createElement(SlotPage, { params: makeThenableParams(slotParams) });
|
|
1000
|
-
// Wrap with slot-specific layout if present.
|
|
1001
|
-
// In Next.js, @slot/layout.tsx wraps the slot's page content
|
|
1002
|
-
// before it is passed as a prop to the parent layout.
|
|
1003
|
-
const SlotLayout = slotMod.layout?.default;
|
|
1004
|
-
if (SlotLayout) {
|
|
1005
|
-
slotElement = createElement(SlotLayout, {
|
|
1006
|
-
children: slotElement,
|
|
1007
|
-
params: makeThenableParams(slotParams),
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
// Wrap with slot-specific loading if present
|
|
1011
|
-
if (slotMod.loading?.default) {
|
|
1012
|
-
slotElement = createElement(Suspense,
|
|
1013
|
-
{ fallback: createElement(slotMod.loading.default) },
|
|
1014
|
-
slotElement,
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1017
|
-
// Wrap with slot-specific error boundary if present
|
|
1018
|
-
if (slotMod.error?.default) {
|
|
1019
|
-
slotElement = createElement(ErrorBoundary, {
|
|
1020
|
-
fallback: slotMod.error.default,
|
|
1021
|
-
children: slotElement,
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
layoutProps[slotName] = slotElement;
|
|
866
|
+
return __buildAppPageRouteElement({
|
|
867
|
+
element: createElement(PageComponent, pageProps),
|
|
868
|
+
globalErrorModule: ${globalErrorVar ? globalErrorVar : "null"},
|
|
869
|
+
makeThenableParams,
|
|
870
|
+
matchedParams: params,
|
|
871
|
+
resolvedMetadata,
|
|
872
|
+
resolvedViewport,
|
|
873
|
+
rootNotFoundModule: ${rootNotFoundVar ? rootNotFoundVar : "null"},
|
|
874
|
+
route,
|
|
875
|
+
slotOverrides:
|
|
876
|
+
opts && opts.interceptSlot && opts.interceptPage
|
|
877
|
+
? {
|
|
878
|
+
[opts.interceptSlot]: {
|
|
879
|
+
pageModule: opts.interceptPage,
|
|
880
|
+
params: opts.interceptParams || params,
|
|
881
|
+
},
|
|
1025
882
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
element = createElement(LayoutComponent, layoutProps);
|
|
1030
|
-
|
|
1031
|
-
// Wrap the layout with LayoutSegmentProvider so useSelectedLayoutSegments()
|
|
1032
|
-
// called INSIDE this layout gets the correct child segments. We resolve the
|
|
1033
|
-
// route tree segments using actual param values and pass them through context.
|
|
1034
|
-
// We wrap the layout (not just children) because hooks are called from
|
|
1035
|
-
// components rendered inside the layout's own JSX.
|
|
1036
|
-
const treePos = route.layoutTreePositions ? route.layoutTreePositions[i] : 0;
|
|
1037
|
-
const childSegs = __resolveChildSegments(route.routeSegments || [], treePos, params);
|
|
1038
|
-
element = createElement(LayoutSegmentProvider, { segmentMap: { children: childSegs } }, element);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// Wrap with global error boundary if app/global-error.tsx exists.
|
|
1043
|
-
// This must be present in both HTML and RSC paths so the component tree
|
|
1044
|
-
// structure matches — otherwise React reconciliation on client-side navigation
|
|
1045
|
-
// would see a mismatched tree and destroy/recreate the DOM.
|
|
1046
|
-
//
|
|
1047
|
-
// For RSC requests (client-side nav), this provides error recovery on the client.
|
|
1048
|
-
// For HTML requests (initial page load), the ErrorBoundary catches during SSR
|
|
1049
|
-
// but produces double <html>/<body> (root layout + global-error). The request
|
|
1050
|
-
// handler detects this via the rscOnError flag and re-renders without layouts.
|
|
1051
|
-
${globalErrorVar ? `
|
|
1052
|
-
const GlobalErrorComponent = ${globalErrorVar}.default;
|
|
1053
|
-
if (GlobalErrorComponent) {
|
|
1054
|
-
element = createElement(ErrorBoundary, {
|
|
1055
|
-
fallback: GlobalErrorComponent,
|
|
1056
|
-
children: element,
|
|
1057
|
-
});
|
|
1058
|
-
}
|
|
1059
|
-
` : ""}
|
|
1060
|
-
|
|
1061
|
-
return element;
|
|
883
|
+
: null,
|
|
884
|
+
});
|
|
1062
885
|
}
|
|
1063
886
|
|
|
1064
887
|
${middlewarePath ? generateMiddlewareMatcherCode("modern") : ""}
|
|
@@ -1069,6 +892,7 @@ const __i18nConfig = ${JSON.stringify(i18nConfig)};
|
|
|
1069
892
|
const __configRedirects = ${JSON.stringify(redirects)};
|
|
1070
893
|
const __configRewrites = ${JSON.stringify(rewrites)};
|
|
1071
894
|
const __configHeaders = ${JSON.stringify(headers)};
|
|
895
|
+
const __publicFiles = new Set(${JSON.stringify(publicFiles)});
|
|
1072
896
|
const __allowedOrigins = ${JSON.stringify(allowedOrigins)};
|
|
1073
897
|
|
|
1074
898
|
${generateDevOriginCheckCode(config?.allowedDevOrigins)}
|
|
@@ -1267,6 +1091,9 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
1267
1091
|
let pathname = __normalizePath(decodedUrlPathname);
|
|
1268
1092
|
|
|
1269
1093
|
${bp ? `
|
|
1094
|
+
if (!hasBasePath(pathname, __basePath) && !pathname.startsWith("/__vinext/")) {
|
|
1095
|
+
return new Response("Not Found", { status: 404 });
|
|
1096
|
+
}
|
|
1270
1097
|
// Strip basePath prefix
|
|
1271
1098
|
pathname = stripBasePath(pathname, __basePath);
|
|
1272
1099
|
` : ""}
|
|
@@ -1614,6 +1441,18 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
1614
1441
|
}
|
|
1615
1442
|
}
|
|
1616
1443
|
|
|
1444
|
+
// Serve public/ files as filesystem routes after middleware and before
|
|
1445
|
+
// afterFiles/fallback rewrites, matching Next.js routing semantics.
|
|
1446
|
+
if (
|
|
1447
|
+
(request.method === "GET" || request.method === "HEAD") &&
|
|
1448
|
+
!pathname.endsWith(".rsc") &&
|
|
1449
|
+
__publicFiles.has(cleanPathname)
|
|
1450
|
+
) {
|
|
1451
|
+
setHeadersContext(null);
|
|
1452
|
+
setNavigationContext(null);
|
|
1453
|
+
return __createStaticFileSignal(cleanPathname, _mwCtx);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1617
1456
|
// Set navigation context for Server Components.
|
|
1618
1457
|
// Note: Headers context is already set by runWithRequestContext in the handler wrapper.
|
|
1619
1458
|
setNavigationContext({
|
|
@@ -1669,7 +1508,10 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
1669
1508
|
returnValue = { ok: true, data };
|
|
1670
1509
|
} catch (e) {
|
|
1671
1510
|
// Detect redirect() / permanentRedirect() called inside the action.
|
|
1672
|
-
// These throw errors with digest "NEXT_REDIRECT
|
|
1511
|
+
// These throw errors with digest "NEXT_REDIRECT;<type>;<url>[;<status>]".
|
|
1512
|
+
// The type field is empty when redirect() was called without an explicit
|
|
1513
|
+
// type argument. In Server Action context, Next.js defaults to "push" so
|
|
1514
|
+
// the Back button works after form submissions.
|
|
1673
1515
|
// The URL is encodeURIComponent-encoded to prevent semicolons in the URL
|
|
1674
1516
|
// from corrupting the delimiter-based digest format.
|
|
1675
1517
|
if (e && typeof e === "object" && "digest" in e) {
|
|
@@ -1678,7 +1520,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
1678
1520
|
const parts = digest.split(";");
|
|
1679
1521
|
actionRedirect = {
|
|
1680
1522
|
url: decodeURIComponent(parts[2]),
|
|
1681
|
-
type: parts[1] || "
|
|
1523
|
+
type: parts[1] || "push", // Server Action → default "push"
|
|
1682
1524
|
status: parts[3] ? parseInt(parts[3], 10) : 307,
|
|
1683
1525
|
};
|
|
1684
1526
|
returnValue = { ok: true, data: undefined };
|
|
@@ -1714,10 +1556,14 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
1714
1556
|
const redirectHeaders = new Headers({
|
|
1715
1557
|
"Content-Type": "text/x-component; charset=utf-8",
|
|
1716
1558
|
"Vary": "RSC, Accept",
|
|
1717
|
-
"x-action-redirect": actionRedirect.url,
|
|
1718
|
-
"x-action-redirect-type": actionRedirect.type,
|
|
1719
|
-
"x-action-redirect-status": String(actionRedirect.status),
|
|
1720
1559
|
});
|
|
1560
|
+
// Merge middleware headers first so the framework's own redirect control
|
|
1561
|
+
// headers below are always authoritative and cannot be clobbered by
|
|
1562
|
+
// middleware that happens to set x-action-redirect* keys.
|
|
1563
|
+
__mergeMiddlewareResponseHeaders(redirectHeaders, _mwCtx.headers);
|
|
1564
|
+
redirectHeaders.set("x-action-redirect", actionRedirect.url);
|
|
1565
|
+
redirectHeaders.set("x-action-redirect-type", actionRedirect.type);
|
|
1566
|
+
redirectHeaders.set("x-action-redirect-status", String(actionRedirect.status));
|
|
1721
1567
|
for (const cookie of actionPendingCookies) {
|
|
1722
1568
|
redirectHeaders.append("Set-Cookie", cookie);
|
|
1723
1569
|
}
|
|
@@ -1737,7 +1583,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
1737
1583
|
searchParams: url.searchParams,
|
|
1738
1584
|
params: actionParams,
|
|
1739
1585
|
});
|
|
1740
|
-
element = buildPageElement(actionRoute, actionParams, undefined, url.searchParams);
|
|
1586
|
+
element = await buildPageElement(actionRoute, actionParams, undefined, url.searchParams);
|
|
1741
1587
|
} else {
|
|
1742
1588
|
element = createElement("div", null, "Page not found");
|
|
1743
1589
|
}
|
|
@@ -1760,15 +1606,15 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
1760
1606
|
const actionPendingCookies = getAndClearPendingCookies();
|
|
1761
1607
|
const actionDraftCookie = getDraftModeCookieHeader();
|
|
1762
1608
|
|
|
1763
|
-
const actionHeaders = { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" };
|
|
1764
|
-
|
|
1609
|
+
const actionHeaders = new Headers({ "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" });
|
|
1610
|
+
__mergeMiddlewareResponseHeaders(actionHeaders, _mwCtx.headers);
|
|
1765
1611
|
if (actionPendingCookies.length > 0 || actionDraftCookie) {
|
|
1766
1612
|
for (const cookie of actionPendingCookies) {
|
|
1767
|
-
|
|
1613
|
+
actionHeaders.append("Set-Cookie", cookie);
|
|
1768
1614
|
}
|
|
1769
|
-
if (actionDraftCookie)
|
|
1615
|
+
if (actionDraftCookie) actionHeaders.append("Set-Cookie", actionDraftCookie);
|
|
1770
1616
|
}
|
|
1771
|
-
return
|
|
1617
|
+
return new Response(rscStream, { status: _mwCtx.status ?? 200, headers: actionHeaders });
|
|
1772
1618
|
} catch (err) {
|
|
1773
1619
|
getAndClearPendingCookies(); // Clear pending cookies on error
|
|
1774
1620
|
console.error("[vinext] Server action error:", err);
|
|
@@ -2148,7 +1994,31 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
2148
1994
|
},
|
|
2149
1995
|
isRscRequest,
|
|
2150
1996
|
matchSourceRouteParams(pattern) {
|
|
2151
|
-
|
|
1997
|
+
// Extract actual URL param values by prefix-matching the request pathname
|
|
1998
|
+
// against the source route's pattern. This handles all interception conventions:
|
|
1999
|
+
// (.) same-level, (..) one-level-up, and (...) root — the source pattern's
|
|
2000
|
+
// dynamic segments that align with the URL get their real values extracted.
|
|
2001
|
+
// We must NOT use matchRoute(pattern) here: the trie would match the literal
|
|
2002
|
+
// ":param" strings as dynamic segment values, returning e.g. {id: ":id"}.
|
|
2003
|
+
const patternParts = pattern.split("/").filter(Boolean);
|
|
2004
|
+
const urlParts = cleanPathname.split("/").filter(Boolean);
|
|
2005
|
+
const params = Object.create(null);
|
|
2006
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
2007
|
+
const pp = patternParts[i];
|
|
2008
|
+
if (pp.endsWith("+") || pp.endsWith("*")) {
|
|
2009
|
+
// urlParts.slice(i) safely returns [] when i >= urlParts.length,
|
|
2010
|
+
// which is the correct value for optional catch-all with zero segments.
|
|
2011
|
+
params[pp.slice(1, -1)] = urlParts.slice(i);
|
|
2012
|
+
break;
|
|
2013
|
+
}
|
|
2014
|
+
if (i >= urlParts.length) break;
|
|
2015
|
+
if (pp.startsWith(":")) {
|
|
2016
|
+
params[pp.slice(1)] = urlParts[i];
|
|
2017
|
+
} else if (pp !== urlParts[i]) {
|
|
2018
|
+
break;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return params;
|
|
2152
2022
|
},
|
|
2153
2023
|
renderInterceptResponse(sourceRoute, interceptElement) {
|
|
2154
2024
|
const interceptOnError = createRscOnErrorHandler(
|
|
@@ -2163,8 +2033,11 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
2163
2033
|
// by the client, and async server components that run during consumption need the
|
|
2164
2034
|
// context to still be live. The AsyncLocalStorage scope from runWithRequestContext
|
|
2165
2035
|
// handles cleanup naturally when all async continuations complete.
|
|
2036
|
+
const interceptHeaders = new Headers({ "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" });
|
|
2037
|
+
__mergeMiddlewareResponseHeaders(interceptHeaders, _mwCtx.headers);
|
|
2166
2038
|
return new Response(interceptStream, {
|
|
2167
|
-
|
|
2039
|
+
status: _mwCtx.status ?? 200,
|
|
2040
|
+
headers: interceptHeaders,
|
|
2168
2041
|
});
|
|
2169
2042
|
},
|
|
2170
2043
|
searchParams: url.searchParams,
|
|
@@ -2215,6 +2088,19 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
2215
2088
|
// rscCssTransform — no manual loadCss() call needed.
|
|
2216
2089
|
const _hasLoadingBoundary = !!(route.loading && route.loading.default);
|
|
2217
2090
|
const _asyncLayoutParams = makeThenableParams(params);
|
|
2091
|
+
// Convert URLSearchParams to a plain object then wrap in makeThenableParams()
|
|
2092
|
+
// so probePage() passes the same shape that buildPageElement() gives to the
|
|
2093
|
+
// real render. Without this, pages that destructure await-ed searchParams
|
|
2094
|
+
// throw TypeError during probe.
|
|
2095
|
+
const _probeSearchObj = {};
|
|
2096
|
+
url.searchParams.forEach(function(v, k) {
|
|
2097
|
+
if (k in _probeSearchObj) {
|
|
2098
|
+
_probeSearchObj[k] = Array.isArray(_probeSearchObj[k]) ? _probeSearchObj[k].concat(v) : [_probeSearchObj[k], v];
|
|
2099
|
+
} else {
|
|
2100
|
+
_probeSearchObj[k] = v;
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
const _asyncSearchParams = makeThenableParams(_probeSearchObj);
|
|
2218
2104
|
return __renderAppPageLifecycle({
|
|
2219
2105
|
cleanPathname,
|
|
2220
2106
|
clearRequestContext() {
|
|
@@ -2260,7 +2146,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
|
|
|
2260
2146
|
return LayoutComp({ params: _asyncLayoutParams, children: null });
|
|
2261
2147
|
},
|
|
2262
2148
|
probePage() {
|
|
2263
|
-
return PageComponent({ params });
|
|
2149
|
+
return PageComponent({ params: _asyncLayoutParams, searchParams: _asyncSearchParams });
|
|
2264
2150
|
},
|
|
2265
2151
|
revalidateSeconds,
|
|
2266
2152
|
renderErrorBoundaryResponse(renderErr) {
|