vinext 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +1 -1
  2. package/dist/build/action-owner-manifest.js +1 -0
  3. package/dist/check.js +1 -1
  4. package/dist/config/config-matchers.js +2 -2
  5. package/dist/config/destination-params.js +5 -2
  6. package/dist/config/next-config.d.ts +16 -1
  7. package/dist/config/next-config.js +31 -1
  8. package/dist/entries/app-rsc-entry.js +41 -18
  9. package/dist/entries/app-rsc-manifest.js +4 -0
  10. package/dist/entries/pages-server-entry.js +2 -0
  11. package/dist/index.js +15 -12
  12. package/dist/init-cloudflare.js +18 -2
  13. package/dist/plugins/css-target.d.ts +18 -0
  14. package/dist/plugins/css-target.js +93 -0
  15. package/dist/routing/app-route-graph.d.ts +10 -0
  16. package/dist/routing/app-route-graph.js +15 -1
  17. package/dist/server/api-handler.d.ts +2 -0
  18. package/dist/server/api-handler.js +3 -5
  19. package/dist/server/app-action-forwarding.js +3 -1
  20. package/dist/server/app-browser-entry.js +21 -4
  21. package/dist/server/app-elements.d.ts +3 -0
  22. package/dist/server/app-elements.js +14 -1
  23. package/dist/server/app-fallback-renderer.js +2 -2
  24. package/dist/server/app-optimistic-routing.d.ts +12 -1
  25. package/dist/server/app-optimistic-routing.js +38 -16
  26. package/dist/server/app-page-boundary-render.js +2 -2
  27. package/dist/server/app-page-cache-render.js +1 -1
  28. package/dist/server/app-page-dispatch.d.ts +10 -1
  29. package/dist/server/app-page-dispatch.js +73 -30
  30. package/dist/server/app-page-element-builder.d.ts +9 -20
  31. package/dist/server/app-page-element-builder.js +37 -49
  32. package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
  33. package/dist/server/app-page-intercept-segment-config.js +90 -0
  34. package/dist/server/app-page-navigation-params.d.ts +23 -0
  35. package/dist/server/app-page-navigation-params.js +49 -0
  36. package/dist/server/app-page-render.d.ts +5 -2
  37. package/dist/server/app-page-render.js +17 -3
  38. package/dist/server/app-page-request.d.ts +24 -2
  39. package/dist/server/app-page-request.js +10 -10
  40. package/dist/server/app-page-route-wiring.d.ts +10 -0
  41. package/dist/server/app-page-route-wiring.js +16 -12
  42. package/dist/server/app-page-segment-plan.js +9 -1
  43. package/dist/server/app-page-segment-state.d.ts +9 -0
  44. package/dist/server/app-page-segment-state.js +54 -1
  45. package/dist/server/app-response-stage-entry.js +1 -0
  46. package/dist/server/app-route-module-loader.d.ts +2 -0
  47. package/dist/server/app-route-module-loader.js +1 -0
  48. package/dist/server/app-rsc-handler.d.ts +10 -1
  49. package/dist/server/app-rsc-handler.js +75 -43
  50. package/dist/server/app-rsc-response-finalizer.d.ts +3 -0
  51. package/dist/server/app-rsc-response-finalizer.js +11 -1
  52. package/dist/server/app-rsc-route-matching.d.ts +14 -0
  53. package/dist/server/app-rsc-route-matching.js +42 -18
  54. package/dist/server/app-server-action-execution.js +16 -10
  55. package/dist/server/cache-control.d.ts +2 -0
  56. package/dist/server/cache-control.js +3 -1
  57. package/dist/server/image-local-patterns.d.ts +21 -0
  58. package/dist/server/image-local-patterns.js +59 -0
  59. package/dist/server/image-optimization.d.ts +23 -22
  60. package/dist/server/image-optimization.js +12 -2
  61. package/dist/server/metadata-route-response.js +24 -6
  62. package/dist/server/nitro-public-files-plugin.js +20 -8
  63. package/dist/server/pages-data-route.d.ts +5 -1
  64. package/dist/server/pages-data-route.js +14 -6
  65. package/dist/server/pages-request-pipeline.js +6 -5
  66. package/dist/server/pages-request-stage-entry.js +1 -0
  67. package/dist/server/prod-server.js +17 -9
  68. package/dist/server/request-pipeline.d.ts +5 -0
  69. package/dist/server/request-pipeline.js +11 -1
  70. package/dist/server/response-stage-cacheability.d.ts +7 -0
  71. package/dist/server/response-stage-cacheability.js +6 -5
  72. package/dist/server/worker-image-optimization.d.ts +2 -2
  73. package/dist/server/worker-image-optimization.js +18 -3
  74. package/dist/server/worker-utils.d.ts +8 -0
  75. package/dist/server/worker-utils.js +19 -2
  76. package/dist/shims/image-props.d.ts +6 -0
  77. package/dist/shims/image-props.js +39 -3
  78. package/dist/shims/image.js +3 -3
  79. package/dist/shims/slot.d.ts +3 -0
  80. package/dist/shims/slot.js +67 -9
  81. package/dist/utils/base-path.d.ts +7 -0
  82. package/dist/utils/base-path.js +11 -1
  83. package/package.json +1 -1
@@ -116,24 +116,6 @@ export type BuildPageElementsOptions<TModule extends AppPageModule = AppPageModu
116
116
  htmlLimitedBots?: string;
117
117
  scriptNonce?: string;
118
118
  };
119
- type AppPageNavigationParamModule = {
120
- default?: unknown;
121
- };
122
- type AppPageNavigationParamSlot = {
123
- default?: AppPageNavigationParamModule | null;
124
- page?: AppPageNavigationParamModule | null;
125
- slotPatternParts?: readonly string[] | null;
126
- slotParamNames?: readonly string[] | null;
127
- };
128
- type AppPageNavigationParamRoute = {
129
- params?: readonly string[] | null;
130
- slots?: Readonly<Record<string, AppPageNavigationParamSlot>> | null;
131
- };
132
- type AppPageNavigationParamInterceptOptions = {
133
- interceptPage?: unknown;
134
- interceptParams?: AppPageParams | null;
135
- interceptSlotKey?: string | null;
136
- };
137
119
  /**
138
120
  * Build the App Router element tree for a matched route.
139
121
  *
@@ -151,6 +133,15 @@ type AppPageNavigationParamInterceptOptions = {
151
133
  * {@link https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/create-metadata.tsx|create-metadata.tsx}.
152
134
  */
