vinext 1.0.0-beta.2 → 1.0.0-beta.4

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 (174) hide show
  1. package/README.md +6 -9
  2. package/dist/build/client-build-config.js +7 -1
  3. package/dist/build/precompress.js +1 -1
  4. package/dist/build/prerender.d.ts +2 -1
  5. package/dist/build/prerender.js +12 -2
  6. package/dist/check.js +4 -0
  7. package/dist/cli-args.d.ts +2 -1
  8. package/dist/cli-args.js +10 -0
  9. package/dist/cli.js +14 -8
  10. package/dist/client/navigation-runtime.d.ts +6 -0
  11. package/dist/client/navigation-runtime.js +1 -1
  12. package/dist/config/config-matchers.js +4 -1
  13. package/dist/config/dotenv.d.ts +3 -4
  14. package/dist/config/dotenv.js +30 -22
  15. package/dist/config/next-config.d.ts +16 -5
  16. package/dist/config/next-config.js +7 -0
  17. package/dist/entries/app-browser-entry.d.ts +4 -10
  18. package/dist/entries/app-browser-entry.js +20 -4
  19. package/dist/entries/app-rsc-entry.js +21 -3
  20. package/dist/entries/app-rsc-manifest.js +19 -0
  21. package/dist/entries/pages-client-entry.js +0 -1
  22. package/dist/entries/pages-server-entry.js +13 -3
  23. package/dist/index.js +122 -49
  24. package/dist/init-cloudflare.d.ts +3 -1
  25. package/dist/init-cloudflare.js +59 -5
  26. package/dist/plugins/client-reference-dedup.js +7 -5
  27. package/dist/plugins/extensionless-dynamic-import.js +23 -18
  28. package/dist/plugins/ignore-dynamic-requests.js +3 -1
  29. package/dist/plugins/import-meta-url.js +17 -6
  30. package/dist/plugins/require-context.js +20 -15
  31. package/dist/plugins/sass.js +3 -4
  32. package/dist/plugins/transform-cache.d.ts +19 -0
  33. package/dist/plugins/transform-cache.js +36 -0
  34. package/dist/routing/app-route-graph.d.ts +14 -6
  35. package/dist/routing/app-route-graph.js +106 -16
  36. package/dist/server/api-handler.d.ts +2 -0
  37. package/dist/server/api-handler.js +7 -6
  38. package/dist/server/app-bfcache-identity.js +28 -2
  39. package/dist/server/app-browser-entry.js +175 -75
  40. package/dist/server/app-browser-navigation-controller.d.ts +23 -21
  41. package/dist/server/app-browser-navigation-controller.js +30 -5
  42. package/dist/server/app-browser-prefetch-response.d.ts +9 -0
  43. package/dist/server/app-browser-prefetch-response.js +32 -0
  44. package/dist/server/app-browser-state.d.ts +8 -3
  45. package/dist/server/app-browser-state.js +9 -3
  46. package/dist/server/app-page-cache-finalizer.js +1 -1
  47. package/dist/server/app-page-cache.d.ts +1 -1
  48. package/dist/server/app-page-cache.js +16 -2
  49. package/dist/server/app-page-dispatch.d.ts +9 -1
  50. package/dist/server/app-page-dispatch.js +37 -8
  51. package/dist/server/app-page-element-builder.d.ts +4 -1
  52. package/dist/server/app-page-element-builder.js +34 -7
  53. package/dist/server/app-page-head.d.ts +3 -3
  54. package/dist/server/app-page-head.js +58 -13
  55. package/dist/server/app-page-http-access-fallback-metadata.d.ts +2 -2
  56. package/dist/server/app-page-http-access-fallback-metadata.js +9 -1
  57. package/dist/server/app-page-probe.d.ts +4 -0
  58. package/dist/server/app-page-probe.js +6 -2
  59. package/dist/server/app-page-render.d.ts +1 -1
  60. package/dist/server/app-page-render.js +13 -3
  61. package/dist/server/app-page-request.d.ts +3 -1
  62. package/dist/server/app-page-request.js +4 -13
  63. package/dist/server/app-page-response.d.ts +2 -0
  64. package/dist/server/app-page-response.js +7 -2
  65. package/dist/server/app-page-route-wiring.d.ts +10 -2
  66. package/dist/server/app-page-route-wiring.js +269 -59
  67. package/dist/server/app-pages-bridge.d.ts +2 -1
  68. package/dist/server/app-pages-bridge.js +3 -1
  69. package/dist/server/app-route-handler-cache.js +4 -0
  70. package/dist/server/app-route-handler-dispatch.js +9 -3
  71. package/dist/server/app-route-handler-response.js +1 -1
  72. package/dist/server/app-route-module-loader.d.ts +6 -0
  73. package/dist/server/app-route-module-loader.js +3 -0
  74. package/dist/server/app-router-entry.js +6 -3
  75. package/dist/server/app-rsc-cache-busting.d.ts +7 -0
  76. package/dist/server/app-rsc-cache-busting.js +6 -0
  77. package/dist/server/app-rsc-errors.js +10 -0
  78. package/dist/server/app-rsc-handler.d.ts +2 -0
  79. package/dist/server/app-rsc-handler.js +30 -13
  80. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  81. package/dist/server/app-rsc-render-mode.js +4 -1
  82. package/dist/server/app-rsc-request-normalization.js +65 -4
  83. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  84. package/dist/server/app-rsc-route-matching.js +6 -0
  85. package/dist/server/app-server-action-execution.d.ts +1 -1
  86. package/dist/server/app-ssr-entry.js +4 -4
  87. package/dist/server/app-visited-response-cache.d.ts +6 -1
  88. package/dist/server/app-visited-response-cache.js +49 -1
  89. package/dist/server/cache-control.d.ts +2 -2
  90. package/dist/server/cache-control.js +2 -1
  91. package/dist/server/dev-server.d.ts +3 -1
  92. package/dist/server/dev-server.js +204 -348
  93. package/dist/server/edge-api-runtime.d.ts +10 -1
  94. package/dist/server/edge-api-runtime.js +29 -1
  95. package/dist/server/headers.d.ts +4 -2
  96. package/dist/server/headers.js +6 -3
  97. package/dist/server/isr-cache.d.ts +10 -33
  98. package/dist/server/isr-cache.js +33 -43
  99. package/dist/server/isr-decision.d.ts +2 -2
  100. package/dist/server/pages-api-route.d.ts +4 -0
  101. package/dist/server/pages-api-route.js +6 -3
  102. package/dist/server/pages-asset-tags.d.ts +1 -0
  103. package/dist/server/pages-asset-tags.js +6 -4
  104. package/dist/server/pages-dev-hydration.js +0 -1
  105. package/dist/server/pages-document-asset-props.d.ts +26 -0
  106. package/dist/server/pages-document-asset-props.js +69 -0
  107. package/dist/server/pages-get-initial-props.d.ts +1 -1
  108. package/dist/server/pages-get-initial-props.js +1 -4
  109. package/dist/server/pages-i18n.js +64 -16
  110. package/dist/server/pages-node-compat.d.ts +2 -0
  111. package/dist/server/pages-node-compat.js +10 -6
  112. package/dist/server/pages-page-data.d.ts +61 -9
  113. package/dist/server/pages-page-data.js +334 -74
  114. package/dist/server/pages-page-handler.d.ts +6 -1
  115. package/dist/server/pages-page-handler.js +119 -20
  116. package/dist/server/pages-page-response.d.ts +7 -3
  117. package/dist/server/pages-page-response.js +41 -33
  118. package/dist/server/pages-request-pipeline.d.ts +7 -0
  119. package/dist/server/pages-request-pipeline.js +5 -1
  120. package/dist/server/pages-revalidate.d.ts +1 -1
  121. package/dist/server/pages-revalidate.js +54 -8
  122. package/dist/server/pages-router-entry.d.ts +1 -0
  123. package/dist/server/pages-router-entry.js +10 -4
  124. package/dist/server/prerender-manifest.d.ts +1 -0
  125. package/dist/server/prod-server.d.ts +2 -1
  126. package/dist/server/prod-server.js +38 -13
  127. package/dist/server/revalidation-host.d.ts +11 -0
  128. package/dist/server/revalidation-host.js +19 -0
  129. package/dist/server/seed-cache.js +1 -1
  130. package/dist/server/worker-revalidation-context.d.ts +13 -0
  131. package/dist/server/worker-revalidation-context.js +30 -0
  132. package/dist/shims/app-router-scroll-state.d.ts +2 -1
  133. package/dist/shims/app-router-scroll-state.js +7 -2
  134. package/dist/shims/cache-handler.d.ts +1 -1
  135. package/dist/shims/cache-handler.js +10 -7
  136. package/dist/shims/cache-runtime.d.ts +6 -0
  137. package/dist/shims/cache-runtime.js +4 -1
  138. package/dist/shims/cache.js +1 -1
  139. package/dist/shims/document.js +15 -2
  140. package/dist/shims/error.js +1 -1
  141. package/dist/shims/fetch-cache.js +1 -1
  142. package/dist/shims/font-google-base.js +17 -8
  143. package/dist/shims/font-local.js +41 -10
  144. package/dist/shims/hash-scroll.d.ts +1 -1
  145. package/dist/shims/hash-scroll.js +2 -1
  146. package/dist/shims/headers.d.ts +10 -13
  147. package/dist/shims/headers.js +85 -36
  148. package/dist/shims/internal/cookie-serialize.d.ts +10 -9
  149. package/dist/shims/internal/cookie-serialize.js +9 -8
  150. package/dist/shims/internal/interpolate-as.d.ts +15 -1
  151. package/dist/shims/internal/interpolate-as.js +27 -2
  152. package/dist/shims/link.d.ts +2 -0
  153. package/dist/shims/link.js +77 -24
  154. package/dist/shims/metadata.d.ts +18 -34
  155. package/dist/shims/metadata.js +98 -56
  156. package/dist/shims/navigation.d.ts +18 -2
  157. package/dist/shims/navigation.js +70 -21
  158. package/dist/shims/request-context.d.ts +6 -2
  159. package/dist/shims/router.js +18 -7
  160. package/dist/shims/script.js +1 -1
  161. package/dist/shims/server.d.ts +7 -6
  162. package/dist/shims/server.js +156 -57
  163. package/dist/shims/slot.d.ts +2 -1
  164. package/dist/shims/slot.js +6 -2
  165. package/dist/shims/streamed-icons.d.ts +9 -0
  166. package/dist/shims/streamed-icons.js +33 -0
  167. package/dist/shims/unified-request-context.d.ts +21 -2
  168. package/dist/shims/unified-request-context.js +82 -1
  169. package/dist/utils/cache-control-metadata.d.ts +2 -1
  170. package/dist/utils/cache-control-metadata.js +5 -1
  171. package/dist/utils/html-limited-bots.js +1 -1
  172. package/dist/utils/protocol-headers.d.ts +7 -1
  173. package/dist/utils/protocol-headers.js +7 -1
  174. package/package.json +1 -1
