vinext 1.4.0 → 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 (156) hide show
  1. package/README.md +21 -21
  2. package/dist/build/action-owner-manifest.js +1 -0
  3. package/dist/build/prerender.js +3 -2
  4. package/dist/check.js +4 -7
  5. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  6. package/dist/client/dev-error-overlay.d.ts +2 -0
  7. package/dist/client/dev-error-overlay.js +19 -3
  8. package/dist/config/config-matchers.js +4 -2
  9. package/dist/config/destination-params.d.ts +2 -1
  10. package/dist/config/destination-params.js +55 -2
  11. package/dist/config/next-config.d.ts +22 -1
  12. package/dist/config/next-config.js +49 -4
  13. package/dist/entries/app-rsc-entry.js +41 -19
  14. package/dist/entries/app-rsc-manifest.js +4 -0
  15. package/dist/entries/pages-server-entry.js +5 -1
  16. package/dist/index.js +44 -32
  17. package/dist/init-cloudflare.js +18 -2
  18. package/dist/plugins/css-target.d.ts +18 -0
  19. package/dist/plugins/css-target.js +93 -0
  20. package/dist/plugins/strip-server-exports.js +2 -1
  21. package/dist/routing/app-route-graph.d.ts +10 -0
  22. package/dist/routing/app-route-graph.js +15 -1
  23. package/dist/server/api-handler.d.ts +2 -0
  24. package/dist/server/api-handler.js +5 -6
  25. package/dist/server/app-action-forwarding.js +3 -1
  26. package/dist/server/app-browser-entry.js +28 -7
  27. package/dist/server/app-browser-rsc-redirect.js +3 -3
  28. package/dist/server/app-browser-server-action-client.js +2 -2
  29. package/dist/server/app-browser-visible-commit.js +2 -2
  30. package/dist/server/app-elements.d.ts +3 -0
  31. package/dist/server/app-elements.js +14 -1
  32. package/dist/server/app-fallback-renderer.js +2 -2
  33. package/dist/server/app-optimistic-routing.d.ts +12 -1
  34. package/dist/server/app-optimistic-routing.js +40 -19
  35. package/dist/server/app-page-boundary-render.js +7 -12
  36. package/dist/server/app-page-cache-render.js +1 -1
  37. package/dist/server/app-page-dispatch.d.ts +10 -2
  38. package/dist/server/app-page-dispatch.js +73 -31
  39. package/dist/server/app-page-element-builder.d.ts +9 -20
  40. package/dist/server/app-page-element-builder.js +40 -52
  41. package/dist/server/app-page-execution.js +9 -6
  42. package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
  43. package/dist/server/app-page-intercept-segment-config.js +90 -0
  44. package/dist/server/app-page-navigation-params.d.ts +23 -0
  45. package/dist/server/app-page-navigation-params.js +49 -0
  46. package/dist/server/app-page-render.d.ts +5 -3
  47. package/dist/server/app-page-render.js +31 -17
  48. package/dist/server/app-page-request.d.ts +24 -2
  49. package/dist/server/app-page-request.js +10 -10
  50. package/dist/server/app-page-response.js +1 -1
  51. package/dist/server/app-page-route-wiring.d.ts +10 -0
  52. package/dist/server/app-page-route-wiring.js +34 -13
  53. package/dist/server/app-page-segment-plan.js +9 -1
  54. package/dist/server/app-page-segment-state.d.ts +9 -0
  55. package/dist/server/app-page-segment-state.js +54 -1
  56. package/dist/server/app-page-stream.d.ts +10 -8
  57. package/dist/server/app-page-stream.js +2 -1
  58. package/dist/server/app-request-stage-independent-entry.js +1 -1
  59. package/dist/server/app-response-stage-entry.js +1 -0
  60. package/dist/server/app-route-handler-execution.js +3 -2
  61. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  62. package/dist/server/app-route-handler-runtime.js +11 -7
  63. package/dist/server/app-route-module-loader.d.ts +2 -0
  64. package/dist/server/app-route-module-loader.js +1 -0
  65. package/dist/server/app-router-entry.js +1 -1
  66. package/dist/server/app-rsc-cache-busting.d.ts +10 -10
  67. package/dist/server/app-rsc-cache-busting.js +27 -68
  68. package/dist/server/app-rsc-error-handler.js +1 -1
  69. package/dist/server/app-rsc-errors.js +4 -1
  70. package/dist/server/app-rsc-handler.d.ts +10 -1
  71. package/dist/server/app-rsc-handler.js +126 -103
  72. package/dist/server/app-rsc-request-normalization.d.ts +10 -12
  73. package/dist/server/app-rsc-request-normalization.js +11 -14
  74. package/dist/server/app-rsc-response-finalizer.d.ts +3 -0
  75. package/dist/server/app-rsc-response-finalizer.js +11 -1
  76. package/dist/server/app-rsc-response-stage.js +4 -12
  77. package/dist/server/app-rsc-route-matching.d.ts +14 -0
  78. package/dist/server/app-rsc-route-matching.js +42 -18
  79. package/dist/server/app-server-action-execution.js +24 -14
  80. package/dist/server/app-ssr-entry.d.ts +6 -0
  81. package/dist/server/app-ssr-entry.js +13 -8
  82. package/dist/server/app-worker-stages.d.ts +12 -3
  83. package/dist/server/app-worker-stages.js +37 -4
  84. package/dist/server/bfcache-identity.d.ts +7 -0
  85. package/dist/server/bfcache-identity.js +10 -1
  86. package/dist/server/cache-control.d.ts +2 -0
  87. package/dist/server/cache-control.js +3 -1
  88. package/dist/server/cacheability-manifest.js +1 -2
  89. package/dist/server/dev-origin-check.d.ts +2 -1
  90. package/dist/server/dev-origin-check.js +2 -1
  91. package/dist/server/dev-server.js +2 -1
  92. package/dist/server/image-local-patterns.d.ts +21 -0
  93. package/dist/server/image-local-patterns.js +59 -0
  94. package/dist/server/image-optimization.d.ts +28 -22
  95. package/dist/server/image-optimization.js +17 -2
  96. package/dist/server/isr-cache.js +4 -1
  97. package/dist/server/metadata-route-response.js +25 -7
  98. package/dist/server/middleware-matcher.js +19 -6
  99. package/dist/server/middleware-runtime.js +6 -2
  100. package/dist/server/multi-stage.d.ts +7 -3
  101. package/dist/server/nitro-public-files-plugin.js +20 -8
  102. package/dist/server/pages-data-route.d.ts +5 -1
  103. package/dist/server/pages-data-route.js +14 -6
  104. package/dist/server/pages-node-compat.d.ts +1 -1
  105. package/dist/server/pages-node-compat.js +2 -0
  106. package/dist/server/pages-page-data.js +2 -1
  107. package/dist/server/pages-page-handler.js +17 -6
  108. package/dist/server/pages-page-response.d.ts +9 -0
  109. package/dist/server/pages-page-response.js +18 -4
  110. package/dist/server/pages-request-pipeline.js +6 -5
  111. package/dist/server/pages-request-stage-entry.js +25 -4
  112. package/dist/server/pages-router-entry.js +3 -2
  113. package/dist/server/prod-server.d.ts +2 -2
  114. package/dist/server/prod-server.js +59 -17
  115. package/dist/server/request-error-log.d.ts +9 -0
  116. package/dist/server/request-error-log.js +13 -0
  117. package/dist/server/request-pipeline.d.ts +14 -4
  118. package/dist/server/request-pipeline.js +33 -15
  119. package/dist/server/response-stage-cacheability.d.ts +7 -0
  120. package/dist/server/response-stage-cacheability.js +6 -5
  121. package/dist/server/rsc-completion-metadata.js +3 -0
  122. package/dist/server/static-file-cache.d.ts +9 -0
  123. package/dist/server/static-file-cache.js +13 -2
  124. package/dist/server/worker-image-optimization.d.ts +2 -2
  125. package/dist/server/worker-image-optimization.js +18 -3
  126. package/dist/server/worker-utils.d.ts +8 -0
  127. package/dist/server/worker-utils.js +19 -2
  128. package/dist/shims/cache-request-state.d.ts +6 -6
  129. package/dist/shims/cache-request-state.js +6 -36
  130. package/dist/shims/cache-runtime.d.ts +3 -1
  131. package/dist/shims/cache-runtime.js +77 -47
  132. package/dist/shims/cache.d.ts +10 -5
  133. package/dist/shims/cache.js +35 -22
  134. package/dist/shims/client-hook-error.d.ts +2 -0
  135. package/dist/shims/client-hook-error.js +16 -1
  136. package/dist/shims/fetch-cache.js +6 -2
  137. package/dist/shims/headers.d.ts +2 -1
  138. package/dist/shims/headers.js +5 -3
  139. package/dist/shims/image-props.d.ts +6 -0
  140. package/dist/shims/image-props.js +39 -3
  141. package/dist/shims/image.js +3 -3
  142. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  143. package/dist/shims/link.js +5 -10
  144. package/dist/shims/navigation.js +2 -3
  145. package/dist/shims/request-state-types.d.ts +2 -1
  146. package/dist/shims/slot.d.ts +9 -2
  147. package/dist/shims/slot.js +76 -11
  148. package/dist/shims/unified-request-context.d.ts +6 -0
  149. package/dist/shims/unified-request-context.js +1 -0
  150. package/dist/utils/base-path.d.ts +7 -0
  151. package/dist/utils/base-path.js +11 -1
  152. package/dist/utils/cache-life-profiles.d.ts +33 -0
  153. package/dist/utils/cache-life-profiles.js +111 -0
  154. package/dist/utils/middleware-request-headers.d.ts +14 -3
  155. package/dist/utils/middleware-request-headers.js +54 -7
  156. package/package.json +1 -1