153
135
  export declare function buildPageElements<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule>(options: BuildPageElementsOptions<TModule, TErrorModule>): Promise<AppElements>;
136
+ /**
137
+ * The state key of each segment a sibling intercept's levels retain, from the
138
+ * app root down to the segment its element renders, so its Activity lists key
139
+ * like Next.js's for the intercepting route: the marker segment carries its
140
+ * marker, and a dynamic marker segment keys per value. The intercepting
141
+ * route's own layouts and boundaries render inside its page element, so the
142
+ * levels stop above the first of them.
143
+ */
144
+ export declare function resolveSiblingInterceptSegmentKeys(sourcePageSegments: readonly string[] | null | undefined, routeSegments: readonly string[] | null | undefined, opts: AppPageInterceptOptions | null | undefined, routeParams: AppPageParams, interceptParams: AppPageParams): readonly string[] | null;
154
145
  export declare const APP_PAGE_INTERCEPTION_MARKER_TRAVERSALS: readonly [{
155
146
  readonly prefix: "(...)";
156
147
  readonly levels: number;
@@ -172,7 +163,5 @@ export declare const APP_PAGE_INTERCEPTION_MARKER_TRAVERSALS: readonly [{
172
163
  */
173
164
  export declare function resolveInterceptedSlotIdentitySegments(sourcePageSegments: readonly string[] | null | undefined, slotKey: string): readonly AppPageSemanticSegment[] | null;
174
165
  export declare function resolveInterceptedSlotSegments(sourcePageSegments: readonly string[] | null | undefined, slotKey: string): readonly string[] | null;
175
- export declare function resolveSlotParamOverrides(route: AppPageNavigationParamRoute, routePath: string): Readonly<Record<string, AppPageParams>> | null;
176
- export declare function resolveAppPageNavigationParams(route: AppPageNavigationParamRoute, routeParams: AppPageParams, routePath: string, opts?: AppPageNavigationParamInterceptOptions | null): AppPageParams;
177
166
  //#endregion
178
167
  export type { AppPageErrorModule, AppPageRouteWiringRoute };
@@ -1,9 +1,8 @@
1
1
  import { isPromiseLike } from "../utils/promise.js";
2
2
  import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
3
- import { matchRoutePattern } from "../routing/route-pattern.js";
4
3
  import { AppElementsWire } from "./app-elements-wire.js";
5
4
  import "./app-elements.js";
6
- import { APP_PAGE_SEGMENT_KEY, isAppPageRouteGroupSegment, resolveAppPagePatternStateKey, resolveAppPageRouteStateKey } from "./app-page-segment-state.js";
5
+ import { APP_PAGE_SEGMENT_KEY, isAppPageRouteGroupSegment, resolveAppPagePatternStateKey, resolveAppPageRouteStateKey, resolveAppPageSemanticSegmentStateKey } from "./app-page-segment-state.js";
7
6
  import { ErrorBoundary, ForbiddenBoundary, NotFoundBoundary, UnauthorizedBoundary } from "../shims/error-boundary.js";
8
7
  import { withUseCacheLayoutMarker, withUseCachePageMarker } from "../shims/internal/app-page-props-cache-key.js";
9
8
  import { makeThenableParams } from "../shims/thenable-params.js";
@@ -12,15 +11,16 @@ import { createAppPageMetadataSearchParamsObserver, makeObservedAppPageSearchPar
12
11
  import { resolveAppPageBranchParams } from "./app-page-params.js";
13
12
  import { collectAppPageSearchParams, hasAppPageDynamicMetadata, prepareAppPageHead, resolveActiveParallelRouteHeadInputs, resolveInterceptLayoutsBesidePage, slotInterceptKeepsSourceHead } from "./app-page-head.js";
14
13
  import { createRouteNotFoundHeadPlanOptions, recoverAppPageRouteHead } from "./app-page-http-access-fallback-metadata.js";
15
- import { ClientPageRoot } from "../shims/client-page-root.js";
16
14
  import "./app-rsc-route-matching.js";
15
+ import { resolveSlotParamOverrides } from "./app-page-navigation-params.js";
17
16
  import { createAppPageRenderDependency, invokeAppComponent, isAppClientReference, isAppRenderSuspension, isReactOwnedAppComponent, renderAfterAppDependencies } from "./app-render-dependency.js";
18
17
  import { sanitizeErrorForClient } from "./app-rsc-errors.js";
19
18
  import { resolveAppPageModuleTraceSegment, traceCreateComponentTree, traceGetLayoutOrPageModule } from "./app-page-tracing.js";
20
19
  import { buildAppPageElements, createAppPageSourcePage, createAppPageTreePath, resolveAppPageLoadingModuleAtOrAbove } from "./app-page-route-wiring.js";
21
20
  import { DEFAULT_GLOBAL_ERROR_MODULE } from "./default-global-error-module.js";
22
- import { createAppPageRenderIdentity } from "./app-page-render-identity.js";
23
21
  import { shouldServeStreamingMetadata } from "./streaming-metadata.js";
22
+ import { ClientPageRoot } from "../shims/client-page-root.js";
23
+ import { createAppPageRenderIdentity } from "./app-page-render-identity.js";
24
24
  import { Fragment, Suspense, cache, createElement } from "react";
25
25
  //#region src/server/app-page-element-builder.ts
26
26
  /**
@@ -429,6 +429,7 @@ async function buildPageElements(options) {
429
429
  routePath,
430
430
  semanticPageIdentity,
431
431
  semanticInterceptionTargetRouteId: opts?.interceptTargetRouteGraphId ?? null,
432
+ siblingInterceptSegmentKeys: isSiblingIntercept ? resolveSiblingInterceptSegmentKeys(sourcePageSegments, route.routeSegments, opts, params, effectiveParams) : null,
432
433
  sourcePageSegments,
433
434
  rootNotFoundModule: rootNotFoundModule ?? null,
434
435
  rootForbiddenModule: rootForbiddenModule ?? null,
@@ -487,6 +488,37 @@ function buildSlotOverrides(route, routeParams, routePath, opts, interceptMetada
487
488
  }
488
489
  return Object.keys(overrides).length > 0 ? overrides : null;
489
490
  }
491
+ /**
492
+ * The state key of each segment a sibling intercept's levels retain, from the
493
+ * app root down to the segment its element renders, so its Activity lists key
494
+ * like Next.js's for the intercepting route: the marker segment carries its
495
+ * marker, and a dynamic marker segment keys per value. The intercepting
496
+ * route's own layouts and boundaries render inside its page element, so the
497
+ * levels stop above the first of them.
498
+ */
499
+ function resolveSiblingInterceptSegmentKeys(sourcePageSegments, routeSegments, opts, routeParams, interceptParams) {
500
+ if (!sourcePageSegments || !routeSegments) return null;
501
+ const markerIndex = sourcePageSegments.findIndex((segment) => APP_PAGE_INTERCEPTION_MARKER_TRAVERSALS.some(({ prefix }) => segment.startsWith(prefix)));
502
+ if (markerIndex < 0) return null;
503
+ if (routeSegments.length !== markerIndex || routeSegments.some((segment, index) => segment !== sourcePageSegments[index])) return null;
504
+ const branchTreePositions = [
505
+ ...(opts?.interceptLayouts ?? []).flatMap((layoutModule, index) => layoutModule?.default ? [opts?.interceptLayoutSegments?.[index]?.length ?? 0] : []),
506
+ ...opts?.interceptLoadingTreePositions ?? [],
507
+ ...opts?.interceptNotFoundTreePositions ?? [],
508
+ ...opts?.interceptForbiddenTreePositions ?? [],
509
+ ...opts?.interceptUnauthorizedTreePositions ?? [],
510
+ ...opts?.interceptErrorTreePositions ?? []
511
+ ];
512
+ const segmentCount = Math.max(markerIndex, Math.min(sourcePageSegments.length, markerIndex + Math.min(...branchTreePositions) - 1));
513
+ return sourcePageSegments.slice(0, segmentCount).map((segment, index) => {
514
+ const marker = index === markerIndex ? APP_PAGE_INTERCEPTION_MARKER_TRAVERSALS.find(({ prefix }) => segment.startsWith(prefix)) : void 0;
515
+ return resolveAppPageSemanticSegmentStateKey({
516
+ marker: marker?.prefix ?? null,
517
+ paramSource: "route",
518
+ segment: marker ? segment.slice(marker.prefix.length) : segment
519
+ }, index < markerIndex ? routeParams : interceptParams);
520
+ });
521
+ }
490
522
  const APP_PAGE_INTERCEPTION_MARKER_TRAVERSALS = [
491
523
  {
492
524
  prefix: "(...)",
@@ -581,49 +613,5 @@ function resolveInterceptedSlotSegments(sourcePageSegments, slotKey) {
581
613
  routeSegments.push(...sourcePageSegments.slice(markerIndex + 1).filter(isVisibleInterceptedSlotSegment));
582
614
  return routeSegments;
583
615
  }
584
- function resolveSlotParamOverrides(route, routePath) {
585
- const overrides = {};
586
- const slots = route.slots;
587
- if (slots) {
588
- let urlParts = null;
589
- const routeParamSet = collectParamNameSet(route.params);
590
- for (const [slotKey, slot] of Object.entries(slots)) {
591
- const patternParts = slot.slotPatternParts;
592
- const paramNames = slot.slotParamNames;
593
- if (!patternParts || patternParts.length === 0) continue;
594
- if (paramNames && paramNames.every((name) => routeParamSet.has(name))) continue;
595
- if (urlParts === null) urlParts = routePath.split("/").filter(Boolean);
596
- const matched = matchRoutePattern(urlParts, patternParts);
597
- if (!matched) continue;
598
- overrides[slotKey] = matched;
599
- }
600
- }
601
- return Object.keys(overrides).length > 0 ? overrides : null;
602
- }
603
- function mergeAppPageParams(target, source) {
604
- for (const [key, value] of Object.entries(source)) target[key] = value;
605
- }
606
- function isDefaultExportModule(module) {
607
- return typeof module === "object" && module !== null;
608
- }
609
- function hasDefaultExport(module) {
610
- if (!isDefaultExportModule(module)) return false;
611
- return module?.default !== null && module?.default !== void 0;
612
- }
613
- function resolveAppPageNavigationParams(route, routeParams, routePath, opts) {
614
- const navigationParams = { ...routeParams };
615
- const slotParamOverrides = resolveSlotParamOverrides(route, routePath);
616
- for (const [slotKey, slot] of Object.entries(route.slots ?? {})) {
617
- const isInterceptedSlot = opts?.interceptSlotKey === slotKey && opts.interceptSlotKey !== "__vinext_page_intercept" && hasDefaultExport(opts.interceptPage);
618
- if (!isInterceptedSlot && !hasDefaultExport(slot.page) && !hasDefaultExport(slot.default)) continue;
619
- mergeAppPageParams(navigationParams, isInterceptedSlot ? opts?.interceptParams ?? routeParams : slotParamOverrides?.[slotKey] ?? routeParams);
620
- }
621
- return navigationParams;
622
- }
623
- function collectParamNameSet(params) {
624
- const set = /* @__PURE__ */ new Set();
625
- if (params) for (const name of params) set.add(name);
626
- return set;
627
- }
628
616
  //#endregion
629
- export { APP_PAGE_INTERCEPTION_MARKER_TRAVERSALS, buildPageElements, resolveAppPageNavigationParams, resolveInterceptedSlotIdentitySegments, resolveInterceptedSlotSegments, resolveSlotParamOverrides };
617
+ export { APP_PAGE_INTERCEPTION_MARKER_TRAVERSALS, buildPageElements, resolveInterceptedSlotIdentitySegments, resolveInterceptedSlotSegments, resolveSiblingInterceptSegmentKeys };
@@ -0,0 +1,56 @@
1
+ import { FetchCacheMode } from "../shims/fetch-cache-state.js";
2
+ import "../shims/fetch-cache.js";
3
+ //#region src/server/app-page-intercept-segment-config.d.ts
4
+ type SegmentModule = Record<string, unknown>;
5
+ type AppPageInterceptSegmentConfigSlot = {
6
+ configLayouts?: readonly (SegmentModule | null | undefined)[] | null;
7
+ configLayoutTreePositions?: readonly number[] | null;
8
+ default?: SegmentModule | null;
9
+ layout?: SegmentModule | null;
10
+ layoutIndex?: number;
11
+ name?: string;
12
+ ownerTreePosition?: number | null;
13
+ page?: SegmentModule | null;
14
+ routeSegments?: readonly string[] | null;
15
+ };
16
+ /** The parts of the route an interception renders under that its config reads. */
17
+ export type AppPageInterceptSegmentConfigRoute = {
18
+ layouts?: readonly (SegmentModule | null | undefined)[];
19
+ layoutTreePositions?: readonly number[];
20
+ routeSegments?: readonly string[];
21
+ slots?: Readonly<Record<string, AppPageInterceptSegmentConfigSlot>> | null;
22
+ };
23
+ /** The parts of the interception's options its config reads. */
24
+ export type AppPageInterceptSegmentConfigOptions = {
25
+ interceptBranchSegments?: readonly string[] | null;
26
+ interceptLayoutSegments?: readonly (readonly string[])[] | null;
27
+ interceptLayouts?: readonly unknown[] | null;
28
+ interceptPage?: unknown;
29
+ interceptSlotKey: string;
30
+ interceptSourcePageSegments?: readonly string[] | null;
31
+ };
32
+ export type AppPageInterceptSegmentConfig = {
33
+ dynamicConfig: string | undefined;
34
+ dynamicStaleTimeSeconds: number | undefined;
35
+ fetchCacheMode: FetchCacheMode | null;
36
+ /** Whether any segment of the interception exports `generateStaticParams`. */
37
+ hasAnyGenerateStaticParams: boolean;
38
+ /** Whether one sits at or below the interception's last dynamic segment. */
39
+ hasGenerateStaticParams: boolean;
40
+ /** Whether the interception's own segments choose the edge runtime. */
41
+ isEdgeRuntime: boolean;
42
+ isStaticEligible: boolean;
43
+ isStaticGenerationEdgeRuntime: boolean;
44
+ revalidateSeconds: number | null;
45
+ };
46
+ /**
47
+ * Next.js renders an interception as its own route, whose loader tree holds
48
+ * the owner's layouts and the intercepting page, never the source's page, so
49
+ * its segment config comes from those modules alone. A sibling intercept's
50
+ * branch takes the children position below its owner folder; a slot
51
+ * intercept's branch takes its slot, beside the other slots of the owner's
52
+ * layouts, and children renders the owner's default.
53
+ */
54
+ export declare function resolveAppPageInterceptSegmentConfig(route: AppPageInterceptSegmentConfigRoute, interceptOpts: AppPageInterceptSegmentConfigOptions): AppPageInterceptSegmentConfig;
55
+ //#endregion
56
+ export {};
@@ -0,0 +1,90 @@
1
+ import { collectAppPageStaticGenerationRuntimes, collectAppPageStaticParamsWalkSegments, hasAppPageGenerateStaticParamsAtLastDynamicSegment, isAppPageStaticEligible, isEdgeRuntime, resolveAppPageSegmentConfig, resolveAppPageStaticGenerationRuntime } from "./app-segment-config.js";
2
+ import { SIBLING_PAGE_INTERCEPT_SLOT_KEY } from "./app-rsc-route-matching.js";
3
+ //#region src/server/app-page-intercept-segment-config.ts
4
+ /**
5
+ * Next.js renders an interception as its own route, whose loader tree holds
6
+ * the owner's layouts and the intercepting page, never the source's page, so
7
+ * its segment config comes from those modules alone. A sibling intercept's
8
+ * branch takes the children position below its owner folder; a slot
9
+ * intercept's branch takes its slot, beside the other slots of the owner's
10
+ * layouts, and children renders the owner's default.
11
+ */
12
+ function resolveAppPageInterceptSegmentConfig(route, interceptOpts) {
13
+ const routeLayouts = route.layouts ?? [];
14
+ const routeSegments = route.routeSegments ?? [];
15
+ const isSiblingIntercept = interceptOpts.interceptSlotKey === SIBLING_PAGE_INTERCEPT_SLOT_KEY;
16
+ const interceptedSlot = isSiblingIntercept ? void 0 : route.slots?.[interceptOpts.interceptSlotKey];
17
+ const branchSegments = interceptOpts.interceptBranchSegments ?? [];
18
+ const interceptLayouts = interceptOpts.interceptLayouts ?? [];
19
+ const interceptLayoutPositions = (interceptOpts.interceptLayoutSegments ?? []).map((segments) => segments.length);
20
+ const ownerPosition = isSiblingIntercept ? interceptOpts.interceptSourcePageSegments ? interceptOpts.interceptSourcePageSegments.length - branchSegments.length : routeSegments.length : interceptedSlot?.ownerTreePosition ?? route.layoutTreePositions?.[interceptedSlot?.layoutIndex ?? -1] ?? routeSegments.length;
21
+ const ownerLayoutIndexes = routeLayouts.flatMap((_, index) => (route.layoutTreePositions?.[index] ?? 0) <= ownerPosition ? [index] : []);
22
+ const ownerLayouts = ownerLayoutIndexes.map((index) => routeLayouts[index]);
23
+ const ownerLayoutPositions = ownerLayoutIndexes.map((index) => route.layoutTreePositions?.[index] ?? 0);
24
+ const ownerSegments = routeSegments.slice(0, ownerPosition);
25
+ const page = interceptOpts.interceptPage;
26
+ const retainedBranches = Object.entries(route.slots ?? {}).flatMap(([slotKey, slot]) => slotKey !== interceptOpts.interceptSlotKey && (slot.ownerTreePosition ?? route.layoutTreePositions?.[slot.layoutIndex ?? 0] ?? 0) <= ownerPosition ? [{
27
+ configLayouts: slot.page ? slot.configLayouts : null,
28
+ configLayoutTreePositions: slot.page ? slot.configLayoutTreePositions : null,
29
+ isDefault: !slot.page,
30
+ layout: slot.page ? slot.layout : null,
31
+ name: slot.name,
32
+ ownerTreePosition: slot.ownerTreePosition,
33
+ page: slot.page ?? slot.default,
34
+ routeSegments: slot.routeSegments
35
+ }] : []);
36
+ const tree = isSiblingIntercept ? {
37
+ layouts: [...ownerLayouts, ...interceptLayouts],
38
+ layoutTreePositions: [...ownerLayoutPositions, ...interceptLayoutPositions.map((position) => ownerPosition + position)],
39
+ page,
40
+ parallelBranches: retainedBranches,
41
+ routeSegments: [...ownerSegments, ...branchSegments]
42
+ } : {
43
+ layouts: ownerLayouts,
44
+ layoutTreePositions: ownerLayoutPositions,
45
+ page: null,
46
+ parallelBranches: [...retainedBranches, {
47
+ configLayouts: interceptLayouts,
48
+ configLayoutTreePositions: interceptLayoutPositions,
49
+ layout: interceptedSlot?.layout,
50
+ name: interceptedSlot?.name,
51
+ ownerTreePosition: ownerPosition,
52
+ page,
53
+ routeSegments: branchSegments
54
+ }],
55
+ routeSegments: ownerSegments
56
+ };
57
+ const config = resolveAppPageSegmentConfig({
58
+ layouts: [
59
+ ...ownerLayouts,
60
+ ...isSiblingIntercept ? [] : [interceptedSlot?.layout],
61
+ ...interceptLayouts
62
+ ],
63
+ page,
64
+ parallelBranches: retainedBranches,
65
+ parallelPages: retainedBranches.map((branch) => branch.page)
66
+ });
67
+ const walkSegments = collectAppPageStaticParamsWalkSegments(tree);
68
+ const hasGenerateStaticParams = hasAppPageGenerateStaticParamsAtLastDynamicSegment(tree);
69
+ const isDynamicRoute = walkSegments.some((segment) => segment.dynamic);
70
+ const isStaticGenerationEdgeRuntime = isEdgeRuntime(resolveAppPageStaticGenerationRuntime(collectAppPageStaticGenerationRuntimes(tree)));
71
+ return {
72
+ dynamicConfig: config.dynamicConfig,
73
+ dynamicStaleTimeSeconds: config.dynamicStaleTimeSeconds,
74
+ fetchCacheMode: config.fetchCache ?? null,
75
+ hasAnyGenerateStaticParams: walkSegments.some((segment) => segment.generateStaticParams),
76
+ hasGenerateStaticParams,
77
+ isEdgeRuntime: isEdgeRuntime(config.runtime),
78
+ isStaticEligible: isAppPageStaticEligible({
79
+ dynamicConfig: config.dynamicConfig,
80
+ hasGenerateStaticParams,
81
+ isDynamicRoute,
82
+ isStaticGenerationEdgeRuntime,
83
+ revalidateSeconds: config.revalidateSeconds
84
+ }),
85
+ isStaticGenerationEdgeRuntime,
86
+ revalidateSeconds: config.revalidateSeconds
87
+ };
88
+ }
89
+ //#endregion
90
+ export { resolveAppPageInterceptSegmentConfig };
@@ -0,0 +1,23 @@
1
+ import { AppPageParams } from "./app-page-boundary.js";
2
+ //#region src/server/app-page-navigation-params.d.ts
3
+ export type AppPageNavigationParamSlot = {
4
+ __loadDefault?: unknown;
5
+ __loadPage?: unknown;
6
+ default?: unknown;
7
+ page?: unknown;
8
+ slotPatternParts?: readonly string[] | null;
9
+ slotParamNames?: readonly string[] | null;
10
+ };
11
+ type AppPageNavigationParamRoute = {
12
+ params?: readonly string[] | null;
13
+ slots?: Readonly<Record<string, AppPageNavigationParamSlot>> | null;
14
+ };
15
+ type AppPageNavigationParamInterceptOptions = {
16
+ interceptPage?: unknown;
17
+ interceptParams?: AppPageParams | null;
18
+ interceptSlotKey?: string | null;
19
+ };
20
+ export declare function resolveSlotParamOverrides(route: AppPageNavigationParamRoute, routePath: string): Readonly<Record<string, AppPageParams>> | null;
21
+ export declare function resolveAppPageNavigationParams(route: AppPageNavigationParamRoute, routeParams: AppPageParams, routePath: string, opts?: AppPageNavigationParamInterceptOptions | null): AppPageParams;
22
+ //#endregion
23
+ export {};
@@ -0,0 +1,49 @@
1
+ import { matchRoutePattern } from "../routing/route-pattern.js";
2
+ import "./app-rsc-route-matching.js";
3
+ //#region src/server/app-page-navigation-params.ts
4
+ function resolveSlotParamOverrides(route, routePath) {
5
+ const overrides = {};
6
+ const slots = route.slots;
7
+ if (slots) {
8
+ let urlParts = null;
9
+ const routeParamSet = collectParamNameSet(route.params);
10
+ for (const [slotKey, slot] of Object.entries(slots)) {
11
+ const patternParts = slot.slotPatternParts;
12
+ const paramNames = slot.slotParamNames;
13
+ if (!patternParts || patternParts.length === 0) continue;
14
+ if (paramNames && paramNames.every((name) => routeParamSet.has(name))) continue;
15
+ if (urlParts === null) urlParts = routePath.split("/").filter(Boolean);
16
+ const matched = matchRoutePattern(urlParts, patternParts);
17
+ if (!matched) continue;
18
+ overrides[slotKey] = matched;
19
+ }
20
+ }
21
+ return Object.keys(overrides).length > 0 ? overrides : null;
22
+ }
23
+ function mergeAppPageParams(target, source) {
24
+ for (const [key, value] of Object.entries(source)) target[key] = value;
25
+ }
26
+ function isDefaultExportModule(module) {
27
+ return typeof module === "object" && module !== null;
28
+ }
29
+ function hasDefaultExport(module) {
30
+ if (!isDefaultExportModule(module)) return false;
31
+ return module?.default !== null && module?.default !== void 0;
32
+ }
33
+ function resolveAppPageNavigationParams(route, routeParams, routePath, opts) {
34
+ const navigationParams = { ...routeParams };
35
+ const slotParamOverrides = resolveSlotParamOverrides(route, routePath);
36
+ for (const [slotKey, slot] of Object.entries(route.slots ?? {})) {
37
+ const isInterceptedSlot = opts?.interceptSlotKey === slotKey && opts.interceptSlotKey !== "__vinext_page_intercept" && hasDefaultExport(opts.interceptPage);
38
+ if (!isInterceptedSlot && !hasDefaultExport(slot.page) && !hasDefaultExport(slot.default) && !slot.__loadPage && !slot.__loadDefault) continue;
39
+ mergeAppPageParams(navigationParams, isInterceptedSlot ? opts?.interceptParams ?? routeParams : slotParamOverrides?.[slotKey] ?? routeParams);
40
+ }
41
+ return navigationParams;
42
+ }
43
+ function collectParamNameSet(params) {
44
+ const set = /* @__PURE__ */ new Set();
45
+ if (params) for (const name of params) set.add(name);
46
+ return set;
47
+ }
48
+ //#endregion
49
+ export { resolveAppPageNavigationParams, resolveSlotParamOverrides };
@@ -150,9 +150,12 @@ type RenderAppPageLifecycleOptions = RenderAppPageLifecycleOptionsBase & ({
150
150
  * static, draft mode, `force-dynamic`, `revalidate = 0`, or a dynamic API read
151
151
  * before the response left the render. Responses that leave the render before
152
152
  * its response policy, such as error boundaries and special errors, get the
153
- * same header as the normal render.
153
+ * same header as the normal render. One not yet known dynamic is never stored
154
+ * either, and its body can still reach a dynamic API after its headers leave,
155
+ * so it is sent like a dynamic render (see resolveUnstoredEarlyResponseCacheControl
156
+ * for the responses that aren't).
154
157
  */
155
- export declare function applyIneligibleRouteCachePolicy(response: Response, options: Pick<RenderAppPageLifecycleOptions, "dynamicStaleTimeSeconds" | "isDraftMode" | "isDynamicError" | "isForceDynamic" | "isForceStatic" | "isProduction" | "isProgressiveActionRender" | "isRscRequest" | "isStaticEligible" | "middlewareContext" | "peekDynamicUsage" | "revalidateSeconds" | "scriptNonce">, requestCacheLife?: AppPageRequestCacheLife | null): Response;
158
+ export declare function applyIneligibleRouteCachePolicy(response: Response, options: Pick<RenderAppPageLifecycleOptions, "dynamicStaleTimeSeconds" | "isDraftMode" | "isDynamicError" | "isForceDynamic" | "isForceStatic" | "isPrerender" | "isProduction" | "isProgressiveActionRender" | "isRscRequest" | "isStaticEligible" | "middlewareContext" | "peekDynamicUsage" | "revalidateSeconds" | "scriptNonce">, requestCacheLife?: AppPageRequestCacheLife | null): Response;
156
159
  export declare function renderAppPageLifecycle(options: RenderAppPageLifecycleOptions): Promise<Response>;
157
160
  //#endregion
158
161
  export {};
@@ -24,10 +24,10 @@ import { appendRscCompletionMetadata } from "./rsc-completion-metadata.js";
24
24
  import { isBailoutToCSRError } from "../shims/navigation-errors.js";
25
25
  import { applyClientStaleTimeHeader, applyEdgeRuntimeHeader, buildAppPageHtmlResponse, buildAppPageRscResponse, resolveAppPageHtmlResponsePolicy, resolveAppPageRscResponsePolicy, resolveUncacheableCacheControl } from "./app-page-response.js";
26
26
  import { buildAppPageFontLinkHeader, readAppPageBinaryStream, resolveAppPageCaughtNotFoundBoundaries, resolveAppPageShellSpecialError, resolveAppPageSpecialError, resolveAppPageSpecialErrorStoredHeaders, teeAppPageRscStreamForCapture } from "./app-page-execution.js";
27
+ import { buildAppPageHtmlDocumentResponse, buildAppPageLinkHeader, createAppPageFontData, createAppPageRscErrorTracker, createAppPageSsrErrorHandler, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery } from "./app-page-stream.js";
27
28
  import { getAppPageLayoutGate } from "./app-page-layout-gate.js";
28
29
  import { hasDigest } from "./app-rsc-errors.js";
29
30
  import { recordAppPageRenderError, traceAppPageRender } from "./app-page-tracing.js";
30
- import { buildAppPageHtmlDocumentResponse, buildAppPageLinkHeader, createAppPageFontData, createAppPageRscErrorTracker, createAppPageSsrErrorHandler, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery } from "./app-page-stream.js";
31
31
  import { createAppPageHtmlOutputScope, createAppPageRenderObservation, createAppPageRscOutputScope } from "./app-page-render-observation.js";
32
32
  import { createAppPageRscRenderStatusResolver } from "./app-page-rsc-render-status.js";
33
33
  import { finalizeAppPageCacheabilityEvaluationResponse, finalizeAppPageHtmlCacheResponse, finalizeAppPageRscCacheResponse } from "./app-page-cache-finalizer.js";
@@ -70,11 +70,14 @@ function applyRequestCacheLife(options) {
70
70
  * static, draft mode, `force-dynamic`, `revalidate = 0`, or a dynamic API read
71
71
  * before the response left the render. Responses that leave the render before
72
72
  * its response policy, such as error boundaries and special errors, get the
73
- * same header as the normal render.
73
+ * same header as the normal render. One not yet known dynamic is never stored
74
+ * either, and its body can still reach a dynamic API after its headers leave,
75
+ * so it is sent like a dynamic render (see resolveUnstoredEarlyResponseCacheControl
76
+ * for the responses that aren't).
74
77
  */
75
78
  function applyIneligibleRouteCachePolicy(response, options, requestCacheLife = null) {
76
- const cacheControl = resolveEarlyResponseCacheControl(options);
77
79
  const isRscRedirect = options.isRscRequest && response.headers.has("X-Vinext-Rsc-Redirect");
80
+ const cacheControl = resolveEarlyResponseCacheControl(options) ?? (isRscRedirect ? null : resolveUnstoredEarlyResponseCacheControl(response, options));
78
81
  if (!cacheControl) {
79
82
  const staleTimeSeconds = isRscRedirect ? resolveClientStaleTimeSeconds(requestCacheLife) : void 0;
80
83
  if (staleTimeSeconds === void 0) return response;
@@ -108,6 +111,17 @@ function resolveEarlyResponseCacheControl(options) {
108
111
  const ignoresDynamicUsage = (options.isForceStatic || options.isDynamicError) && (options.revalidateSeconds === null || options.revalidateSeconds === Infinity);
109
112
  return options.revalidateSeconds === 0 || !ignoresDynamicUsage && (options.peekDynamicUsage?.() ?? peekDynamicUsage()) ? uncacheable : null;
110
113
  }
114
+ /**
115
+ * The normal render can still become a cache entry, so it sends its pending
116
+ * policy until the stream proves it static. An early response never does, and
117
+ * nothing checks it again, so one with a body takes the never-cache header
118
+ * unless it already carries a policy, such as middleware's. A force-static or
119
+ * dynamic = "error" render can't read the request, and dev and the build's
120
+ * prerender, which reads `no-store` as a dynamic render, keep no policy.
121
+ */
122
+ function resolveUnstoredEarlyResponseCacheControl(response, options) {
123
+ return options.isProduction && options.isPrerender !== true && !options.isForceStatic && !options.isDynamicError && response.body !== null && !response.headers.has("cache-control") ? NEVER_CACHE_CONTROL : null;
124
+ }
111
125
  function resolveAppPageCacheWriteRevalidateSeconds(options) {
112
126
  if (!options.isStaticEligible) return null;
113
127
  if (options.revalidateSeconds === null && (options.isForceStatic || options.isDynamicError)) return Infinity;
@@ -100,6 +100,25 @@ type AppPageInterceptMatch<TPage = unknown> = {
100
100
  targetPatternParts?: readonly string[];
101
101
  targetRouteGraphId?: string | null;
102
102
  };
103
+ export type AppPageInterceptState<TRoute, TPage> = {
104
+ kind: "none";
105
+ } | {
106
+ kind: "current-route";
107
+ intercept: AppPageInterceptMatch<TPage>;
108
+ } | {
109
+ kind: "source-route";
110
+ intercept: AppPageInterceptMatch<TPage>;
111
+ sourceRoute: TRoute;
112
+ };
113
+ type AppPageInterceptStateLookup<TRoute, TPage> = {
114
+ cleanPathname: string;
115
+ currentRoute: TRoute;
116
+ findIntercept: (pathname: string) => AppPageInterceptMatch<TPage> | null;
117
+ getSourceRoute: (sourceRouteIndex: number) => Awaitable<TRoute | undefined>;
118
+ /** The state this request already resolved, which is then reused. */
119
+ interceptState?: AppPageInterceptState<TRoute, TPage>;
120
+ isRscRequest: boolean;
121
+ };
103
122
  type ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOpts> = {
104
123
  cleanPathname: string;
105
124
  currentParams: AppPageParams;
@@ -107,6 +126,7 @@ type ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOp
107
126
  findIntercept: (pathname: string) => AppPageInterceptMatch<TPage> | null;
108
127
  getRouteParamNames: (route: TRoute) => readonly string[];
109
128
  getSourceRoute: (sourceRouteIndex: number) => Awaitable<TRoute | undefined>;
129
+ interceptState?: AppPageInterceptState<TRoute, TPage>;
110
130
  isRscRequest: boolean;
111
131
  toInterceptOpts: (intercept: AppPageInterceptMatch<TPage>) => TInterceptOpts;
112
132
  };
@@ -128,10 +148,11 @@ type ResolveAppPageInterceptOptions<TRoute, TPage, TInterceptOpts, TElement> = {
128
148
  findIntercept: (pathname: string) => AppPageInterceptMatch<TPage> | null;
129
149
  getRouteParamNames: (route: TRoute) => readonly string[];
130
150
  getSourceRoute: (sourceRouteIndex: number) => Awaitable<TRoute | undefined>;
151
+ interceptState?: AppPageInterceptState<TRoute, TPage>;
131
152
  isRscRequest: boolean;
132
153
  resolveNavigationParams: (route: TRoute, params: AppPageParams, pathname: string, interceptOpts: TInterceptOpts) => AppPageParams;
133
- renderInterceptResponse: (route: TRoute, element: TElement) => Promise<Response> | Response;
134
- resolveSearchParams?: (route: TRoute, searchParams: URLSearchParams) => Awaitable<URLSearchParams>;
154
+ renderInterceptResponse: (route: TRoute, element: TElement, navigationParams: AppPageParams) => Promise<Response> | Response;
155
+ resolveSearchParams?: (route: TRoute, searchParams: URLSearchParams, interceptOpts: TInterceptOpts) => Awaitable<URLSearchParams>;
135
156
  searchParams: URLSearchParams;
136
157
  setNavigationContext: (context: {
137
158
  params: AppPageParams;
@@ -146,6 +167,7 @@ type ResolveAppPageInterceptResult<TInterceptOpts> = {
146
167
  };
147
168
  export declare function resolveAppPageGenerateStaticParamsSources(options: ResolveAppPageGenerateStaticParamsSourcesOptions): GenerateStaticParamsSource[];
148
169
  export declare function validateAppPageDynamicParams(options: ValidateAppPageDynamicParamsOptions): Promise<Response | null>;
170
+ export declare function resolveAppPageInterceptState<TRoute, TPage>(options: AppPageInterceptStateLookup<TRoute, TPage>): Promise<AppPageInterceptState<TRoute, TPage>>;
149
171
  export declare function resolveAppPageInterceptionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageInterceptionRerenderTargetResult<TRoute, TInterceptOpts>>;
150
172
  export declare function resolveAppPageActionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageActionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageActionRerenderTargetResult<TRoute, TInterceptOpts>>;
151
173
  export declare function resolveAppPageIntercept<TRoute, TPage, TInterceptOpts, TElement>(options: ResolveAppPageInterceptOptions<TRoute, TPage, TInterceptOpts, TElement>): Promise<ResolveAppPageInterceptResult<TInterceptOpts>>;
@@ -278,6 +278,7 @@ async function validateAppPageDynamicParams(options) {
278
278
  return null;
279
279
  }
280
280
  async function resolveAppPageInterceptState(options) {
281
+ if (options.interceptState) return options.interceptState;
281
282
  if (!options.isRscRequest) return { kind: "none" };
282
283
  const intercept = options.findIntercept(options.cleanPathname);
283
284
  if (!intercept) return { kind: "none" };
@@ -301,10 +302,9 @@ async function resolveAppPageInterceptionRerenderTarget(options) {
301
302
  cleanPathname: options.cleanPathname,
302
303
  currentRoute: options.currentRoute,
303
304
  findIntercept: options.findIntercept,
304
- getRouteParamNames: options.getRouteParamNames,
305
305
  getSourceRoute: options.getSourceRoute,
306
- isRscRequest: options.isRscRequest,
307
- toInterceptOpts: options.toInterceptOpts
306
+ interceptState: options.interceptState,
307
+ isRscRequest: options.isRscRequest
308
308
  });
309
309
  if (interceptState.kind === "source-route") {
310
310
  const sourceMatchedParams = interceptState.intercept.sourceMatchedParams ?? interceptState.intercept.matchedParams;
@@ -333,10 +333,9 @@ async function resolveAppPageIntercept(options) {
333
333
  cleanPathname: options.cleanPathname,
334
334
  currentRoute: options.currentRoute,
335
335
  findIntercept: options.findIntercept,
336
- getRouteParamNames: options.getRouteParamNames,
337
336
  getSourceRoute: options.getSourceRoute,
338
- isRscRequest: options.isRscRequest,
339
- toInterceptOpts: options.toInterceptOpts
337
+ interceptState: options.interceptState,
338
+ isRscRequest: options.isRscRequest
340
339
  });
341
340
  if (interceptState.kind === "source-route") {
342
341
  const renderRoute = interceptState.sourceRoute;
@@ -346,17 +345,18 @@ async function resolveAppPageIntercept(options) {
346
345
  ...sourceMatchedParams,
347
346
  ...interceptState.intercept.matchedParams
348
347
  };
349
- const renderSearchParams = options.resolveSearchParams ? await options.resolveSearchParams(renderRoute, options.searchParams) : options.searchParams;
348
+ const renderSearchParams = options.resolveSearchParams ? await options.resolveSearchParams(renderRoute, options.searchParams, interceptOpts) : options.searchParams;
350
349
  const renderParams = pickRouteParams(sourceMatchedParams, options.getRouteParamNames(interceptState.sourceRoute));
350
+ const resolvedNavigationParams = options.resolveNavigationParams(renderRoute, navigationParams, options.cleanPathname, interceptOpts);
351
351
  options.setNavigationContext({
352
- params: options.resolveNavigationParams(renderRoute, navigationParams, options.cleanPathname, interceptOpts),
352
+ params: resolvedNavigationParams,
353
353
  pathname: options.cleanPathname,
354
354
  searchParams: renderSearchParams
355
355
  });
356
356
  const interceptElement = await options.buildPageElement(renderRoute, renderParams, interceptOpts, renderSearchParams);
357
357
  return {
358
358
  interceptOpts: void 0,
359
- response: await options.renderInterceptResponse(renderRoute, interceptElement)
359
+ response: await options.renderInterceptResponse(renderRoute, interceptElement, resolvedNavigationParams)
360
360
  };
361
361
  }
362
362
  return {
@@ -385,4 +385,4 @@ async function buildAppPageElement(options) {
385
385
  }
386
386
  }
387
387
  //#endregion
388
- export { buildAppPageElement, resolveAppPageActionRerenderTarget, resolveAppPageGenerateStaticParamsSources, resolveAppPageIntercept, resolveAppPageInterceptionRerenderTarget, validateAppPageDynamicParams };
388
+ export { buildAppPageElement, resolveAppPageActionRerenderTarget, resolveAppPageGenerateStaticParamsSources, resolveAppPageIntercept, resolveAppPageInterceptState, resolveAppPageInterceptionRerenderTarget, validateAppPageDynamicParams };
@@ -35,6 +35,8 @@ export type AppPageRouteWiringSlot<TModule extends AppPageModule = AppPageModule
35
35
  configLayouts?: readonly (TModule | null | undefined)[] | null;
36
36
  configLayoutTreePositions?: readonly number[] | null;
37
37
  error?: TErrorModule | null;
38
+ errors?: readonly (TErrorModule | null | undefined)[] | null;
39
+ errorTreePositions?: readonly number[] | null;
38
40
  layout?: TModule | null;
39
41
  layoutIndex: number;
40
42
  loading?: TModule | null;
@@ -99,6 +101,8 @@ export type AppPageRouteWiringRoute<TModule extends AppPageModule = AppPageModul
99
101
  id: string;
100
102
  ownerTreePath: string;
101
103
  state: AppElementsSlotBinding["state"];
104
+ /** A slot sub-route whose children its route segments don't describe. */
105
+ synthetic?: boolean;
102
106
  } | null;
103
107
  /**
104
108
  * Static sibling segment names at each dynamic URL level for this route. Used
@@ -241,6 +245,12 @@ type BuildAppPageElementsOptions<TModule extends AppPageModule = AppPageModule,
241
245
  sourceRouteGraphId: string;
242
246
  }> | null;
243
247
  semanticInterceptionTargetRouteId?: string | null;
248
+ /**
249
+ * A sibling intercept's segment state keys, app root first, down to the
250
+ * segment its page element renders. Its BFCache segment levels follow these
251
+ * rather than the source route's segments.
252
+ */
253
+ siblingInterceptSegmentKeys?: readonly string[] | null;
244
254
  sourcePageSegments?: readonly string[] | null;
245
255
  };
246
256
  export type AppPageTemplateEntry<TModule extends AppPageModule = AppPageModule> = {