@@ -133,11 +133,10 @@ declare function resolvePendingNavigationCommitDispositionDecision(options: {
133
133
  startedNavigationId: number;
134
134
  targetHref?: string;
135
135
  }): PendingNavigationCommitDispositionDecision;
136
- declare function createPendingNavigationCommit(options: {
136
+ type CreatePendingNavigationCommitOptions = {
137
137
  currentState: AppRouterState;
138
138
  navigationCommitKind?: "authoritative" | "detached";
139
139
  navigationId?: number;
140
- nextElements: Promise<AppElements>;
141
140
  navigationSnapshot: ClientNavigationRenderSnapshot;
142
141
  operationLane: OperationLane;
143
142
  payloadOrigin: AppNavigationPayloadOrigin;
@@ -146,6 +145,12 @@ declare function createPendingNavigationCommit(options: {
146
145
  restoredBfcacheIds?: BfcacheIdMap | null;
147
146
  reuseCurrentBfcacheIds?: boolean;
148
147
  type: "navigate" | "replace" | "traverse";
148
+ };
149
+ declare function createPendingNavigationCommitFromElements(options: CreatePendingNavigationCommitOptions & {
150
+ nextElements: AppElements;
151
+ }): PendingNavigationCommit;
152
+ declare function createPendingNavigationCommit(options: CreatePendingNavigationCommitOptions & {
153
+ nextElements: Promise<AppElements>;
149
154
  }): Promise<PendingNavigationCommit>;
150
155
  //#endregion
151
- export { AppNavigationPayloadOrigin, AppRouterAction, AppRouterState, type BfcacheIdMap, COMMITTED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, CommittedOperationRecord, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, type HistoryTraversalIntent, type OperationLane, PendingNavigationCommit, PendingOperationRecord, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, createBfcacheSegmentStateKeyMap, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, createInitialBfcacheIdMap, createNextBfcacheIdMap, createPendingNavigationCommit, isCacheRestorableAppPayloadMetadata, isCompleteAppPayloadMetadata, isHistoryStateBfcacheVersionCurrent, preserveBfcacheIdsForMergedElements, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent, resolveInterceptionContextFromPreviousNextUrl, resolvePendingNavigationCommitDispositionDecision, resolveServerActionRequestState };
156
+ export { AppNavigationPayloadOrigin, AppRouterAction, AppRouterState, COMMITTED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, CommittedOperationRecord, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, type HistoryTraversalIntent, type OperationLane, PendingNavigationCommit, PendingOperationRecord, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, createBfcacheSegmentStateKeyMap, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, createInitialBfcacheIdMap, createNextBfcacheIdMap, createPendingNavigationCommit, createPendingNavigationCommitFromElements, isCacheRestorableAppPayloadMetadata, isCompleteAppPayloadMetadata, isHistoryStateBfcacheVersionCurrent, preserveBfcacheIdsForMergedElements, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent, resolveInterceptionContextFromPreviousNextUrl, resolvePendingNavigationCommitDispositionDecision, resolveServerActionRequestState };
@@ -279,8 +279,8 @@ function mergeSkippedLayoutSlotPreservation(options) {
279
279
  }
280
280
  return preservePreviousSlotIds;
281
281
  }
282
- async function createPendingNavigationCommit(options) {
283
- const elements = await options.nextElements;
282
+ function createPendingNavigationCommitFromElements(options) {
283
+ const elements = options.nextElements;
284
284
  const metadata = AppElementsWire.readMetadata(elements);
285
285
  const cacheEntryReuseProof = metadata.cacheEntryReuseProof ?? (requiresCacheEntryReuseProof(options.payloadOrigin) ? createCacheEntryReuseProof(null) : void 0);
286
286
  const requestedPreviousNextUrl = options.previousNextUrl !== void 0 ? options.previousNextUrl : options.currentState.previousNextUrl;
@@ -328,5 +328,11 @@ async function createPendingNavigationCommit(options) {
328
328
  skippedLayoutIds: metadata.skippedLayoutIds
329
329
  };
330
330
  }
331
+ async function createPendingNavigationCommit(options) {
332
+ return createPendingNavigationCommitFromElements({
333
+ ...options,
334
+ nextElements: await options.nextElements
335
+ });
336
+ }
331
337
  //#endregion
332
- export { COMMITTED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, createBfcacheSegmentStateKeyMap, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, createInitialBfcacheIdMap, createNextBfcacheIdMap, createPendingNavigationCommit, isCacheRestorableAppPayloadMetadata, isCompleteAppPayloadMetadata, isHistoryStateBfcacheVersionCurrent, preserveBfcacheIdsForMergedElements, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent, resolveInterceptionContextFromPreviousNextUrl, resolvePendingNavigationCommitDispositionDecision, resolveServerActionRequestState };
338
+ export { COMMITTED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, createBfcacheSegmentStateKeyMap, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, createInitialBfcacheIdMap, createNextBfcacheIdMap, createPendingNavigationCommit, createPendingNavigationCommitFromElements, isCacheRestorableAppPayloadMetadata, isCompleteAppPayloadMetadata, isHistoryStateBfcacheVersionCurrent, preserveBfcacheIdsForMergedElements, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent, resolveInterceptionContextFromPreviousNextUrl, resolvePendingNavigationCommitDispositionDecision, resolveServerActionRequestState };
@@ -1,7 +1,7 @@
1
1
  import { NEXTJS_CACHE_HEADER, VINEXT_CACHE_HEADER } from "./headers.js";
2
- import { setCacheStateHeaders } from "./cache-headers.js";
3
2
  import { applyCdnResponseHeaders } from "./cache-control.js";
4
3
  import { buildAppPageCacheValue } from "./isr-cache.js";
4
+ import { setCacheStateHeaders } from "./cache-headers.js";
5
5
  import { readStreamAsText } from "../utils/text-stream.js";
6
6
  import { createEmptyAppPageRenderObservationState } from "./app-page-render-observation.js";
7
7
  //#region src/server/app-page-cache-finalizer.ts
@@ -18,7 +18,7 @@ type AppPageCacheOutcomeMetric = Readonly<{
18
18
  */
19
19
  cacheKey: string;
20
20
  outcome: "hit" | "miss" | "stale";
21
- reason: "empty-entry" | "no-entry" | "non-app-page-entry" | "query-variant-unproven" | "read-error" | "served" | "stale-empty-entry";
21
+ reason: "empty-entry" | "expired" | "no-entry" | "non-app-page-entry" | "query-variant-unproven" | "read-error" | "served" | "stale-empty-entry";
22
22
  }>;
23
23
  type AppPageCacheOutcomeRecorder = (metric: AppPageCacheOutcomeMetric) => void;
24
24
  type AppPageCacheRenderResult = {
@@ -1,9 +1,9 @@
1
1
  import { VINEXT_MOUNTED_SLOTS_HEADER } from "./headers.js";
2
- import { setCacheStateHeaders } from "./cache-headers.js";
3
2
  import { applyCdnResponseHeaders } from "./cache-control.js";
4
3
  import { decideIsr } from "./isr-decision.js";
5
4
  import { buildAppPageCacheValue } from "./isr-cache.js";
6
5
  import { encodeCacheTag } from "../utils/encode-cache-tag.js";
6
+ import { setCacheStateHeaders } from "./cache-headers.js";
7
7
  import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
8
8
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
9
9
  import { applyEdgeRuntimeHeader } from "./app-page-response.js";
@@ -58,7 +58,7 @@ function hasQueryInvariantAppPageProof(cachedValue) {
58
58
  function resolveRegeneratedAppPageCachePolicy(options) {
59
59
  let revalidateSeconds = options.routeRevalidateSeconds;
60
60
  const renderRevalidateSeconds = options.renderCacheControl?.revalidate;
61
- if (renderRevalidateSeconds !== void 0) revalidateSeconds = revalidateSeconds > 0 ? Math.min(revalidateSeconds, renderRevalidateSeconds) : renderRevalidateSeconds;
61
+ if (typeof renderRevalidateSeconds === "number") revalidateSeconds = revalidateSeconds > 0 ? Math.min(revalidateSeconds, renderRevalidateSeconds) : renderRevalidateSeconds;
62
62
  return {
63
63
  expireSeconds: options.renderCacheControl?.expire ?? options.expireSeconds,
64
64
  revalidateSeconds
@@ -105,6 +105,10 @@ function buildAppPageCachedResponse(cachedValue, options) {
105
105
  });
106
106
  }
107
107
  async function serveAppPageCachedHtml(options, transformValue) {
108
+ if (options.cached?.isExpired) {
109
+ options.isrDebug?.("MISS (expired)", options.pathname);
110
+ return null;
111
+ }
108
112
  if (typeof options.cachedValue.html !== "string" || options.cachedValue.html.length === 0) {
109
113
  if (options.cached?.isStale) options.scheduleRegeneration();
110
114
  options.isrDebug?.(options.emptyDebugMessage, options.pathname);
@@ -137,6 +141,16 @@ async function readAppPageCacheResponse(options) {
137
141
  try {
138
142
  const cached = await options.isrGet(isrKey);
139
143
  const cachedValue = getCachedAppPageValue(cached);
144
+ if (cached?.isExpired) {
145
+ recordAppPageCacheOutcome(options.recordCacheOutcome, {
146
+ artifact,
147
+ cacheKey: isrKey,
148
+ outcome: "miss",
149
+ reason: "expired"
150
+ });
151
+ options.isrDebug?.("MISS (expired)", options.cleanPathname);
152
+ return null;
153
+ }
140
154
  if (cached && !cachedValue) {
141
155
  recordAppPageCacheOutcome(options.recordCacheOutcome, {
142
156
  artifact,
@@ -1,11 +1,11 @@
1
1
  import { ClassificationReason } from "../build/layout-classification-types.js";
2
2
  import { ClientReuseManifestParseResult } from "./client-reuse-manifest.js";
3
3
  import { AppOutgoingElements } from "./app-elements-wire.js";
4
+ import { AppRscRenderMode } from "./app-rsc-render-mode.js";
4
5
  import { NavigationContext } from "../shims/navigation-context-state.js";
5
6
  import { AppPageFontPreload, LayoutClassificationOptions } from "./app-page-execution.js";
6
7
  import { AppPageMiddlewareContext } from "./app-page-response.js";
7
8
  import { AppLayoutParamAccessTracker } from "./app-layout-param-observation.js";
8
- import { AppRscRenderMode } from "./app-rsc-render-mode.js";
9
9
  import { RootParams } from "../shims/root-params.js";
10
10
  import { AppPageSsrHandler } from "./app-page-stream.js";
11
11
  import { CachedAppPageValue } from "../shims/cache-handler.js";
@@ -36,6 +36,8 @@ type AppPageDispatchIntercept<TPage = unknown> = {
36
36
  interceptLayouts?: readonly unknown[] | null;
37
37
  interceptLayoutSegments?: readonly (readonly string[])[] | null;
38
38
  interceptBranchSegments?: readonly string[] | null;
39
+ interceptLoadings?: readonly unknown[] | null;
40
+ interceptLoadingTreePositions?: readonly number[] | null;
39
41
  interceptNotFoundBranchSegments?: readonly string[] | null;
40
42
  notFound?: unknown;
41
43
  notFoundTreePosition?: number | null;
@@ -52,6 +54,8 @@ type AppPageDispatchInterceptOptions<TPage = unknown> = {
52
54
  interceptLayouts?: readonly unknown[] | null;
53
55
  interceptLayoutSegments?: readonly (readonly string[])[] | null;
54
56
  interceptBranchSegments?: readonly string[] | null;
57
+ interceptLoadings?: readonly unknown[] | null;
58
+ interceptLoadingTreePositions?: readonly number[] | null;
55
59
  interceptNotFoundBranchSegments?: readonly string[] | null;
56
60
  interceptNotFound?: unknown;
57
61
  interceptNotFoundTreePosition?: number | null;
@@ -69,6 +73,8 @@ type AppPageModule = {
69
73
  };
70
74
  type AppPageDispatchSlot = {
71
75
  default?: AppPageModule | null;
76
+ loading?: AppPageModule | null;
77
+ loadings?: readonly (AppPageModule | null | undefined)[] | null;
72
78
  page?: AppPageModule | null;
73
79
  slotPatternParts?: readonly string[] | null;
74
80
  slotParamNames?: readonly string[] | null;
@@ -85,6 +91,8 @@ type AppPageDispatchRoute = {
85
91
  layouts: readonly AppPageModule[];
86
92
  layoutTreePositions?: readonly number[];
87
93
  loading?: AppPageModule | null;
94
+ loadings?: readonly (AppPageModule | null | undefined)[];
95
+ loadingTreePositions?: readonly number[];
88
96
  notFound?: AppPageModule | null;
89
97
  notFounds?: readonly (AppPageModule | null | undefined)[];
90
98
  params: readonly string[];
@@ -1,4 +1,4 @@
1
- import { createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
1
+ import { closeAfterResponse, createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
2
2
  import { getRequestExecutionContext } from "../shims/request-context.js";
3
3
  import "../utils/protocol-headers.js";
4
4
  import { AppElementsWire } from "./app-elements-wire.js";
@@ -23,6 +23,22 @@ import { resolveAppPageMethodResponse } from "./app-page-method.js";
23
23
  import { buildAppPageElement, resolveAppPageIntercept, resolveAppPageInterceptionRerenderTarget, validateAppPageDynamicParams } from "./app-page-request.js";
24
24
  import { renderAppPageLifecycle } from "./app-page-render.js";
25
25
  //#region src/server/app-page-dispatch.ts
26
+ function getActiveLoadingTreePositions(route) {
27
+ const positions = [];
28
+ for (const [index, loadingModule] of (route.loadings ?? []).entries()) {
29
+ if (!loadingModule?.default) continue;
30
+ const treePosition = route.loadingTreePositions?.[index];
31
+ if (treePosition !== void 0) positions.push(treePosition);
32
+ }
33
+ if (positions.length === 0 && route.loading?.default) positions.push(route.routeSegments.length);
34
+ return positions;
35
+ }
36
+ function getAppPageLayoutProbeCount(route, loadingTreePositions) {
37
+ const firstLoadingTreePosition = loadingTreePositions.reduce((first, position) => first === null || position < first ? position : first, null);
38
+ if (firstLoadingTreePosition === null) return route.layouts.length;
39
+ const firstSuspendedLayoutIndex = (route.layoutTreePositions ?? []).findIndex((position) => position > firstLoadingTreePosition);
40
+ return firstSuspendedLayoutIndex === -1 ? route.layouts.length : firstSuspendedLayoutIndex;
41
+ }
26
42
  function resolveAppPageRouteBoundaryModule(route, statusCode) {
27
43
  if (statusCode === 403) return route.forbidden ?? null;
28
44
  if (statusCode === 401) return route.unauthorized ?? null;
@@ -110,7 +126,7 @@ function hasSearchParams(searchParams) {
110
126
  }
111
127
  async function runAppPageRevalidationContext(options, renderFn) {
112
128
  const { createStaticGenerationHeadersContext } = await import("./app-static-generation.js");
113
- return runWithRequestContext(createRequestContext({
129
+ const requestContext = createRequestContext({
114
130
  headersContext: createStaticGenerationHeadersContext({
115
131
  draftModeEnabled: false,
116
132
  draftModeSecret: options.draftModeSecret,
@@ -122,7 +138,8 @@ async function runAppPageRevalidationContext(options, renderFn) {
122
138
  currentForceDynamicFetchDefault: options.dynamicConfig === "force-dynamic",
123
139
  executionContext: getRequestExecutionContext(),
124
140
  unstableCacheRevalidation: "foreground"
125
- }), async () => {
141
+ });
142
+ const revalidation = runWithRequestContext(requestContext, async () => {
126
143
  ensureFetchPatch();
127
144
  setRefreshStaleFetchesInForeground(process.env.VINEXT_PRERENDER === "1");
128
145
  setCurrentFetchSoftTags(buildAppPageTags(options.cleanPathname, [], options.routeSegments));
@@ -133,6 +150,11 @@ async function runAppPageRevalidationContext(options, renderFn) {
133
150
  });
134
151
  return await runWithFetchDedupe(renderFn);
135
152
  });
153
+ try {
154
+ return await revalidation;
155
+ } finally {
156
+ await closeAfterResponse(requestContext);
157
+ }
136
158
  }
137
159
  function toInterceptOptions(interceptionContext, intercept) {
138
160
  return {
@@ -140,6 +162,8 @@ function toInterceptOptions(interceptionContext, intercept) {
140
162
  interceptLayouts: intercept.interceptLayouts,
141
163
  interceptLayoutSegments: intercept.interceptLayoutSegments,
142
164
  interceptBranchSegments: intercept.interceptBranchSegments,
165
+ interceptLoadings: intercept.interceptLoadings,
166
+ interceptLoadingTreePositions: intercept.interceptLoadingTreePositions,
143
167
  interceptNotFoundBranchSegments: intercept.interceptNotFoundBranchSegments,
144
168
  interceptNotFound: intercept.notFound,
145
169
  interceptNotFoundTreePosition: intercept.notFoundTreePosition,
@@ -173,7 +197,8 @@ async function dispatchAppPageInner(options) {
173
197
  const hasRequestSearchParams = !shouldUseEmptySearchParams && hasSearchParams(options.searchParams);
174
198
  const pageSearchParams = shouldUseEmptySearchParams ? new URLSearchParams() : options.searchParams;
175
199
  const layoutParamAccess = createAppLayoutParamAccessTracker();
176
- const hasActiveLoadingBoundary = Boolean(route.loading?.default);
200
+ const activeLoadingTreePositions = getActiveLoadingTreePositions(route);
201
+ const hasActiveLoadingBoundary = activeLoadingTreePositions.length > 0;
177
202
  setCurrentFetchSoftTags(buildAppPageTags(options.cleanPathname, [], route.routeSegments));
178
203
  setCurrentFetchCacheMode(options.fetchCache ?? null);
179
204
  setCurrentForceDynamicFetchDefault(isForceDynamic);
@@ -323,13 +348,17 @@ async function dispatchAppPageInner(options) {
323
348
  }
324
349
  if (options.skipStaticParamsValidation !== true && !(options.isProduction && isForceDynamic)) {
325
350
  const dynamicParamsResponse = await validateAppPageDynamicParams({
326
- clearRequestContext: options.clearRequestContext,
327
351
  enforceStaticParamsOnly: options.dynamicParamsConfig === false,
328
352
  generateStaticParams: options.generateStaticParams,
329
353
  isDynamicRoute: route.isDynamic,
330
354
  params: options.staticParamsValidationParams ?? options.params
331
355
  });
332
- if (dynamicParamsResponse) return dynamicParamsResponse;
356
+ if (dynamicParamsResponse) {
357
+ const renderedNotFound = await options.renderHttpAccessFallbackPage(404, { matchedParams: options.params }, options.middlewareContext);
358
+ if (renderedNotFound) return renderedNotFound;
359
+ options.clearRequestContext();
360
+ return dynamicParamsResponse;
361
+ }
333
362
  }
334
363
  const fallbackShellResponse = options.pprRuntime ? await options.pprRuntime.tryServe(options, currentRevalidateSeconds, isDraftMode, isForceStatic, isForceDynamic) : null;
335
364
  if (fallbackShellResponse) return fallbackShellResponse;
@@ -410,7 +439,7 @@ async function dispatchAppPageInner(options) {
410
439
  });
411
440
  },
412
441
  async probePageSpecialError() {
413
- if (route.loading?.default) return null;
442
+ if (hasActiveLoadingBoundary) return null;
414
443
  return resolveAppPageSpecialError(await probeAppPageThrownError({
415
444
  probePage: () => options.probePage(pageSearchParams),
416
445
  runWithSuppressedHookWarning(probe) {
@@ -500,7 +529,7 @@ async function dispatchAppPageInner(options) {
500
529
  isrSet: options.isrSet,
501
530
  interceptionContext: options.interceptionContext,
502
531
  expireSeconds: options.expireSeconds,
503
- layoutCount: route.layouts.length,
532
+ layoutCount: getAppPageLayoutProbeCount(route, activeLoadingTreePositions),
504
533
  loadSsrHandler: options.loadSsrHandler,
505
534
  middlewareContext: options.middlewareContext,
506
535
  navigationParams,
@@ -1,9 +1,9 @@
1
1
  import { MetadataFileRoute } from "./metadata-routes.js";
2
2
  import { AppElements } from "./app-elements-wire.js";
3
+ import { AppRscRenderMode } from "./app-rsc-render-mode.js";
3
4
  import { AppPageParams } from "./app-page-boundary.js";
4
5
  import { ApplyAppPageFileBasedMetadata } from "./app-page-head.js";
5
6
  import { AppLayoutParamAccessTracker } from "./app-layout-param-observation.js";
6
- import { AppRscRenderMode } from "./app-rsc-render-mode.js";
7
7
  import { AppPageErrorModule, AppPageModule, AppPageRouteWiringRoute } from "./app-page-route-wiring.js";
8
8
 
9
9
  //#region src/server/app-page-element-builder.d.ts
@@ -22,6 +22,8 @@ type AppPageInterceptOptions<TModule extends AppPageModule = AppPageModule> = {
22
22
  interceptLayouts?: readonly (TModule | null | undefined)[] | null;
23
23
  interceptLayoutSegments?: readonly (readonly string[])[] | null;
24
24
  interceptBranchSegments?: readonly string[] | null;
25
+ interceptLoadings?: readonly (TModule | null | undefined)[] | null;
26
+ interceptLoadingTreePositions?: readonly number[] | null;
25
27
  interceptNotFoundBranchSegments?: readonly string[] | null;
26
28
  interceptNotFound?: TModule | null;
27
29
  interceptNotFoundTreePosition?: number | null;
@@ -64,6 +66,7 @@ type BuildPageElementsOptions<TModule extends AppPageModule = AppPageModule, TEr
64
66
  basePath?: string; /** Configured next.config `trailingSlash`, threaded into canonical URL rendering. */
65
67
  trailingSlash?: boolean; /** Serialized next.config `htmlLimitedBots` regexp source. */
66
68
  htmlLimitedBots?: string;
69
+ scriptNonce?: string;
67
70
  };
68
71
  type AppPageNavigationParamModule = {
69
72
  default?: unknown;
@@ -15,7 +15,7 @@ import { resolveAppPageParentHttpAccessBoundary } from "./app-page-boundary.js";
15
15
  import { sanitizeErrorForClient } from "./app-rsc-errors.js";
16
16
  import { DEFAULT_GLOBAL_ERROR_MODULE } from "./default-global-error-module.js";
17
17
  import { shouldServeStreamingMetadata } from "./streaming-metadata.js";
18
- import { createElement } from "react";
18
+ import { Suspense, createElement } from "react";
19
19
  //#region src/server/app-page-element-builder.ts
20
20
  function resolveInterceptLayoutParams(branchSegments, layoutSegments, params) {
21
21
  return resolveAppPageBranchParams(branchSegments, layoutSegments.length, params, layoutSegments);
@@ -111,7 +111,7 @@ async function buildPageElements(options) {
111
111
  } : {},
112
112
  ownerTreePosition: route.routeSegments?.length ?? 0
113
113
  } : null;
114
- const parallelRoutes = [...activeParallelRouteHeadInputs.map((input) => input.head), ...primaryParallelRouteHeadInput ? [primaryParallelRouteHeadInput.head] : []];
114
+ const parallelRoutes = [...primaryParallelRouteHeadInput ? [primaryParallelRouteHeadInput.head] : [], ...activeParallelRouteHeadInputs.map((input) => input.head)];
115
115
  const metadataSearchParamsObserver = observeMetadataSearchParamsAccess ? createAppPageSearchParamsObserver() : void 0;
116
116
  const preparedHead = prepareAppPageHead({
117
117
  applyFileBasedMetadata: options.applyFileBasedMetadata,
@@ -210,11 +210,35 @@ async function buildPageElements(options) {
210
210
  const mountedSlotIds = mountedSlotsHeader ? new Set(mountedSlotsHeader.split(" ")) : null;
211
211
  const slotOverrides = buildSlotOverrides(route, params, routePath, opts);
212
212
  let siblingInterceptElement = isSiblingIntercept && EffectivePageComponent ? createPageElement(EffectivePageComponent, pageProps) : null;
213
- if (isSiblingIntercept && siblingInterceptElement !== null && opts?.interceptLayouts?.length) for (let i = opts.interceptLayouts.length - 1; i >= 0; i--) {
214
- const LayoutComponent = opts.interceptLayouts[i]?.default;
215
- if (LayoutComponent) {
216
- const interceptLayoutSegments = opts.interceptLayoutSegments?.[i] ?? [];
217
- siblingInterceptElement = createElement(LayoutComponent, { params: makeThenableParams(resolveInterceptLayoutParams(opts.interceptBranchSegments ?? interceptLayoutSegments, interceptLayoutSegments, effectiveParams)) }, siblingInterceptElement);
213
+ if (isSiblingIntercept && siblingInterceptElement !== null) {
214
+ const layoutIndexesByTreePosition = /* @__PURE__ */ new Map();
215
+ for (const [index, layoutModule] of (opts?.interceptLayouts ?? []).entries()) {
216
+ if (!layoutModule?.default) continue;
217
+ const treePosition = opts?.interceptLayoutSegments?.[index]?.length ?? 0;
218
+ const indexes = layoutIndexesByTreePosition.get(treePosition) ?? [];
219
+ indexes.push(index);
220
+ layoutIndexesByTreePosition.set(treePosition, indexes);
221
+ }
222
+ const loadingIndexesByTreePosition = /* @__PURE__ */ new Map();
223
+ for (const [index, loadingModule] of (opts?.interceptLoadings ?? []).entries()) {
224
+ if (!loadingModule?.default) continue;
225
+ const treePosition = opts?.interceptLoadingTreePositions?.[index];
226
+ if (treePosition !== void 0) loadingIndexesByTreePosition.set(treePosition, index);
227
+ }
228
+ const treePositions = Array.from(/* @__PURE__ */ new Set([...layoutIndexesByTreePosition.keys(), ...loadingIndexesByTreePosition.keys()])).sort((left, right) => left - right);
229
+ for (let index = treePositions.length - 1; index >= 0; index--) {
230
+ const treePosition = treePositions[index];
231
+ const loadingIndex = loadingIndexesByTreePosition.get(treePosition);
232
+ const LoadingComponent = loadingIndex === void 0 ? null : opts?.interceptLoadings?.[loadingIndex]?.default;
233
+ if (LoadingComponent) siblingInterceptElement = createElement(Suspense, { fallback: createElement(LoadingComponent) }, siblingInterceptElement);
234
+ const layoutIndexes = layoutIndexesByTreePosition.get(treePosition) ?? [];
235
+ for (let layoutOffset = layoutIndexes.length - 1; layoutOffset >= 0; layoutOffset--) {
236
+ const layoutIndex = layoutIndexes[layoutOffset];
237
+ const LayoutComponent = opts?.interceptLayouts?.[layoutIndex]?.default;
238
+ if (!LayoutComponent) continue;
239
+ const interceptLayoutSegments = opts?.interceptLayoutSegments?.[layoutIndex] ?? [];
240
+ siblingInterceptElement = createElement(LayoutComponent, { params: makeThenableParams(resolveInterceptLayoutParams(opts?.interceptBranchSegments ?? interceptLayoutSegments, interceptLayoutSegments, effectiveParams)) }, siblingInterceptElement);
241
+ }
218
242
  }
219
243
  }
220
244
  return buildAppPageElements({
@@ -230,6 +254,7 @@ async function buildPageElements(options) {
230
254
  resolvedMetadata,
231
255
  resolvedMetadataPathname: routePath,
232
256
  resolvedViewport,
257
+ scriptNonce: options.scriptNonce,
233
258
  streamingMetadata,
234
259
  streamingMetadataOutlet,
235
260
  streamingMetadataOutletSuspended: streamGeneratedHead,
@@ -267,6 +292,8 @@ function buildSlotOverrides(route, routeParams, routePath, opts) {
267
292
  branchSegments: opts.interceptBranchSegments ?? null,
268
293
  layoutModules: opts.interceptLayouts || null,
269
294
  layoutSegments: opts.interceptLayoutSegments ?? null,
295
+ loadingModules: opts.interceptLoadings || null,
296
+ loadingTreePositions: opts.interceptLoadingTreePositions ?? null,
270
297
  pageModule: opts.interceptPage,
271
298
  params: opts.interceptParams || routeParams,
272
299
  routeSegments: resolveInterceptedSlotSegments(opts.interceptSourcePageSegments, opts.interceptSlotKey)
@@ -1,7 +1,7 @@
1
1
  import { MetadataFileRoute } from "./metadata-routes.js";
2
2
  import { AppPageParams } from "./app-page-boundary.js";
3
3
  import { ThenableParamsObserver } from "../shims/thenable-params.js";
4
- import { Metadata, Viewport } from "../shims/metadata.js";
4
+ import { Metadata, ResolvedViewport } from "../shims/metadata.js";
5
5
  import { applyFileBasedMetadata } from "./file-based-metadata.js";
6
6
 
7
7
  //#region src/server/app-page-head.d.ts
@@ -96,11 +96,11 @@ type ResolveAppPageHeadResult = {
96
96
  hasSearchParams: boolean;
97
97
  metadata: Metadata | null;
98
98
  pageSearchParams: AppPageSearchParams;
99
- viewport: Viewport;
99
+ viewport: ResolvedViewport;
100
100
  };
101
101
  type PreparedAppPageHead = Omit<ResolveAppPageHeadResult, "metadata" | "viewport"> & {
102
102
  metadata: Promise<Metadata | null>;
103
- viewport: Promise<Viewport>;
103
+ viewport: Promise<ResolvedViewport>;
104
104
  };
105
105
  type AppPageSearchParamsCollection = {
106
106
  hasSearchParams: boolean;
@@ -79,7 +79,7 @@ function resolveActiveParallelRouteHeadInputs(options) {
79
79
  } : {},
80
80
  ownerTreePosition
81
81
  };
82
- });
82
+ }).sort((left, right) => right.ownerTreePosition - left.ownerTreePosition);
83
83
  }
84
84
  function isPresent(value) {
85
85
  return value !== null && value !== void 0;
@@ -165,11 +165,25 @@ async function resolveLayoutMetadata(layoutInputs, params, routeSegments) {
165
165
  }
166
166
  return Promise.all(layoutMetadataPromises);
167
167
  }
168
- async function resolveLayoutViewport(layoutInputs, params, routeSegments) {
169
- return Promise.all(layoutInputs.map((layoutInput) => {
168
+ function resolveLayoutViewport(layoutInputs, params, routeSegments) {
169
+ const viewportPromises = [];
170
+ let accumulatedViewport = Promise.resolve(mergeViewport([]));
171
+ for (const layoutInput of layoutInputs) {
172
+ const parentForLayout = accumulatedViewport;
170
173
  const layoutParams = resolveAppPageSegmentParams(routeSegments, layoutInput.treePosition, params);
171
- return resolveModuleViewport(layoutInput.module, layoutParams);
172
- }));
174
+ const viewportPromise = resolveModuleViewport(layoutInput.module, layoutParams, void 0, parentForLayout);
175
+ viewportPromises.push(viewportPromise);
176
+ viewportPromise.catch(() => null);
177
+ accumulatedViewport = mergeResolvedViewport(parentForLayout, viewportPromise);
178
+ accumulatedViewport.catch(() => null);
179
+ }
180
+ return {
181
+ resolvedViewport: accumulatedViewport,
182
+ viewportResults: Promise.all(viewportPromises)
183
+ };
184
+ }
185
+ function mergeResolvedViewport(parent, viewport) {
186
+ return Promise.all([parent, viewport]).then(([resolvedParent, resolvedViewport]) => resolvedViewport ? mergeViewport([resolvedParent, resolvedViewport]) : resolvedParent);
173
187
  }
174
188
  function getParallelRouteModules(parallelRoute) {
175
189
  return [...parallelRoute.layoutModules ?? [], parallelRoute.layoutModule].filter(isPresent);
@@ -211,16 +225,34 @@ async function resolveParallelRouteMetadata(parallelRoute, fallbackParams, fallb
211
225
  metadataSources
212
226
  };
213
227
  }
214
- async function resolveParallelRouteViewport(parallelRoute, fallbackParams, fallbackRouteSegments, pageSearchParams, searchParamsObserver) {
228
+ function resolveParallelRouteViewport(parallelRoute, fallbackParams, fallbackRouteSegments, pageSearchParams, parent, searchParamsObserver) {
215
229
  const params = parallelRoute.params ?? fallbackParams;
216
230
  const routeSegments = parallelRoute.routeSegments ?? fallbackRouteSegments;
217
231
  const layoutModules = getParallelRouteModules(parallelRoute);
218
232
  const layoutTreePositions = parallelRoute.layoutTreePositions ?? [];
219
233
  const layoutParams = parallelRoute.layoutParams ?? [];
220
- const layoutViewportPromise = Promise.all(layoutModules.map((layoutModule, index) => resolveModuleViewport(layoutModule, layoutParams[index] ?? resolveParallelLayoutParams(routeSegments, layoutTreePositions[index] ?? 0, params))));
221
- const pageViewportPromise = parallelRoute.pageModule ? resolveModuleViewport(parallelRoute.pageModule, params, pageSearchParams, searchParamsObserver) : Promise.resolve(null);
222
- const [layoutViewports, pageViewport] = await Promise.all([layoutViewportPromise, pageViewportPromise]);
223
- return parallelRoute.pageModule ? [...layoutViewports, pageViewport] : layoutViewports;
234
+ const viewportPromises = [];
235
+ let accumulatedViewport = parent;
236
+ for (const [index, layoutModule] of layoutModules.entries()) {
237
+ const parentForLayout = accumulatedViewport;
238
+ const viewportPromise = resolveModuleViewport(layoutModule, layoutParams[index] ?? resolveParallelLayoutParams(routeSegments, layoutTreePositions[index] ?? 0, params), void 0, parentForLayout);
239
+ viewportPromises.push(viewportPromise);
240
+ viewportPromise.catch(() => null);
241
+ accumulatedViewport = mergeResolvedViewport(parentForLayout, viewportPromise);
242
+ accumulatedViewport.catch(() => null);
243
+ }
244
+ if (parallelRoute.pageModule) {
245
+ const parentForPage = accumulatedViewport;
246
+ const viewportPromise = resolveModuleViewport(parallelRoute.pageModule, params, pageSearchParams, parentForPage, searchParamsObserver);
247
+ viewportPromises.push(viewportPromise);
248
+ viewportPromise.catch(() => null);
249
+ accumulatedViewport = mergeResolvedViewport(parentForPage, viewportPromise);
250
+ accumulatedViewport.catch(() => null);
251
+ }
252
+ return {
253
+ resolvedViewport: accumulatedViewport,
254
+ viewportResults: Promise.all(viewportPromises)
255
+ };
224
256
  }
225
257
  /**
226
258
  * Resolve an explicit metadata-source sequence.
@@ -279,16 +311,29 @@ function prepareAppPageHeadInner(options) {
279
311
  const primaryHasDynamicMetadata = layoutInputs.some((input) => hasGenerateMetadata(input.module)) || hasGenerateMetadata(options.pageModule);
280
312
  const { hasSearchParams, pageSearchParams } = collectAppPageSearchParams(options.searchParams);
281
313
  const layoutMetadataPromise = resolveLayoutMetadata(layoutInputs, options.params, routeSegments);
282
- const layoutViewportPromise = resolveLayoutViewport(layoutInputs, options.params, routeSegments);
314
+ const layoutViewport = resolveLayoutViewport(layoutInputs, options.params, routeSegments);
315
+ const layoutViewportPromise = layoutViewport.viewportResults;
283
316
  const layoutMetadataResultsForParent = layoutMetadataPromise.then((metadataResults) => metadataResults.filter(isPresent));
284
317
  layoutMetadataResultsForParent.catch(() => null);
285
318
  const pageParentPromise = layoutMetadataResultsForParent.then((metadataResults) => metadataResults.length > 0 ? mergeMetadataEntries(metadataResults.map((metadata) => ({ metadata }))) : {});
286
319
  pageParentPromise.catch(() => null);
287
320
  const pageMetadataPromise = options.pageModule ? resolveModuleMetadata(options.pageModule, options.params, pageSearchParams, pageParentPromise, options.searchParamsObserver) : Promise.resolve(null);
288
- const pageViewportPromise = options.pageModule ? resolveModuleViewport(options.pageModule, options.params, pageSearchParams, options.searchParamsObserver) : Promise.resolve(null);
289
321
  const parallelRoutes = options.parallelRoutes ?? [];
290
322
  const parallelRouteMetadataPromise = Promise.all(parallelRoutes.map((parallelRoute) => resolveParallelRouteMetadata(parallelRoute, options.params, routeSegments, pageSearchParams, pageParentPromise, options.searchParamsObserver)));
291
- const parallelRouteViewportPromise = Promise.all(parallelRoutes.map((parallelRoute) => resolveParallelRouteViewport(parallelRoute, options.params, routeSegments, pageSearchParams, options.searchParamsObserver)));
323
+ const parallelRouteViewportPromises = [];
324
+ let accumulatedViewport = layoutViewport.resolvedViewport;
325
+ const pageParentViewport = accumulatedViewport;
326
+ const pageViewportPromise = options.pageModule ? resolveModuleViewport(options.pageModule, options.params, pageSearchParams, pageParentViewport, options.searchParamsObserver) : Promise.resolve(null);
327
+ if (options.pageModule) {
328
+ accumulatedViewport = mergeResolvedViewport(pageParentViewport, pageViewportPromise);
329
+ accumulatedViewport.catch(() => null);
330
+ }
331
+ for (const parallelRoute of parallelRoutes) {
332
+ const parallelViewport = resolveParallelRouteViewport(parallelRoute, options.params, routeSegments, pageSearchParams, accumulatedViewport, options.searchParamsObserver);
333
+ parallelRouteViewportPromises.push(parallelViewport.viewportResults);
334
+ accumulatedViewport = parallelViewport.resolvedViewport;
335
+ }
336
+ const parallelRouteViewportPromise = Promise.all(parallelRouteViewportPromises);
292
337
  const hasDynamicMetadata = primaryHasDynamicMetadata || parallelRoutes.some(parallelRouteHasDynamicMetadata);
293
338
  const metadata = Promise.all([
294
339
  layoutMetadataPromise,
@@ -1,6 +1,6 @@
1
1
  import { MetadataFileRoute } from "./metadata-routes.js";
2
2
  import { AppPageParams } from "./app-page-boundary.js";
3
- import { Metadata, Viewport } from "../shims/metadata.js";
3
+ import { Metadata, ResolvedViewport } from "../shims/metadata.js";
4
4
  import { ActiveParallelRouteHeadInput, AppPageHeadModule, ApplyAppPageFileBasedMetadata, OrderedAppPageMetadataSource } from "./app-page-head.js";
5
5
 
6
6
  //#region src/server/app-page-http-access-fallback-metadata.d.ts
@@ -24,6 +24,6 @@ type ResolveHttpAccessFallbackMetadataOptions<TModule extends AppPageHeadModule
24
24
  };
25
25
  declare function createHttpAccessFallbackMetadataPlan<TModule extends AppPageHeadModule>(options: HttpAccessFallbackMetadataPlanOptions<TModule>): OrderedAppPageMetadataSource<TModule>[];
26
26
  declare function resolveHttpAccessFallbackMetadata<TModule extends AppPageHeadModule>(options: ResolveHttpAccessFallbackMetadataOptions<TModule>): Promise<Metadata | null>;
27
- declare function resolveHttpAccessFallbackViewport<TModule extends AppPageHeadModule>(options: HttpAccessFallbackMetadataPlanOptions<TModule>): Promise<Viewport>;
27
+ declare function resolveHttpAccessFallbackViewport<TModule extends AppPageHeadModule>(options: HttpAccessFallbackMetadataPlanOptions<TModule>): Promise<ResolvedViewport>;
28
28
  //#endregion
29
29
  export { createHttpAccessFallbackMetadataPlan, resolveHttpAccessFallbackMetadata, resolveHttpAccessFallbackViewport };
@@ -100,7 +100,15 @@ function resolveHttpAccessFallbackMetadata(options) {
100
100
  });
101
101
  }
102
102
  async function resolveHttpAccessFallbackViewport(options) {
103
- return mergeViewport((await Promise.all(createHttpAccessFallbackPlan(options, "snapshot").map((source) => resolveModuleViewport(source.module, source.params)))).filter(isPresent));
103
+ let accumulatedViewport = Promise.resolve(mergeViewport([]));
104
+ for (const source of createHttpAccessFallbackPlan(options, "snapshot")) {
105
+ const parentForSource = accumulatedViewport;
106
+ const viewportPromise = resolveModuleViewport(source.module, source.params, void 0, parentForSource);
107
+ viewportPromise.catch(() => null);
108
+ accumulatedViewport = Promise.all([parentForSource, viewportPromise]).then(([parent, viewport]) => viewport ? mergeViewport([parent, viewport]) : parent);
109
+ accumulatedViewport.catch(() => null);
110
+ }
111
+ return accumulatedViewport;
104
112
  }
105
113
  //#endregion
106
114
  export { createHttpAccessFallbackMetadataPlan, resolveHttpAccessFallbackMetadata, resolveHttpAccessFallbackViewport };
@@ -40,12 +40,16 @@ type AppPageProbeModule = Readonly<{
40
40
  }> | null | undefined;
41
41
  type AppPageProbeSlot = Readonly<{
42
42
  page?: AppPageProbeModule;
43
+ loading?: AppPageProbeModule;
44
+ loadings?: readonly AppPageProbeModule[] | null;
45
+ loadingTreePositions?: readonly number[] | null;
43
46
  }> | null | undefined;
44
47
  type AppPageProbeRoute = Readonly<{
45
48
  slots?: Readonly<Record<string, AppPageProbeSlot>> | null;
46
49
  }>;
47
50
  type AppPageProbeIntercept = Readonly<{
48
51
  page?: AppPageProbeModule;
52
+ interceptLoadings?: readonly AppPageProbeModule[] | null;
49
53
  matchedParams?: unknown;
50
54
  /**
51
55
  * Key of the parallel-route slot this interception overrides. At render
@@ -1,6 +1,6 @@
1
- import { markAppPagePropsForUseCache } from "../shims/internal/app-page-props-cache-key.js";
2
1
  import { isNextRouterError } from "../shims/navigation-errors.js";
3
2
  import "../shims/navigation-server.js";
3
+ import { markAppPagePropsForUseCache } from "../shims/internal/app-page-props-cache-key.js";
4
4
  import { isPromiseLike } from "../utils/promise.js";
5
5
  import { probeAppPageComponent, probeAppPageLayouts } from "./app-page-execution.js";
6
6
  import { collectAppPageSearchParams } from "./app-page-head.js";
@@ -218,13 +218,17 @@ function buildAppPageProbes(options) {
218
218
  const overriddenSlotKey = intercept?.slotKey ?? null;
219
219
  for (const [slotKey, slot] of Object.entries(route.slots ?? {})) {
220
220
  if (overriddenSlotKey !== null && slotKey === overriddenSlotKey) continue;
221
+ if (slot?.loading?.default || slot?.loadings?.some((loading) => loading?.default)) continue;
221
222
  probes.push(probeAppPage({
222
223
  pageComponent: slot?.page?.default,
223
224
  asyncRouteParams,
224
225
  searchParams
225
226
  }));
226
227
  }
227
- if (intercept) probes.push(probeAppPage({
228
+ const interceptedSlot = intercept?.slotKey ? route.slots?.[intercept.slotKey] : null;
229
+ const interceptedSlotHasRootLoading = Boolean(interceptedSlot?.loading?.default || interceptedSlot?.loadings?.some((loading, index) => loading?.default && interceptedSlot.loadingTreePositions?.[index] === 0));
230
+ const interceptHasLoadingBoundary = Boolean(intercept?.interceptLoadings?.some((loading) => loading?.default) || interceptedSlotHasRootLoading);
231
+ if (intercept && !interceptHasLoadingBoundary) probes.push(probeAppPage({
228
232
  pageComponent: intercept.page?.default,
229
233
  asyncRouteParams: options.makeThenableParams(intercept.matchedParams ?? matchedParams),
230
234
  searchParams
@@ -1,10 +1,10 @@
1
1
  import { ClientReuseManifestParseResult, ClientReuseManifestSkipDisposition } from "./client-reuse-manifest.js";
2
2
  import { AppOutgoingElements } from "./app-elements-wire.js";
3
+ import { AppRscRenderMode } from "./app-rsc-render-mode.js";
3
4
  import { NavigationContext } from "../shims/navigation-context-state.js";
4
5
  import { AppPageFontPreload, AppPageSpecialError, LayoutClassificationOptions } from "./app-page-execution.js";
5
6
  import { AppPageMiddlewareContext } from "./app-page-response.js";
6
7
  import { AppLayoutParamAccessTracker } from "./app-layout-param-observation.js";
7
- import { AppRscRenderMode } from "./app-rsc-render-mode.js";
8
8
  import { RootParams } from "../shims/root-params.js";
9
9
  import { AppPageSsrHandler } from "./app-page-stream.js";
10
10
  import { CachedAppPageValue } from "../shims/cache-handler.js";