@@ -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 };
@@ -72,7 +72,6 @@ type RenderAppPageLifecycleOptionsBase = {
72
72
  peekRequestCacheLife?: () => AppPageRequestCacheLife | null;
73
73
  getDraftModeCookieHeader: () => string | null | undefined;
74
74
  handlerStart: number;
75
- hasCustomGlobalError?: boolean;
76
75
  dynamicStaleTimeSeconds?: number;
77
76
  isDynamicError: boolean;
78
77
  isDraftMode: boolean;
@@ -151,9 +150,12 @@ type RenderAppPageLifecycleOptions = RenderAppPageLifecycleOptionsBase & ({
151
150
  * static, draft mode, `force-dynamic`, `revalidate = 0`, or a dynamic API read
152
151
  * before the response left the render. Responses that leave the render before
153
152
  * its response policy, such as error boundaries and special errors, get the
154
- * 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).
155
157
  */
156
- 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;
157
159
  export declare function renderAppPageLifecycle(options: RenderAppPageLifecycleOptions): Promise<Response>;
158
160
  //#endregion
159
161
  export {};
@@ -13,6 +13,7 @@ import { isRenderDynamicLatched } from "../shims/internal/headers-state.js";
13
13
  import { peekDynamicUsage } from "../shims/headers.js";
14
14
  import { runWithFetchDedupe } from "../shims/fetch-cache.js";
15
15
  import { setCacheStateHeaders } from "./cache-headers.js";
16
+ import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
16
17
  import { copyLinkHeaderProvenance, getFrameworkLinkHeader } from "./app-response-header-provenance.js";
17
18
  import { traceResponseStartWithCompletion } from "./response-start-tracing.js";
18
19
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
@@ -21,13 +22,12 @@ import { AppElementsWire, isAppElementsRecord } from "./app-elements-wire.js";
21
22
  import "./app-elements.js";
22
23
  import { appendRscCompletionMetadata } from "./rsc-completion-metadata.js";
23
24
  import { isBailoutToCSRError } from "../shims/navigation-errors.js";
24
- import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.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;
@@ -230,7 +244,6 @@ async function prepareAppPageElement(options) {
230
244
  }
231
245
  async function renderAppPageLifecycleImpl(options, renderSpan) {
232
246
  let dynamicUsageObserved = false;
233
- let dynamicUsageFinalized = false;
234
247
  const isCacheCandidateHtmlRender = options.isCacheCandidate === true && options.isPrerender !== true && !options.isRscRequest;
235
248
  const readsRenderDynamicLatch = isCacheCandidateHtmlRender || !options.isRscRequest && options.pprFallbackShellSignal === void 0 && (options.isPrerender === true || isRouteCacheabilityEvaluation());
236
249
  const readDynamicUsage = bindRequestContext(() => options.consumeDynamicUsage() || readsRenderDynamicLatch && isRenderDynamicLatched());
@@ -240,13 +253,6 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
240
253
  if (!dynamicUsageObserved) dynamicUsageObserved = readDynamicUsage();
241
254
  return dynamicUsageObserved;
242
255
  };
243
- const finalizeRenderDynamicUsage = () => {
244
- if (!dynamicUsageFinalized) {
245
- consumeRenderDynamicUsage();
246
- dynamicUsageFinalized = true;
247
- }
248
- return dynamicUsageObserved;
249
- };
250
256
  const artifactCompatibility = createAppPageArtifactCompatibility(options.element, options.routePattern);
251
257
  const rootBoundaryId = artifactCompatibility?.rootBoundaryId ?? null;
252
258
  const renderEpoch = artifactCompatibility?.renderEpoch ?? null;
@@ -429,7 +435,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
429
435
  const completionHeaders = new Headers(rscResponse.headers);
430
436
  completionHeaders.set(VINEXT_RSC_COMPLETION_METADATA_HEADER, "1");
431
437
  const completionResponse = dynamicStaleTimeSeconds !== void 0 && options.isPrerender !== true && !options.isForceStatic && rscResponse.body ? new Response(appendRscCompletionMetadata(rscResponse.body, () => {
432
- if (!finalizeRenderDynamicUsage()) return void 0;
438
+ if (!consumeRenderDynamicUsage()) return void 0;
433
439
  const completedServerStaleTimeSeconds = resolveClientStaleTimeSeconds(options.peekRequestCacheLife?.());
434
440
  if (shouldEmitDynamicStaleTime && completedServerStaleTimeSeconds === void 0) return;
435
441
  return {
@@ -447,7 +453,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
447
453
  bypassInterceptionContextCache: options.bypassInterceptionContextCache,
448
454
  captureAdmittedDocument,
449
455
  cleanPathname: options.cleanPathname,
450
- consumeDynamicUsage: finalizeRenderDynamicUsage,
456
+ consumeDynamicUsage: consumeRenderDynamicUsage,
451
457
  consumeRenderObservationState,
452
458
  createHtmlRenderObservation(input) {
453
459
  return createAppPageRenderObservation({
@@ -630,6 +636,12 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
630
636
  copyLinkHeaderProvenance(response.headers, prerenderResponse.headers);
631
637
  return prerenderResponse;
632
638
  };
639
+ const markPrerenderedDynamicShellSpecialError = (response, specialError) => {
640
+ if (options.isPrerender === true && response.status === specialError.statusCode && resolveEarlyResponseCacheControl(options) !== null && response.headers.get("cache-control")?.includes("no-store")) getRequestExecutionContext()?.recordPrerenderSpecialError?.({
641
+ headers: resolveAppPageSpecialErrorStoredHeaders(specialError, options.basePath) ?? {},
642
+ status: response.status
643
+ });
644
+ };
633
645
  const finalizeShellSpecialErrorResponse = (response, specialError) => {
634
646
  if (response.status !== specialError.statusCode) return applyIneligibleRouteCachePolicy(response, options);
635
647
  const dynamicUsedDuringRender = consumeRenderDynamicUsage();
@@ -687,7 +699,6 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
687
699
  capturedRscDataRef,
688
700
  getInitialNavigationCacheMetadata: () => resolveInitialNavigationCacheMetadata(dynamicUsedDuringHtmlRender || peekDynamicUsage(), options.isPrerender === true ? requestCacheLifeForPrerender ?? options.peekRequestCacheLife?.() : options.peekRequestCacheLife?.()),
689
701
  fontData,
690
- hasCustomGlobalError: options.hasCustomGlobalError,
691
702
  navigationContext: options.getNavigationContext(),
692
703
  basePath: options.basePath,
693
704
  clientTraceMetadata: options.clientTraceMetadata,
@@ -701,7 +712,8 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
701
712
  scriptNonce: options.scriptNonce,
702
713
  sideStream: rscCapture.sideStream,
703
714
  ssrHandler,
704
- fallbackToErrorDocumentOnShellError: options.isPrerender === true && options.isSpeculativePrerender === true ? false : void 0,
715
+ fallbackToErrorDocumentOnShellError: !(options.isPrerender === true && options.isSpeculativePrerender === true),
716
+ isServerComponentError: rscErrorTracker.isCapturedError,
705
717
  waitForAllReady: shouldWaitForAllReady,
706
718
  isStaticGeneration: options.isPrerender === true,
707
719
  isForceStatic: options.isForceStatic,
@@ -740,7 +752,9 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
740
752
  if (htmlRender.response) {
741
753
  if (shellSpecialError && options.isProduction && options.isPrerender !== true && resolveEarlyResponseCacheControl(options) === null) return finalizeShellSpecialErrorResponse(htmlRender.response, shellSpecialError);
742
754
  if (shellSpecialError && prerenderedSpecialError) return finalizePrerenderedShellSpecialErrorResponse(htmlRender.response, shellSpecialError, prerenderedSpecialError);
743
- return applyIneligibleRouteCachePolicy(htmlRender.response, options);
755
+ const response = applyIneligibleRouteCachePolicy(htmlRender.response, options);
756
+ if (shellSpecialError) markPrerenderedDynamicShellSpecialError(response, shellSpecialError);
757
+ return response;
744
758
  }
745
759
  let htmlStream = htmlRender.htmlStream;
746
760
  if (!htmlStream) throw new Error("[vinext] Expected an HTML stream when no fallback response was returned");
@@ -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 };
@@ -2,10 +2,10 @@ import { VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_RSC_VARY_HEADER } from "./app-rsc-v
2
2
  import { NEXT_CACHE_TAGS_HEADER, NEXT_ROUTER_STALE_TIME_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_STALE_TIME_PENDING_HEADER, VINEXT_TIMING_HEADER } from "./headers.js";
3
3
  import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, buildRevalidateCacheControl } from "./cache-control.js";
4
4
  import { setCacheStateHeaders } from "./cache-headers.js";
5
+ import { applyPrerenderCacheLifeHeader } from "./prerender-cache-life-header.js";
5
6
  import { markFrameworkLinkHeaders } from "./app-response-header-provenance.js";
6
7
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
7
8
  import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
8
- import { applyPrerenderCacheLifeHeader } from "./prerender-cache-life-header.js";
9
9
  //#region src/server/app-page-response.ts
10
10
  function applyTimingHeader(headers, timing) {
11
11
  if (!timing) return;
@@ -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> = {
@@ -3,6 +3,7 @@ import { fnv1a64 } from "../utils/hash.js";
3
3
  import { createInlineScriptTag, escapeHtmlAttr } from "./html.js";
4
4
  import { APP_LAYOUT_IDS_KEY, APP_ROOT_LAYOUT_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_STATIC_SIBLINGS_KEY, AppElementsWire, normalizeAppElementsSlotBindings } from "./app-elements-wire.js";
5
5
  import { APP_PREFETCH_LOADING_SHELL_MARKER_KEY, APP_STREAMING_METADATA_BODY_ID_PREFIX } from "./app-elements.js";
6
+ import { createBfcacheChildSegmentStateKey } from "./bfcache-identity.js";
6
7
  import { AppRouterScrollTarget } from "../shims/app-router-scroll.js";
7
8
  import { hashMountedLayoutIdentity } from "./app-mounted-layouts.js";
8
9
  import { BfcacheChildrenBoundary, BfcacheSegmentBoundary, Children as Children$1, ParallelSlot, Slot } from "../shims/slot.js";
@@ -668,9 +669,11 @@ function buildAppPageElements(options) {
668
669
  branchAccessBoundaries.set(treePosition, entries);
669
670
  }
670
671
  const branchErrors = /* @__PURE__ */ new Map();
671
- if (overrideOrPageComponent && hasSlotTreeOverride) for (const [index, errorModule] of (slotOverride?.errorModules ?? []).entries()) {
672
+ const slotErrorModules = !overrideOrPageComponent ? void 0 : hasSlotTreeOverride ? slotOverride?.errorModules : slot.errors;
673
+ const slotErrorTreePositions = hasSlotTreeOverride ? slotOverride?.errorTreePositions : slot.errorTreePositions;
674
+ for (const [index, errorModule] of (slotErrorModules ?? []).entries()) {
672
675
  const component = getErrorBoundaryExport(errorModule);
673
- const treePosition = slotOverride?.errorTreePositions?.[index];
676
+ const treePosition = slotErrorTreePositions?.[index];
674
677
  if (component && treePosition !== void 0) branchErrors.set(treePosition, component);
675
678
  }
676
679
  const resolveSlotNotFoundBoundary = (treePosition) => {
@@ -752,12 +755,6 @@ function buildAppPageElements(options) {
752
755
  ])).filter((treePosition) => !prefetchSlotLoadingEntry || !isOwnedAtRoutePrefetchCutoff && treePosition <= prefetchSlotLoadingEntry.treePosition).sort((left, right) => left - right);
753
756
  for (let index = branchTreePositions.length - 1; index >= 0; index--) {
754
757
  const treePosition = branchTreePositions[index];
755
- const nestedBfcacheSegment = nestedBfcacheSegments.find((segment) => segment.treePosition === treePosition);
756
- if (nestedBfcacheSegment) slotElement = /* @__PURE__ */ jsx(BfcacheSegmentBoundary, {
757
- id: nestedBfcacheSegment.id,
758
- stateKey: nestedBfcacheSegment.stateKey,
759
- children: slotElement
760
- });
761
758
  const loadingComponents = prefetchSlotLoadingEntry ? [] : branchLoadings.get(treePosition) ?? [];
762
759
  for (let loadingIndex = loadingComponents.length - 1; loadingIndex >= 0; loadingIndex--) {
763
760
  const LoadingComponent = loadingComponents[loadingIndex];
@@ -785,6 +782,12 @@ function buildAppPageElements(options) {
785
782
  fallback: SegmentErrorComponent,
786
783
  children: slotElement
787
784
  });
785
+ const nestedBfcacheSegment = nestedBfcacheSegments.find((segment) => segment.treePosition === treePosition);
786
+ if (nestedBfcacheSegment) slotElement = /* @__PURE__ */ jsx(BfcacheSegmentBoundary, {
787
+ id: nestedBfcacheSegment.id,
788
+ stateKey: nestedBfcacheSegment.stateKey,
789
+ children: slotElement
790
+ });
788
791
  const layoutEntriesAtPosition = branchLayouts.get(treePosition) ?? [];
789
792
  for (let layoutIndex = layoutEntriesAtPosition.length - 1; layoutIndex >= 0; layoutIndex--) {
790
793
  const layoutEntry = layoutEntriesAtPosition[layoutIndex];
@@ -876,8 +879,22 @@ function buildAppPageElements(options) {
876
879
  fallback: pageErrorComponent,
877
880
  children: routeChildren
878
881
  });
879
- const renderedTreePositions = isPrefetchLoadingShell ? orderedTreePositions.filter(includesPrefetchTreePosition) : orderedTreePositions;
880
- let childSegmentElementId = pageElementId;
882
+ const firstLayoutWithElementTreePosition = layoutEntries.find((entry) => getDefaultExport(entry.layoutModule) !== null)?.treePosition;
883
+ const siblingInterceptSegmentKeys = options.siblingInterceptSegmentKeys ?? null;
884
+ const keepsSegmentLevelLists = String(process.env.__NEXT_CACHE_COMPONENTS) === "true" && firstLayoutWithElementTreePosition !== void 0 && !options.route.childrenSlot?.synthetic && (options.semanticPageIdentity === void 0 || siblingInterceptSegmentKeys !== null);
885
+ const spineSegmentCount = siblingInterceptSegmentKeys?.length ?? routeSegments.length;
886
+ const spineTreePositions = keepsSegmentLevelLists ? Array.from(/* @__PURE__ */ new Set([...orderedTreePositions, ...Array.from({ length: Math.max(0, spineSegmentCount - firstLayoutWithElementTreePosition) }, (_, offset) => firstLayoutWithElementTreePosition + 1 + offset)])).sort((left, right) => left - right) : orderedTreePositions;
887
+ const renderedTreePositions = isPrefetchLoadingShell ? spineTreePositions.filter(includesPrefetchTreePosition) : spineTreePositions;
888
+ const firstLayoutBelowShell = keepsSegmentLevelLists && isPrefetchLoadingShell ? layoutEntries.find((entry) => !includesPrefetchTreePosition(entry.treePosition) && getDefaultExport(entry.layoutModule) !== null) : void 0;
889
+ let childSegmentElementId = firstLayoutBelowShell?.id ?? pageElementId;
890
+ let childSegmentTreePosition = firstLayoutBelowShell?.treePosition ?? spineSegmentCount + 1;
891
+ const retainChildSegment = (treePosition, children) => treePosition === childSegmentTreePosition - 1 ? /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
892
+ id: childSegmentElementId,
893
+ children
894
+ }) : /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
895
+ stateKey: createBfcacheChildSegmentStateKey(siblingInterceptSegmentKeys ? JSON.stringify(siblingInterceptSegmentKeys.slice(0, treePosition + 1)) : resolveAppPageTemplateStateKey(routeSegments, treePosition, options.matchedParams)),
896
+ children
897
+ });
881
898
  for (let index = renderedTreePositions.length - 1; index >= 0; index--) {
882
899
  const treePosition = renderedTreePositions[index];
883
900
  const segmentResetKey = resolveRouteSegmentResetKey(treePosition);
@@ -927,11 +944,12 @@ function buildAppPageElements(options) {
927
944
  children: segmentChildren
928
945
  }, String(process.env.__NEXT_CACHE_COMPONENTS) === "true" ? void 0 : templateStateKey);
929
946
  }
947
+ const layoutHasElement = layoutEntry !== void 0 && getDefaultExport(layoutEntry.layoutModule) !== null;
948
+ if (keepsSegmentLevelLists && !layoutHasElement && treePosition > firstLayoutWithElementTreePosition && treePosition < childSegmentTreePosition) segmentChildren = retainChildSegment(treePosition, segmentChildren);
930
949
  if (!layoutEntry) {
931
950
  routeChildren = segmentChildren;
932
951
  continue;
933
952
  }
934
- const layoutHasElement = getDefaultExport(layoutEntry.layoutModule) !== null;
935
953
  const layoutIndex = layoutIndicesByTreePosition.get(treePosition) ?? -1;
936
954
  const segmentMap = { children: resolveAppPageLayoutSegmentProviderSegments(options.route.childrenRouteSegments ?? routeSegments, layoutEntry.treePosition, options.matchedParams) };
937
955
  for (const [slotKey, slot] of Object.entries(options.route.slots ?? {})) {
@@ -949,13 +967,16 @@ function buildAppPageElements(options) {
949
967
  children: layoutHasElement ? /* @__PURE__ */ jsx(Slot, {
950
968
  id: layoutEntry.id,
951
969
  parallelSlots: createAppPageParallelSlotEntries(layoutIndex, layoutEntries, options.route, getEffectiveSlotParams, resolveSlotOverride),
952
- children: /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
970
+ children: keepsSegmentLevelLists ? retainChildSegment(treePosition, segmentChildren) : /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
953
971
  id: childSegmentElementId,
954
972
  children: segmentChildren
955
973
  })
956
974
  }) : segmentChildren
957
975
  });
958
- if (layoutHasElement) childSegmentElementId = layoutEntry.id;
976
+ if (layoutHasElement) {
977
+ childSegmentElementId = layoutEntry.id;
978
+ childSegmentTreePosition = treePosition;
979
+ }
959
980
  }
960
981
  const globalErrorComponent = getErrorBoundaryExport(options.globalErrorModule);
961
982
  routeChildren = /* @__PURE__ */ jsx(GlobalErrorBoundary, {
@@ -1,7 +1,7 @@
1
1
  import { AppElementsWire } from "./app-elements-wire.js";
2
2
  import "./app-elements.js";
3
3
  import { createNestedBfcacheSlotSegmentId, deriveBfcacheSegmentIdentity } from "./bfcache-identity.js";
4
- import { resolveAppPageRouteStateKey, resolveAppPageSemanticSegmentStateKey, resolveAppPageTemplateStateKey } from "./app-page-segment-state.js";
4
+ import { APP_PAGE_DEFAULT_SEGMENT_KEY, APP_PAGE_SEGMENT_KEY, resolveAppPageRouteStateKey, resolveAppPageSemanticSegmentStateKey, resolveAppPageTemplateStateKey } from "./app-page-segment-state.js";
5
5
  //#region src/server/app-page-segment-plan.ts
6
6
  function requireGraphSequenceId(ids, index, kind) {
7
7
  const id = ids[index];
@@ -118,6 +118,14 @@ function createAppPageSegmentPlan(options) {
118
118
  treePosition
119
119
  });
120
120
  }
121
+ if (semanticSegments.length > 0) {
122
+ const leafKey = slotBinding.state === "default" ? APP_PAGE_DEFAULT_SEGMENT_KEY : APP_PAGE_SEGMENT_KEY;
123
+ semanticSegments.push({
124
+ identityKey: JSON.stringify([...boundSegmentKeys, leafKey]),
125
+ stateKey: leafKey,
126
+ treePosition: branchSegments.length
127
+ });
128
+ }
121
129
  }
122
130
  const includedInPayload = options.includeSlot(ownerTreePosition, targetTreePosition);
123
131
  const graphId = graphIds ? graphIds.slots[slotKey] : slot.id ?? slotId;
@@ -1,8 +1,17 @@
1
1
  import { AppPageParams } from "./app-page-boundary.js";
2
2
  //#region src/server/app-page-segment-state.d.ts
3
3
  export declare const APP_PAGE_SEGMENT_KEY = "__PAGE__";
4
+ export declare const APP_PAGE_DEFAULT_SEGMENT_KEY = "__DEFAULT__";
4
5
  export declare function isAppPageRouteGroupSegment(segment: string): boolean;
5
6
  export declare function canonicalizeAppPageParams(params: AppPageParams): void;
7
+ /**
8
+ * Rebinds segment state keys and BFCache identities resolved with one set of
9
+ * params to another. Keys are JSON arrays of segment keys, nested to any
10
+ * depth, so each whole segment key is compared with the param tokens
11
+ * `resolveSingleSegmentStateKey` produced for the learned params; only those
12
+ * whose value differs are replaced. Returns null when no token changes.
13
+ */
14
+ export declare function createAppPageSegmentStateKeyRebinder(fromParams: AppPageParams, toParams: AppPageParams): ((stateKey: string) => string) | null;
6
15
  export declare function resolveAppPageChildSegments(routeSegments: readonly string[], treePosition: number, params: AppPageParams): string[];
7
16
  export declare function resolveAppPageSegmentStateKey(routeSegments: readonly string[], treePosition: number, params: AppPageParams): string;
8
17
  /**
@@ -1,6 +1,7 @@
1
1
  import { isCatchAllSegment, isOptionalCatchAllSegment } from "../routing/utils.js";
2
2
  //#region src/server/app-page-segment-state.ts
3
3
  const APP_PAGE_SEGMENT_KEY = "__PAGE__";
4
+ const APP_PAGE_DEFAULT_SEGMENT_KEY = "__DEFAULT__";
4
5
  function isDynamicSegment(segment) {
5
6
  return segment.startsWith("[") && segment.endsWith("]") && !segment.includes(".");
6
7
  }
@@ -49,6 +50,58 @@ function resolveSingleSegmentStateKey(segment, params) {
49
50
  if (!param) return segment;
50
51
  return `${param.name}|${formatSegmentStateParamValue(param, params, segment)}|${param.type}`;
51
52
  }
53
+ const SEGMENT_PARAM_TYPES = [
54
+ "d",
55
+ "c",
56
+ "oc"
57
+ ];
58
+ function formatSegmentParamPattern(param) {
59
+ if (param.type === "oc") return `[[...${param.name}]]`;
60
+ if (param.type === "c") return `[...${param.name}]`;
61
+ return `[${param.name}]`;
62
+ }
63
+ /**
64
+ * Rebinds segment state keys and BFCache identities resolved with one set of
65
+ * params to another. Keys are JSON arrays of segment keys, nested to any
66
+ * depth, so each whole segment key is compared with the param tokens
67
+ * `resolveSingleSegmentStateKey` produced for the learned params; only those
68
+ * whose value differs are replaced. Returns null when no token changes.
69
+ */
70
+ function createAppPageSegmentStateKeyRebinder(fromParams, toParams) {
71
+ const tokens = /* @__PURE__ */ new Map();
72
+ for (const name of /* @__PURE__ */ new Set([...Object.keys(fromParams), ...Object.keys(toParams)])) for (const type of SEGMENT_PARAM_TYPES) {
73
+ const param = {
74
+ name,
75
+ type
76
+ };
77
+ const fallbackSegment = formatSegmentParamPattern(param);
78
+ const from = formatSegmentStateParamValue(param, fromParams, fallbackSegment);
79
+ const to = formatSegmentStateParamValue(param, toParams, fallbackSegment);
80
+ if (from !== to) tokens.set(`${name}|${from}|${type}`, `${name}|${to}|${type}`);
81
+ }
82
+ if (tokens.size === 0) return null;
83
+ const rebind = (stateKey) => {
84
+ const token = tokens.get(stateKey);
85
+ if (token !== void 0) return token;
86
+ if (!stateKey.startsWith("[")) return stateKey;
87
+ let parts;
88
+ try {
89
+ parts = JSON.parse(stateKey);
90
+ } catch {
91
+ return stateKey;
92
+ }
93
+ if (!Array.isArray(parts)) return stateKey;
94
+ let changed = false;
95
+ const rebound = parts.map((part) => {
96
+ if (typeof part !== "string") return part;
97
+ const next = rebind(part);
98
+ if (next !== part) changed = true;
99
+ return next;
100
+ });
101
+ return changed ? JSON.stringify(rebound) : stateKey;
102
+ };
103
+ return rebind;
104
+ }
52
105
  function resolveAppPageChildSegments(routeSegments, treePosition, params) {
53
106
  const rawSegments = routeSegments.slice(treePosition);
54
107
  const resolvedSegments = [];
@@ -104,4 +157,4 @@ function resolveAppPageTemplateStateKey(routeSegments, treePosition, params) {
104
157
  return statePath.length > 0 ? JSON.stringify(statePath) : "";
105
158
  }
106
159
  //#endregion
107
- export { APP_PAGE_SEGMENT_KEY, canonicalizeAppPageParams, isAppPageRouteGroupSegment, resolveAppPageChildSegments, resolveAppPagePatternStateKey, resolveAppPageRouteStateKey, resolveAppPageSegmentStateKey, resolveAppPageSemanticSegmentStateKey, resolveAppPageTemplateStateKey };
160
+ export { APP_PAGE_DEFAULT_SEGMENT_KEY, APP_PAGE_SEGMENT_KEY, canonicalizeAppPageParams, createAppPageSegmentStateKeyRebinder, isAppPageRouteGroupSegment, resolveAppPageChildSegments, resolveAppPagePatternStateKey, resolveAppPageRouteStateKey, resolveAppPageSegmentStateKey, resolveAppPageSemanticSegmentStateKey, resolveAppPageTemplateStateKey };
@@ -96,10 +96,13 @@ export type AppPageSsrHandler = {
96
96
  /** Mirror inline Flight chunks into Next.js's `self.__next_f` transport, or start
97
97
  * mirroring once a function returns true. */
98
98
  mirrorNextFlight?: boolean | (() => boolean);
99
- /** When true, an SSR-phase-only shell render error resolves to the
100
- * default `__next_error__` error-document shell (with the original
101
- * flight payload and bootstrap) instead of rejecting. See handleSsr. */
99
+ /** When true, a shell render error other than a redirect, HTTP access
100
+ * fallback or bail-out resolves to the default `__next_error__`
101
+ * error-document shell (with the original flight payload and
102
+ * bootstrap) instead of rejecting. See handleSsr. */
102
103
  fallbackToErrorDocumentOnShellError?: boolean;
104
+ /** Whether the RSC render threw the given shell error. See handleSsr. */
105
+ isServerComponentError?: (error: unknown) => boolean;
103
106
  /** The parallel slot not-found boundaries that catch a shell error. See handleSsr. */
104
107
  resolveCaughtNotFoundBoundaries?: (error: unknown) => readonly string[] | null;
105
108
  dynamicStaleTimeSeconds?: number;
@@ -153,8 +156,11 @@ type RenderAppPageHtmlStreamOptions = {
153
156
  isForceStatic?: boolean;
154
157
  /** Production render that may be stored under a query-free key. */
155
158
  isCacheCandidate?: boolean;
156
- /** Override the default shell-error recovery decision passed to handleSsr. */
159
+ /** Whether handleSsr answers a shell error with the error document. Off by
160
+ * default: a boundary render that fails keeps rejecting. */
157
161
  fallbackToErrorDocumentOnShellError?: boolean;
162
+ /** Whether the RSC render threw the given shell error. See handleSsr. */
163
+ isServerComponentError?: (error: unknown) => boolean;
158
164
  /** The parallel slot not-found boundaries that catch a shell error. See handleSsr. */
159
165
  resolveCaughtNotFoundBoundaries?: (error: unknown) => readonly string[] | null;
160
166
  /** Dev-only: original server error to surface in the browser overlay. */
@@ -164,10 +170,6 @@ type RenderAppPageHtmlStreamOptions = {
164
170
  /** Mirror inline Flight chunks into Next.js's `self.__next_f` transport, or start
165
171
  * mirroring once a function returns true. */
166
172
  mirrorNextFlight?: boolean | (() => boolean);
167
- /** True when the app supplies a custom global-error.tsx. Disables the
168
- * default error-document shell fallback so SSR shell errors keep driving
169
- * the server-rendered global-error boundary re-render. */
170
- hasCustomGlobalError?: boolean;
171
173
  };
172
174
  type RenderAppPageHtmlResponseOptions = {
173
175
  clearRequestContext: () => void;