vinext 1.3.1 → 1.4.1

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 (271) hide show
  1. package/README.md +25 -22
  2. package/THIRD_PARTY_LICENSES.md +47 -0
  3. package/dist/_virtual/_rolldown/runtime.js +3 -2
  4. package/dist/build/prerender.js +4 -3
  5. package/dist/build/report.d.ts +0 -14
  6. package/dist/build/report.js +1 -51
  7. package/dist/check.js +4 -7
  8. package/dist/cli.js +3 -0
  9. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  10. package/dist/client/dev-error-overlay.d.ts +2 -0
  11. package/dist/client/dev-error-overlay.js +20 -4
  12. package/dist/client/navigation-runtime.d.ts +13 -1
  13. package/dist/client/navigation-runtime.js +1 -1
  14. package/dist/config/config-matchers.js +2 -0
  15. package/dist/config/destination-params.d.ts +2 -1
  16. package/dist/config/destination-params.js +52 -2
  17. package/dist/config/next-config.d.ts +25 -0
  18. package/dist/config/next-config.js +82 -4
  19. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-codec.js +327 -0
  20. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-data.js +123 -0
  21. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/index.js +29 -0
  22. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/internal.js +134 -0
  23. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-codec.js +50 -0
  24. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data-generated.js +455 -0
  25. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data.js +147 -0
  26. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/big5-added.json.js +169 -0
  27. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp936.json.js +2584 -0
  28. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp949.json.js +2220 -0
  29. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp950.json.js +227 -0
  30. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/eucjp.json.js +425 -0
  31. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gb18030-ranges.json.js +427 -0
  32. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gbk-added.json.js +229 -0
  33. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/shiftjis.json.js +292 -0
  34. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf16.js +106 -0
  35. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf7.js +182 -0
  36. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/bom-handling.js +42 -0
  37. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/extend-node.js +147 -0
  38. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/index.js +94 -0
  39. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/streams.js +95 -0
  40. package/dist/deps/.pnpm/safer-buffer@2.1.2/deps/safer-buffer/safer.js +45 -0
  41. package/dist/entries/app-rsc-entry.js +17 -49
  42. package/dist/entries/app-rsc-manifest.js +3 -4
  43. package/dist/entries/app-ssr-entry.js +1 -0
  44. package/dist/entries/pages-server-entry.js +36 -9
  45. package/dist/index.js +112 -90
  46. package/dist/init-cloudflare.d.ts +1 -3
  47. package/dist/init-cloudflare.js +10 -48
  48. package/dist/init-platform.d.ts +2 -0
  49. package/dist/init-platform.js +4 -0
  50. package/dist/plugins/extensionless-dynamic-import.js +6 -2
  51. package/dist/plugins/next-intl-catalog.d.ts +12 -0
  52. package/dist/plugins/next-intl-catalog.js +85 -0
  53. package/dist/plugins/pages-node-externals.d.ts +1 -0
  54. package/dist/plugins/pages-node-externals.js +1 -1
  55. package/dist/plugins/strip-server-exports.js +2 -1
  56. package/dist/plugins/use-cache-callable.d.ts +1 -1
  57. package/dist/plugins/use-cache-callable.js +136 -94
  58. package/dist/routing/app-route-graph.d.ts +6 -0
  59. package/dist/routing/app-route-graph.js +9 -1
  60. package/dist/server/api-handler.js +21 -5
  61. package/dist/server/app-bfcache-identity.js +1 -1
  62. package/dist/server/app-browser-entry.js +86 -66
  63. package/dist/server/app-browser-history-controller.d.ts +5 -4
  64. package/dist/server/app-browser-history-controller.js +93 -15
  65. package/dist/server/app-browser-navigation-controller.d.ts +2 -2
  66. package/dist/server/app-browser-navigation-controller.js +1 -2
  67. package/dist/server/app-browser-rsc-redirect.js +7 -7
  68. package/dist/server/app-browser-server-action-client.d.ts +2 -0
  69. package/dist/server/app-browser-server-action-client.js +5 -4
  70. package/dist/server/app-browser-state.d.ts +2 -4
  71. package/dist/server/app-browser-state.js +9 -4
  72. package/dist/server/app-browser-supplemental-refresh.js +16 -1
  73. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  74. package/dist/server/app-browser-visible-commit.js +4 -14
  75. package/dist/server/app-elements-wire.d.ts +6 -38
  76. package/dist/server/app-elements-wire.js +4 -41
  77. package/dist/server/app-elements.d.ts +2 -2
  78. package/dist/server/app-elements.js +2 -2
  79. package/dist/server/app-history-state.d.ts +2 -2
  80. package/dist/server/app-history-state.js +5 -2
  81. package/dist/server/app-mounted-layouts.d.ts +21 -0
  82. package/dist/server/app-mounted-layouts.js +67 -0
  83. package/dist/server/app-optimistic-routing.d.ts +19 -1
  84. package/dist/server/app-optimistic-routing.js +43 -4
  85. package/dist/server/app-page-boundary-render.js +10 -14
  86. package/dist/server/app-page-boundary.js +2 -2
  87. package/dist/server/app-page-cache-finalizer.d.ts +12 -1
  88. package/dist/server/app-page-cache-finalizer.js +70 -29
  89. package/dist/server/app-page-cache.d.ts +13 -1
  90. package/dist/server/app-page-cache.js +31 -9
  91. package/dist/server/app-page-dispatch.d.ts +5 -11
  92. package/dist/server/app-page-dispatch.js +84 -155
  93. package/dist/server/app-page-element-builder.d.ts +2 -2
  94. package/dist/server/app-page-element-builder.js +13 -12
  95. package/dist/server/app-page-execution.d.ts +17 -52
  96. package/dist/server/app-page-execution.js +102 -95
  97. package/dist/server/app-page-head.d.ts +13 -0
  98. package/dist/server/app-page-head.js +44 -16
  99. package/dist/server/app-page-http-access-fallback-metadata.d.ts +1 -0
  100. package/dist/server/app-page-http-access-fallback-metadata.js +2 -2
  101. package/dist/server/app-page-layout-gate.d.ts +35 -0
  102. package/dist/server/app-page-layout-gate.js +50 -0
  103. package/dist/server/app-page-params.d.ts +0 -1
  104. package/dist/server/app-page-params.js +1 -14
  105. package/dist/server/app-page-probe.d.ts +3 -21
  106. package/dist/server/app-page-probe.js +4 -37
  107. package/dist/server/app-page-render.d.ts +2 -13
  108. package/dist/server/app-page-render.js +103 -234
  109. package/dist/server/app-page-request.d.ts +1 -3
  110. package/dist/server/app-page-request.js +1 -1
  111. package/dist/server/app-page-response.js +3 -3
  112. package/dist/server/app-page-route-wiring.d.ts +13 -6
  113. package/dist/server/app-page-route-wiring.js +86 -63
  114. package/dist/server/app-page-rsc-render-status.d.ts +27 -1
  115. package/dist/server/app-page-rsc-render-status.js +121 -13
  116. package/dist/server/app-page-stream.d.ts +12 -8
  117. package/dist/server/app-page-stream.js +4 -3
  118. package/dist/server/app-pages-bridge.d.ts +4 -0
  119. package/dist/server/app-pages-bridge.js +7 -3
  120. package/dist/server/app-render-dependency.d.ts +11 -4
  121. package/dist/server/app-render-dependency.js +86 -38
  122. package/dist/server/app-request-stage-dispatch.js +1 -1
  123. package/dist/server/app-request-stage-independent-entry.js +1 -1
  124. package/dist/server/app-route-handler-cache.js +2 -2
  125. package/dist/server/app-route-handler-dispatch.js +14 -9
  126. package/dist/server/app-route-handler-execution.js +11 -5
  127. package/dist/server/app-route-handler-policy.d.ts +1 -1
  128. package/dist/server/app-route-handler-policy.js +3 -3
  129. package/dist/server/app-route-handler-response.js +1 -1
  130. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  131. package/dist/server/app-route-handler-runtime.js +28 -11
  132. package/dist/server/app-router-entry.js +1 -1
  133. package/dist/server/app-rsc-cache-busting.d.ts +12 -12
  134. package/dist/server/app-rsc-cache-busting.js +31 -72
  135. package/dist/server/app-rsc-error-handler.d.ts +2 -1
  136. package/dist/server/app-rsc-error-handler.js +10 -2
  137. package/dist/server/app-rsc-errors.d.ts +8 -0
  138. package/dist/server/app-rsc-errors.js +11 -1
  139. package/dist/server/app-rsc-handler.d.ts +5 -5
  140. package/dist/server/app-rsc-handler.js +80 -83
  141. package/dist/server/app-rsc-request-normalization.d.ts +10 -16
  142. package/dist/server/app-rsc-request-normalization.js +12 -18
  143. package/dist/server/app-rsc-response-finalizer.js +2 -2
  144. package/dist/server/app-rsc-response-stage.js +9 -15
  145. package/dist/server/app-server-action-execution.d.ts +3 -1
  146. package/dist/server/app-server-action-execution.js +15 -8
  147. package/dist/server/app-ssr-entry.d.ts +8 -0
  148. package/dist/server/app-ssr-entry.js +22 -16
  149. package/dist/server/app-ssr-stream.js +1 -1
  150. package/dist/server/app-visited-response-cache.d.ts +36 -1
  151. package/dist/server/app-visited-response-cache.js +46 -5
  152. package/dist/server/app-worker-stages.d.ts +37 -6
  153. package/dist/server/app-worker-stages.js +81 -9
  154. package/dist/server/artifact-compatibility.d.ts +0 -1
  155. package/dist/server/artifact-compatibility.js +1 -10
  156. package/dist/server/bfcache-identity.d.ts +7 -0
  157. package/dist/server/bfcache-identity.js +10 -1
  158. package/dist/server/cache-proof.d.ts +1 -115
  159. package/dist/server/cache-proof.js +1 -544
  160. package/dist/server/cacheability-manifest.js +1 -2
  161. package/dist/server/cacheability-request.js +13 -8
  162. package/dist/server/dev-origin-check.d.ts +2 -1
  163. package/dist/server/dev-origin-check.js +2 -1
  164. package/dist/server/dev-server.js +6 -4
  165. package/dist/server/headers.d.ts +6 -3
  166. package/dist/server/headers.js +9 -5
  167. package/dist/server/image-optimization.d.ts +5 -0
  168. package/dist/server/image-optimization.js +6 -1
  169. package/dist/server/isr-cache.d.ts +8 -2
  170. package/dist/server/isr-cache.js +30 -2
  171. package/dist/server/metadata-route-response.js +32 -15
  172. package/dist/server/middleware-matcher.js +19 -6
  173. package/dist/server/middleware-runtime.js +6 -2
  174. package/dist/server/multi-stage.d.ts +57 -12
  175. package/dist/server/multi-stage.js +13 -1
  176. package/dist/server/navigation-planner.js +1 -1
  177. package/dist/server/pages-api-route.js +28 -19
  178. package/dist/server/pages-media-type.d.ts +21 -1
  179. package/dist/server/pages-media-type.js +69 -5
  180. package/dist/server/pages-node-compat.d.ts +1 -1
  181. package/dist/server/pages-node-compat.js +54 -27
  182. package/dist/server/pages-page-data.js +2 -1
  183. package/dist/server/pages-page-handler.js +28 -26
  184. package/dist/server/pages-page-response.d.ts +9 -0
  185. package/dist/server/pages-page-response.js +18 -4
  186. package/dist/server/pages-request-pipeline.d.ts +8 -0
  187. package/dist/server/pages-request-pipeline.js +2 -2
  188. package/dist/server/pages-request-stage-entry.js +36 -14
  189. package/dist/server/pages-response-stage-entry.js +9 -3
  190. package/dist/server/pages-router-entry.js +3 -2
  191. package/dist/server/prod-server.d.ts +2 -2
  192. package/dist/server/prod-server.js +47 -9
  193. package/dist/server/request-error-log.d.ts +9 -0
  194. package/dist/server/request-error-log.js +13 -0
  195. package/dist/server/request-pipeline.d.ts +15 -4
  196. package/dist/server/request-pipeline.js +43 -15
  197. package/dist/server/rsc-completion-metadata.js +3 -0
  198. package/dist/server/shared-cache-identity.d.ts +23 -0
  199. package/dist/server/shared-cache-identity.js +36 -0
  200. package/dist/server/static-file-cache.d.ts +9 -0
  201. package/dist/server/static-file-cache.js +13 -2
  202. package/dist/server/worker-stages.d.ts +16 -3
  203. package/dist/server/worker-stages.js +23 -4
  204. package/dist/shims/cache-handler.js +2 -2
  205. package/dist/shims/cache-request-state.d.ts +23 -26
  206. package/dist/shims/cache-request-state.js +22 -57
  207. package/dist/shims/cache-runtime.d.ts +3 -1
  208. package/dist/shims/cache-runtime.js +94 -48
  209. package/dist/shims/cache.d.ts +16 -10
  210. package/dist/shims/cache.js +47 -36
  211. package/dist/shims/cacheability-classification.d.ts +2 -0
  212. package/dist/shims/cdn-cache.d.ts +22 -5
  213. package/dist/shims/client-hook-error.d.ts +2 -0
  214. package/dist/shims/client-hook-error.js +16 -1
  215. package/dist/shims/client-page-root.js +2 -2
  216. package/dist/shims/dynamic.js +9 -6
  217. package/dist/shims/error-boundary.js +2 -2
  218. package/dist/shims/fetch-cache-state.d.ts +5 -1
  219. package/dist/shims/fetch-cache-state.js +1 -3
  220. package/dist/shims/fetch-cache.d.ts +2 -1
  221. package/dist/shims/fetch-cache.js +39 -25
  222. package/dist/shims/form.js +2 -2
  223. package/dist/shims/headers.d.ts +10 -24
  224. package/dist/shims/headers.js +22 -70
  225. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  226. package/dist/shims/internal/navigation-untracked.js +1 -1
  227. package/dist/shims/link.js +8 -11
  228. package/dist/shims/metadata.d.ts +23 -21
  229. package/dist/shims/metadata.js +62 -24
  230. package/dist/shims/navigation-server.js +1 -1
  231. package/dist/shims/navigation.d.ts +74 -7
  232. package/dist/shims/navigation.js +184 -31
  233. package/dist/shims/navigation.react-server.js +2 -2
  234. package/dist/shims/request-state-types.d.ts +3 -2
  235. package/dist/shims/slot.d.ts +16 -0
  236. package/dist/shims/slot.js +44 -9
  237. package/dist/shims/unified-request-context.d.ts +7 -1
  238. package/dist/shims/unified-request-context.js +3 -3
  239. package/dist/utils/cache-control-metadata.d.ts +5 -0
  240. package/dist/utils/cache-control-metadata.js +9 -1
  241. package/dist/utils/cache-life-profiles.d.ts +33 -0
  242. package/dist/utils/cache-life-profiles.js +111 -0
  243. package/dist/utils/middleware-request-headers.d.ts +14 -3
  244. package/dist/utils/middleware-request-headers.js +54 -7
  245. package/dist/utils/project.d.ts +2 -2
  246. package/dist/utils/project.js +3 -16
  247. package/dist/utils/regex.d.ts +5 -0
  248. package/dist/utils/regex.js +11 -1
  249. package/dist/utils/text-stream.d.ts +10 -5
  250. package/dist/utils/text-stream.js +6 -6
  251. package/package.json +2 -1
  252. package/dist/build/layout-classification-types.d.ts +0 -60
  253. package/dist/build/layout-classification-types.js +0 -1
  254. package/dist/build/layout-classification.d.ts +0 -62
  255. package/dist/build/layout-classification.js +0 -98
  256. package/dist/build/route-classification-injector.d.ts +0 -33
  257. package/dist/build/route-classification-injector.js +0 -59
  258. package/dist/build/route-classification-manifest.d.ts +0 -54
  259. package/dist/build/route-classification-manifest.js +0 -143
  260. package/dist/server/app-browser-client-reuse-manifest.d.ts +0 -11
  261. package/dist/server/app-browser-client-reuse-manifest.js +0 -101
  262. package/dist/server/app-layout-param-observation.d.ts +0 -42
  263. package/dist/server/app-layout-param-observation.js +0 -168
  264. package/dist/server/client-reuse-manifest.d.ts +0 -103
  265. package/dist/server/client-reuse-manifest.js +0 -209
  266. package/dist/server/skip-cache-proof.d.ts +0 -60
  267. package/dist/server/skip-cache-proof.js +0 -168
  268. package/dist/server/static-layout-client-reuse-proof.d.ts +0 -14
  269. package/dist/server/static-layout-client-reuse-proof.js +0 -33
  270. package/dist/utils/sorted-array.d.ts +0 -8
  271. package/dist/utils/sorted-array.js +0 -20
@@ -2,14 +2,14 @@ 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 { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
6
- import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
7
5
  import { applyPrerenderCacheLifeHeader } from "./prerender-cache-life-header.js";
8
6
  import { markFrameworkLinkHeaders } from "./app-response-header-provenance.js";
7
+ import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
8
+ import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
9
9
  //#region src/server/app-page-response.ts
10
10
  function applyTimingHeader(headers, timing) {
11
11
  if (!timing) return;
12
- const handlerStart = Math.round(timing.handlerStart);
12
+ const handlerStart = Math.round(Date.now() - (performance.now() - timing.handlerStart));
13
13
  const compileMs = timing.compileEnd !== void 0 ? Math.round(timing.compileEnd - timing.handlerStart) : -1;
14
14
  const renderMs = timing.responseKind === "html" && timing.renderEnd !== void 0 && timing.compileEnd !== void 0 ? Math.round(timing.renderEnd - timing.compileEnd) : -1;
15
15
  headers.set(VINEXT_TIMING_HEADER, `${handlerStart},${compileMs},${renderMs}`);
@@ -3,9 +3,7 @@ import { AppElements, AppElementsInterception, AppElementsSlotBinding } from "./
3
3
  import "./app-elements.js";
4
4
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
5
5
  import { AppPageParams } from "./app-page-boundary.js";
6
- import { ThenableParamsObserver } from "../shims/thenable-params.js";
7
6
  import { Metadata, Viewport } from "../shims/metadata.js";
8
- import { AppLayoutParamAccessTracker } from "./app-layout-param-observation.js";
9
7
  import { AppPageRenderDependency } from "./app-render-dependency.js";
10
8
  import { AppPageSemanticSegment, resolveAppPageChildSegments } from "./app-page-segment-state.js";
11
9
  import { AppPageRenderIdentity } from "./app-page-render-identity.js";
@@ -172,7 +170,6 @@ type BuildAppPageRouteElementOptions<TModule extends AppPageModule = AppPageModu
172
170
  */
173
171
  renderElementWithMetadataOutlet?: ((metadataOutlet: ReactNode) => ReactNode) | null;
174
172
  globalErrorModule?: TErrorModule | null;
175
- layoutParamAccess?: AppLayoutParamAccessTracker;
176
173
  makeThenableParams: MakeThenableParams;
177
174
  matchedParams: AppPageParams;
178
175
  metadataPlacement?: "body" | "head";
@@ -211,7 +208,7 @@ export type AppPageRouteHead = {
211
208
  /** `null` when resolution failed: like Next.js, the head then has no viewport tags. */
212
209
  viewport: Promise<Viewport | null>;
213
210
  };
214
- type MakeThenableParams = (params: AppPageParams, observer?: ThenableParamsObserver) => unknown;
211
+ type MakeThenableParams = (params: AppPageParams) => unknown;
215
212
  type BuildAppPageElementsOptions<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = BuildAppPageRouteElementOptions<TModule, TErrorModule> & {
216
213
  interception?: AppElementsInterception | null;
217
214
  interceptionId?: string | null;
@@ -226,6 +223,13 @@ type BuildAppPageElementsOptions<TModule extends AppPageModule = AppPageModule,
226
223
  * still store the route tree, as a CDN would store Next.js's.
227
224
  */
228
225
  isUncacheableRender?: boolean;
226
+ /**
227
+ * Hashed segment identities of the layouts the navigating client holds (see
228
+ * app-mounted-layouts.ts). A render that can never be stored leaves out the
229
+ * outermost layouts it names, as Next.js renders only below where a
230
+ * navigation diverges from the client's router state.
231
+ */
232
+ mountedLayouts?: ReadonlySet<string> | null;
229
233
  mountedSlotIds?: ReadonlySet<string> | null;
230
234
  renderIdentity?: AppPageRenderIdentity;
231
235
  renderMode?: AppRscRenderMode;
@@ -246,9 +250,12 @@ export type AppPageTemplateEntry<TModule extends AppPageModule = AppPageModule>
246
250
  treePosition: number;
247
251
  };
248
252
  export declare function createAppPageTreePath(routeSegments: readonly string[] | null | undefined, treePosition: number): string;
249
- export declare function probeAppPageLayoutWithTracking<TModule extends AppPageModule>(options: {
253
+ /**
254
+ * Renders the layout at `layoutIndex` with a placeholder for its children, so
255
+ * whatever it or a component it renders throws comes out.
256
+ */
257
+ export declare function probeAppPageLayout<TModule extends AppPageModule>(options: {
250
258
  layoutIndex: number;
251
- layoutParamAccess: AppLayoutParamAccessTracker | undefined;
252
259
  makeThenableParams: MakeThenableParams;
253
260
  matchedParams: AppPageParams;
254
261
  route: Pick<AppPageRouteWiringRoute<TModule>, "layoutTreePositions" | "layouts" | "routeSegments">;
@@ -1,24 +1,27 @@
1
1
  import { APP_RSC_RENDER_MODE_PREFETCH_EMPTY, APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL } from "./app-rsc-render-mode.js";
2
2
  import { fnv1a64 } from "../utils/hash.js";
3
3
  import { createInlineScriptTag, escapeHtmlAttr } from "./html.js";
4
- import { createAppRenderDependency, invokeAppComponentMappingErrors, isReactOwnedAppComponent, registerAppElementRenderDependencies, renderAfterAppDependencies, renderAppComponentWithDependencyBarrier } from "./app-render-dependency.js";
5
- import { APP_LAYOUT_IDS_KEY, APP_ROOT_LAYOUT_KEY, APP_STATIC_SIBLINGS_KEY, AppElementsWire, normalizeAppElementsSlotBindings } from "./app-elements-wire.js";
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";
6
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";
7
+ import { AppRouterScrollTarget } from "../shims/app-router-scroll.js";
8
+ import { hashMountedLayoutIdentity } from "./app-mounted-layouts.js";
9
+ import { BfcacheChildrenBoundary, BfcacheSegmentBoundary, Children as Children$1, ParallelSlot, Slot } from "../shims/slot.js";
10
+ import { APP_PAGE_SEGMENT_KEY, isAppPageRouteGroupSegment, resolveAppPageChildSegments, resolveAppPageSegmentStateKey, resolveAppPageTemplateStateKey } from "./app-page-segment-state.js";
7
11
  import DefaultGlobalError from "../shims/default-global-error.js";
8
12
  import { ErrorBoundary, ForbiddenBoundary, GlobalErrorBoundary, HttpAccessFallbackPassthrough, NotFoundBoundary, RedirectBoundary, UnauthorizedBoundary } from "../shims/error-boundary.js";
9
- import { AppRouterScrollTarget } from "../shims/app-router-scroll.js";
10
- import DefaultForbidden from "../shims/default-forbidden.js";
11
- import DefaultNotFound from "../shims/default-not-found.js";
12
- import DefaultUnauthorized from "../shims/default-unauthorized.js";
13
13
  import { LayoutSegmentProvider } from "../shims/layout-segment-context.js";
14
14
  import { withUseCacheLayoutMarker } from "../shims/internal/app-page-props-cache-key.js";
15
15
  import { MetadataHead, ViewportHead, renderMetadataToHtml } from "../shims/metadata.js";
16
- import { BfcacheSegmentBoundary, Children as Children$1, ParallelSlot, Slot } from "../shims/slot.js";
16
+ import { APP_PAGE_NOT_FOUND_BOUNDARY_ID, tagAppPageLayoutError, tagAppPageSlotError } from "./app-page-execution.js";
17
+ import { resolveAppPageBranchParams, resolveAppPageSegmentParams } from "./app-page-params.js";
18
+ import DefaultForbidden from "../shims/default-forbidden.js";
19
+ import DefaultNotFound from "../shims/default-not-found.js";
20
+ import DefaultUnauthorized from "../shims/default-unauthorized.js";
17
21
  import { StreamedIconsInsertion } from "../shims/streamed-icons.js";
18
- import { APP_PAGE_NOT_FOUND_BOUNDARY_ID, tagAppPageSlotError } from "./app-page-execution.js";
19
- import { resolveAppPageBranchParams, resolveAppPageSegmentParamScopeKeys, resolveAppPageSegmentParams } from "./app-page-params.js";
22
+ import { createAppRenderDependency, invokeAppComponentMappingErrors, isReactOwnedAppComponent, renderAfterAppDependencies, renderAppComponentWithDependencyBarrier } from "./app-render-dependency.js";
23
+ import { createAppPageLayoutGate, registerAppPageLayoutGate } from "./app-page-layout-gate.js";
20
24
  import { probeReactServerSubtree } from "./app-page-probe.js";
21
- import { APP_PAGE_SEGMENT_KEY, isAppPageRouteGroupSegment, resolveAppPageChildSegments, resolveAppPageSegmentStateKey, resolveAppPageTemplateStateKey } from "./app-page-segment-state.js";
22
25
  import { createAppPageSegmentPlan } from "./app-page-segment-plan.js";
23
26
  import { resolveAppPageModuleTraceSegment, traceGetLayoutOrPageModule } from "./app-page-tracing.js";
24
27
  import { Fragment, Suspense } from "react";
@@ -44,6 +47,21 @@ function createSlotLayoutElement(LayoutComponent, props, slotLayoutIndex, notFou
44
47
  const SlotLayoutInvoker = () => invokeAppComponentMappingErrors(LayoutComponent, props, (error) => tagAppPageSlotError(error, slotLayoutIndex, notFoundBoundary));
45
48
  return /* @__PURE__ */ jsx(SlotLayoutInvoker, {});
46
49
  }
50
+ /**
51
+ * A route layout, whose notFound(), forbidden() and unauthorized() are caught
52
+ * above it: its own boundaries wrap only its `children`. The layout's index
53
+ * tags what it throws, so the response's fallback finds that boundary from the
54
+ * render. `onSettled` hears how the layout rendered.
55
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/create-component-tree.tsx
56
+ */
57
+ function createRouteLayoutElement(LayoutComponent, props, layoutIndex, dependency, onSettled) {
58
+ const mapError = (error) => tagAppPageLayoutError(error, layoutIndex);
59
+ if (dependency) return renderAppComponentWithDependencyBarrier(LayoutComponent, props, dependency, mapError, onSettled);
60
+ if (isReactOwnedAppComponent(LayoutComponent)) return /* @__PURE__ */ jsx(LayoutComponent, { ...props });
61
+ const RouteLayoutInvoker = () => invokeAppComponentMappingErrors(LayoutComponent, props, mapError, onSettled);
62
+ RouteLayoutInvoker.displayName = LayoutComponent.displayName ?? LayoutComponent.name;
63
+ return /* @__PURE__ */ jsx(RouteLayoutInvoker, {});
64
+ }
47
65
  function getDefaultExport(module) {
48
66
  return module?.default ?? null;
49
67
  }
@@ -55,37 +73,19 @@ function createAppPageTreePath(routeSegments, treePosition) {
55
73
  if (treePathSegments.length === 0) return "/";
56
74
  return `/${treePathSegments.join("/")}`;
57
75
  }
58
- function readFiniteRevalidateSeconds(module) {
59
- const revalidate = module?.revalidate;
60
- return typeof revalidate === "number" && Number.isFinite(revalidate) && revalidate > 0 ? revalidate : null;
61
- }
62
- function recordLayoutSkipObservationScope(options) {
63
- options.layoutParamAccess?.recordLayoutParamScope(options.layoutId, resolveAppPageSegmentParamScopeKeys(options.routeSegments, options.treePosition));
64
- const revalidateSeconds = readFiniteRevalidateSeconds(options.layoutModule);
65
- if (revalidateSeconds !== null) options.layoutParamAccess?.recordLayoutFiniteRevalidate(options.layoutId, revalidateSeconds);
66
- }
67
- function probeAppPageLayoutWithTracking(options) {
76
+ /**
77
+ * Renders the layout at `layoutIndex` with a placeholder for its children, so
78
+ * whatever it or a component it renders throws comes out.
79
+ */
80
+ function probeAppPageLayout(options) {
68
81
  const treePosition = options.route.layoutTreePositions?.[options.layoutIndex] ?? 0;
69
- const treePath = createAppPageTreePath(options.route.routeSegments, treePosition);
70
- const layoutId = AppElementsWire.encodeLayoutId(treePath);
71
- const probe = () => {
72
- const layoutModule = options.route.layouts[options.layoutIndex];
73
- const LayoutComponent = getDefaultExport(layoutModule);
74
- if (!LayoutComponent) return null;
75
- recordLayoutSkipObservationScope({
76
- layoutId,
77
- layoutModule,
78
- layoutParamAccess: options.layoutParamAccess,
79
- routeSegments: options.route.routeSegments,
80
- treePosition
81
- });
82
- const layoutParams = resolveAppPageSegmentParams(options.route.routeSegments, treePosition, options.matchedParams);
83
- return probeReactServerSubtree(/* @__PURE__ */ jsx(LayoutComponent, {
84
- ...withUseCacheLayoutMarker(LayoutComponent, { params: options.makeThenableParams(layoutParams, options.layoutParamAccess?.createThenableParamsObserver(layoutId)) }),
85
- children: APP_PAGE_LAYOUT_PROBE_CHILD
86
- }));
87
- };
88
- return options.layoutParamAccess ? options.layoutParamAccess.runLayoutProbe(layoutId, probe) : probe();
82
+ const LayoutComponent = getDefaultExport(options.route.layouts[options.layoutIndex]);
83
+ if (!LayoutComponent) return null;
84
+ const layoutParams = resolveAppPageSegmentParams(options.route.routeSegments, treePosition, options.matchedParams);
85
+ return probeReactServerSubtree(/* @__PURE__ */ jsx(LayoutComponent, {
86
+ ...withUseCacheLayoutMarker(LayoutComponent, { params: options.makeThenableParams(layoutParams) }),
87
+ children: APP_PAGE_LAYOUT_PROBE_CHILD
88
+ }));
89
89
  }
90
90
  function createAppPageLayoutEntries(route) {
91
91
  return route.layouts.map((layoutModule, index) => {
@@ -426,8 +426,8 @@ function buildAppPageElements(options) {
426
426
  const notFoundBoundaries = resolveAccessBoundaries(options.route.segmentNotFounds, options.route.segmentNotFoundTreePositions, (entry) => entry.notFoundModule, rootNotFoundModule ? null : DEFAULT_NOT_FOUND_COMPONENT);
427
427
  const forbiddenBoundaries = resolveAccessBoundaries(options.route.segmentForbiddens, options.route.segmentForbiddenTreePositions, (entry) => entry.forbiddenModule, rootForbiddenModule ? null : DEFAULT_FORBIDDEN_COMPONENT);
428
428
  const unauthorizedBoundaries = resolveAccessBoundaries(options.route.segmentUnauthorizeds, options.route.segmentUnauthorizedTreePositions, (entry) => entry.unauthorizedModule, rootUnauthorizedModule ? null : DEFAULT_UNAUTHORIZED_COMPONENT);
429
+ const layoutGate = createAppPageLayoutGate();
429
430
  const layoutDependenciesByIndex = /* @__PURE__ */ new Map();
430
- const renderDependenciesByElementId = /* @__PURE__ */ new Map();
431
431
  const layoutDependenciesBefore = [];
432
432
  const slotDependenciesByLayoutIndex = [];
433
433
  const templateDependenciesById = /* @__PURE__ */ new Map();
@@ -488,6 +488,13 @@ function buildAppPageElements(options) {
488
488
  templateEntries
489
489
  });
490
490
  const routeResetKey = segmentPlan.routeResetKey;
491
+ const skippedLayoutIds = /* @__PURE__ */ new Set();
492
+ if (options.mountedLayouts && options.isRscRequest === true && options.isUncacheableRender === true && renderMode === "navigation" && interception === null) for (const layoutEntry of layoutEntries) {
493
+ const identity = segmentPlan.bfcacheSegmentIdentities[layoutEntry.id];
494
+ if (identity === void 0) break;
495
+ if (!options.mountedLayouts.has(hashMountedLayoutIdentity(identity))) break;
496
+ skippedLayoutIds.add(layoutEntry.id);
497
+ }
491
498
  const slotPlansByKey = new Map(segmentPlan.slots.map((slot) => [slot.key, slot]));
492
499
  const resolveRouteSegmentResetKey = (treePosition) => resolveAppPageSegmentStateKey(routeSegments, treePosition, options.matchedParams);
493
500
  const metadataEntries = AppElementsWire.createMetadataEntries({
@@ -530,10 +537,9 @@ function buildAppPageElements(options) {
530
537
  if (layoutIndex !== void 0) {
531
538
  const layoutEntry = layoutEntries[layoutIndex];
532
539
  layoutDependenciesBefore[layoutIndex] = [...pageDependencies];
533
- if (getDefaultExport(layoutEntry.layoutModule)) {
540
+ if (!skippedLayoutIds.has(layoutEntry.id) && getDefaultExport(layoutEntry.layoutModule)) {
534
541
  const layoutDependency = createAppRenderDependency();
535
542
  layoutDependenciesByIndex.set(layoutIndex, layoutDependency);
536
- renderDependenciesByElementId.set(layoutEntry.id, layoutDependency);
537
543
  pageDependencies.push(layoutDependency);
538
544
  }
539
545
  slotDependenciesByLayoutIndex[layoutIndex] = [...pageDependencies];
@@ -581,33 +587,29 @@ function buildAppPageElements(options) {
581
587
  for (let index = 0; index < layoutEntries.length; index++) {
582
588
  const layoutEntry = layoutEntries[index];
583
589
  if (isPrefetchLoadingShell && !includesPrefetchTreePosition(layoutEntry.treePosition)) continue;
590
+ if (skippedLayoutIds.has(layoutEntry.id)) continue;
584
591
  if (!layoutEntry.layoutModule) continue;
585
592
  const layoutComponent = traceGetLayoutOrPageModule(resolveAppPageModuleTraceSegment(routeSegments, layoutEntry.treePosition), () => getDefaultExport(layoutEntry.layoutModule));
586
593
  if (!layoutComponent) continue;
587
594
  const layoutParams = resolveAppPageSegmentParams(options.route.routeSegments, layoutEntry.treePosition, options.matchedParams);
588
- recordLayoutSkipObservationScope({
589
- layoutId: layoutEntry.id,
590
- layoutModule: layoutEntry.layoutModule,
591
- layoutParamAccess: options.layoutParamAccess,
592
- routeSegments: options.route.routeSegments,
593
- treePosition: layoutEntry.treePosition
594
- });
595
- const layoutProps = { params: options.makeThenableParams(layoutParams, options.layoutParamAccess?.createThenableParamsObserver(layoutEntry.id)) };
595
+ const layoutProps = { params: options.makeThenableParams(layoutParams) };
596
596
  for (const slot of Object.values(options.route.slots ?? {})) {
597
597
  const slotName = slot.name;
598
598
  if ((slot.layoutIndex >= 0 ? slot.layoutIndex : layoutEntries.length - 1) !== index) continue;
599
599
  layoutProps[slotName] = /* @__PURE__ */ jsx(ParallelSlot, { name: slotName });
600
600
  }
601
601
  const LayoutComponent = layoutComponent;
602
- const layoutDependency = layoutDependenciesByIndex.get(index);
603
- let layoutElement = layoutDependency ? renderAppComponentWithDependencyBarrier(LayoutComponent, withUseCacheLayoutMarker(LayoutComponent, {
602
+ const ancestorLoadingEntry = findNearestAncestorLoadingEntry(layoutEntry.treePosition);
603
+ const holdsResponse = !ancestorLoadingEntry && !isReactOwnedAppComponent(LayoutComponent);
604
+ if (holdsResponse) layoutGate.hold(layoutEntry.id, index);
605
+ let layoutElement = createRouteLayoutElement(LayoutComponent, withUseCacheLayoutMarker(LayoutComponent, {
604
606
  ...layoutProps,
605
607
  children: /* @__PURE__ */ jsx(Children$1, {})
606
- }), layoutDependency) : /* @__PURE__ */ jsx(LayoutComponent, {
607
- ...withUseCacheLayoutMarker(LayoutComponent, layoutProps),
608
- children: /* @__PURE__ */ jsx(Children$1, {})
608
+ }), index, layoutDependenciesByIndex.get(index), holdsResponse ? (failure) => layoutGate.settle(layoutEntry.id, failure) : void 0);
609
+ layoutElement = /* @__PURE__ */ jsx(RedirectBoundary, {
610
+ resetKey: resolveRouteSegmentResetKey(layoutEntry.treePosition),
611
+ children: layoutElement
609
612
  });
610
- const ancestorLoadingEntry = findNearestAncestorLoadingEntry(layoutEntry.treePosition);
611
613
  const ancestorLoadingComponent = getDefaultExport(ancestorLoadingEntry?.loadingModule);
612
614
  if (ancestorLoadingComponent && ancestorLoadingEntry && isOutermostEntryBelowLoading(ancestorLoadingEntry.treePosition, layoutEntry.treePosition, "layout")) {
613
615
  const AncestorLoadingComponent = ancestorLoadingComponent;
@@ -875,7 +877,20 @@ function buildAppPageElements(options) {
875
877
  fallback: pageErrorComponent,
876
878
  children: routeChildren
877
879
  });
878
- const renderedTreePositions = isPrefetchLoadingShell ? orderedTreePositions.filter(includesPrefetchTreePosition) : orderedTreePositions;
880
+ const firstLayoutWithElementTreePosition = layoutEntries.find((entry) => getDefaultExport(entry.layoutModule) !== null)?.treePosition;
881
+ const keepsSegmentLevelLists = String(process.env.__NEXT_CACHE_COMPONENTS) === "true" && firstLayoutWithElementTreePosition !== void 0 && !options.route.childrenSlot && options.semanticPageIdentity === void 0;
882
+ const spineTreePositions = keepsSegmentLevelLists ? Array.from(/* @__PURE__ */ new Set([...orderedTreePositions, ...Array.from({ length: Math.max(0, routeSegments.length - firstLayoutWithElementTreePosition) }, (_, offset) => firstLayoutWithElementTreePosition + 1 + offset)])).sort((left, right) => left - right) : orderedTreePositions;
883
+ const renderedTreePositions = isPrefetchLoadingShell ? spineTreePositions.filter(includesPrefetchTreePosition) : spineTreePositions;
884
+ const firstLayoutBelowShell = keepsSegmentLevelLists && isPrefetchLoadingShell ? layoutEntries.find((entry) => !includesPrefetchTreePosition(entry.treePosition) && getDefaultExport(entry.layoutModule) !== null) : void 0;
885
+ let childSegmentElementId = firstLayoutBelowShell?.id ?? pageElementId;
886
+ let childSegmentTreePosition = firstLayoutBelowShell?.treePosition ?? routeSegments.length + 1;
887
+ const retainChildSegment = (treePosition, children) => treePosition === childSegmentTreePosition - 1 ? /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
888
+ id: childSegmentElementId,
889
+ children
890
+ }) : /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
891
+ stateKey: createBfcacheChildSegmentStateKey(resolveAppPageTemplateStateKey(routeSegments, treePosition, options.matchedParams)),
892
+ children
893
+ });
879
894
  for (let index = renderedTreePositions.length - 1; index >= 0; index--) {
880
895
  const treePosition = renderedTreePositions[index];
881
896
  const segmentResetKey = resolveRouteSegmentResetKey(treePosition);
@@ -925,11 +940,12 @@ function buildAppPageElements(options) {
925
940
  children: segmentChildren
926
941
  }, String(process.env.__NEXT_CACHE_COMPONENTS) === "true" ? void 0 : templateStateKey);
927
942
  }
943
+ const layoutHasElement = layoutEntry !== void 0 && getDefaultExport(layoutEntry.layoutModule) !== null;
944
+ if (keepsSegmentLevelLists && !layoutHasElement && treePosition > firstLayoutWithElementTreePosition && treePosition < childSegmentTreePosition) segmentChildren = retainChildSegment(treePosition, segmentChildren);
928
945
  if (!layoutEntry) {
929
946
  routeChildren = segmentChildren;
930
947
  continue;
931
948
  }
932
- const layoutHasElement = getDefaultExport(layoutEntry.layoutModule) !== null;
933
949
  const layoutIndex = layoutIndicesByTreePosition.get(treePosition) ?? -1;
934
950
  const segmentMap = { children: resolveAppPageLayoutSegmentProviderSegments(options.route.childrenRouteSegments ?? routeSegments, layoutEntry.treePosition, options.matchedParams) };
935
951
  for (const [slotKey, slot] of Object.entries(options.route.slots ?? {})) {
@@ -947,9 +963,16 @@ function buildAppPageElements(options) {
947
963
  children: layoutHasElement ? /* @__PURE__ */ jsx(Slot, {
948
964
  id: layoutEntry.id,
949
965
  parallelSlots: createAppPageParallelSlotEntries(layoutIndex, layoutEntries, options.route, getEffectiveSlotParams, resolveSlotOverride),
950
- children: segmentChildren
966
+ children: keepsSegmentLevelLists ? retainChildSegment(treePosition, segmentChildren) : /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
967
+ id: childSegmentElementId,
968
+ children: segmentChildren
969
+ })
951
970
  }) : segmentChildren
952
971
  });
972
+ if (layoutHasElement) {
973
+ childSegmentElementId = layoutEntry.id;
974
+ childSegmentTreePosition = treePosition;
975
+ }
953
976
  }
954
977
  const globalErrorComponent = getErrorBoundaryExport(options.globalErrorModule);
955
978
  routeChildren = /* @__PURE__ */ jsx(GlobalErrorBoundary, {
@@ -965,9 +988,9 @@ function buildAppPageElements(options) {
965
988
  createAppPageStreamingMetadataBody(streamingMetadataBodyId)
966
989
  ] });
967
990
  elements[routeId] = pageRenderDependency ? renderAfterAppDependencies(routeElement, [pageRenderDependency]) : routeElement;
968
- Object.assign(elements, metadataEntries, options.route.staticSiblings && options.route.staticSiblings.length > 0 ? { [APP_STATIC_SIBLINGS_KEY]: options.route.staticSiblings } : {}, shouldRenderPrefetchLoadingShell ? { [APP_PREFETCH_LOADING_SHELL_MARKER_KEY]: "LoadingBoundary" } : {});
969
- registerAppElementRenderDependencies(elements, renderDependenciesByElementId);
991
+ Object.assign(elements, metadataEntries, options.route.staticSiblings && options.route.staticSiblings.length > 0 ? { [APP_STATIC_SIBLINGS_KEY]: options.route.staticSiblings } : {}, shouldRenderPrefetchLoadingShell ? { [APP_PREFETCH_LOADING_SHELL_MARKER_KEY]: "LoadingBoundary" } : {}, skippedLayoutIds.size > 0 ? { [APP_SKIPPED_LAYOUT_IDS_KEY]: [...skippedLayoutIds] } : {});
992
+ registerAppPageLayoutGate(elements, layoutGate);
970
993
  return elements;
971
994
  }
972
995
  //#endregion
973
- export { buildAppPageElements, createAppPageLayoutEntries, createAppPageRouteBodyMetadata, createAppPageSourcePage, createAppPageTreePath, probeAppPageLayoutWithTracking, resolveAppPageChildSegments, resolveAppPageLoadingModuleAtOrAbove };
996
+ export { buildAppPageElements, createAppPageLayoutEntries, createAppPageRouteBodyMetadata, createAppPageSourcePage, createAppPageTreePath, probeAppPageLayout, resolveAppPageChildSegments, resolveAppPageLoadingModuleAtOrAbove };
@@ -1,7 +1,9 @@
1
1
  import { NavigationContext } from "../shims/navigation-context-state.js";
2
2
  import "../shims/navigation.js";
3
3
  import { RootParams } from "../shims/root-params.js";
4
- import { AppPageSsrHandler } from "./app-page-stream.js";
4
+ import { InitialNavigationCacheMetadata } from "./app-ssr-stream.js";
5
+ import { AppPageFontData, AppPageSsrHandler } from "./app-page-stream.js";
6
+ import { CdnAppPageDocument } from "../shims/cdn-cache.js";
5
7
  //#region src/server/app-page-rsc-render-status.d.ts
6
8
  /**
7
9
  * The status of a finished RSC render, as Next.js's static generation decides
@@ -10,6 +12,8 @@ import { AppPageSsrHandler } from "./app-page-stream.js";
10
12
  */
11
13
  export type AppPageRscRenderStatus = {
12
14
  kind: "page";
15
+ /** The page's document, when the resolver rendered one to store. */
16
+ document?: CdnAppPageDocument;
13
17
  } | {
14
18
  kind: "special-error";
15
19
  status: number;
@@ -22,9 +26,28 @@ export type AppPageRscRenderStatus = {
22
26
  {
23
27
  kind: "unstorable";
24
28
  };
29
+ /** What the page's document renders with, as an HTML request renders it. */
30
+ type AppPageRscRenderDocumentOptions = {
31
+ clientTraceMetadata?: readonly string[];
32
+ /**
33
+ * When the document's render is given up, as completed-response admission
34
+ * gives up a body it hasn't captured by its deadline.
35
+ */
36
+ deadlineAt?: number;
37
+ getFontData: () => AppPageFontData;
38
+ getInitialNavigationCacheMetadata: () => InitialNavigationCacheMetadata;
39
+ mirrorNextFlight: () => boolean;
40
+ onSsrError: (error: unknown) => unknown;
41
+ reactMaxHeadersLength?: number;
42
+ };
25
43
  type CreateAppPageRscRenderStatusResolverOptions = {
26
44
  basePath?: string;
27
45
  capturedRscData: Promise<ArrayBuffer>;
46
+ /**
47
+ * Render the page's document from the payload, to be stored beside it.
48
+ * Without it, only a render that threw a special error renders its shell.
49
+ */
50
+ document?: AppPageRscRenderDocumentOptions;
28
51
  getCapturedSpecialError: () => unknown;
29
52
  getCapturedSpecialErrors: (error: unknown) => readonly unknown[];
30
53
  isForceStatic: boolean;
@@ -39,6 +62,9 @@ type CreateAppPageRscRenderStatusResolverOptions = {
39
62
  * a render that threw one renders the document's shell from the same payload,
40
63
  * as Next.js's static generation renders HTML beside RSC. Its server
41
64
  * components don't run again. A render without a special error is a 200.
65
+ *
66
+ * With `document`, every render renders its document from the payload, as
67
+ * Next.js keeps a page's document and RSC payload in one entry.
42
68
  */
43
69
  export declare function createAppPageRscRenderStatusResolver(options: CreateAppPageRscRenderStatusResolverOptions): () => Promise<AppPageRscRenderStatus>;
44
70
  /**
@@ -1,5 +1,8 @@
1
- import { resolveAppPageShellSpecialError, resolveAppPageSpecialErrorStoredHeaders } from "./app-page-execution.js";
2
- import { isAppSsrRenderResult } from "./app-page-stream.js";
1
+ import { isRenderDynamicLatched } from "../shims/internal/headers-state.js";
2
+ import "../shims/headers.js";
3
+ import { readStreamAsText } from "../utils/text-stream.js";
4
+ import { buildAppPageFontLinkHeader, resolveAppPageShellSpecialError, resolveAppPageSpecialErrorStoredHeaders } from "./app-page-execution.js";
5
+ import { buildAppPageLinkHeader, isAppSsrRenderResult } from "./app-page-stream.js";
3
6
  //#region src/server/app-page-rsc-render-status.ts
4
7
  const PAGE_STATUS = { kind: "page" };
5
8
  /**
@@ -8,28 +11,109 @@ const PAGE_STATUS = { kind: "page" };
8
11
  * a render that threw one renders the document's shell from the same payload,
9
12
  * as Next.js's static generation renders HTML beside RSC. Its server
10
13
  * components don't run again. A render without a special error is a 200.
14
+ *
15
+ * With `document`, every render renders its document from the payload, as
16
+ * Next.js keeps a page's document and RSC payload in one entry.
11
17
  */
12
18
  function createAppPageRscRenderStatusResolver(options) {
13
19
  let status;
14
20
  return () => status ??= resolveAppPageRscRenderStatus(options);
15
21
  }
16
22
  async function resolveAppPageRscRenderStatus(options) {
23
+ const deadlineAt = options.document?.deadlineAt;
24
+ if (deadlineAt === void 0) return renderAppPageRscRenderStatus(options);
25
+ const expired = new AbortController();
26
+ const remaining = deadlineAt - Date.now();
27
+ const timer = remaining > 0 ? setTimeout(() => expired.abort(), remaining) : void 0;
28
+ if (timer === void 0) expired.abort();
29
+ try {
30
+ return await unlessExpired(renderAppPageRscRenderStatus(options, expired.signal), expired.signal, () => {}) ?? expiredDocumentStatus(options);
31
+ } finally {
32
+ clearTimeout(timer);
33
+ }
34
+ }
35
+ /** Settle with `promise`, or with `null` once `signal` aborts. */
36
+ function unlessExpired(promise, signal, discard) {
37
+ if (!signal) return promise;
38
+ if (signal.aborted) {
39
+ promise.then(discard, () => {});
40
+ return Promise.resolve(null);
41
+ }
42
+ return new Promise((resolve, reject) => {
43
+ const onAbort = () => {
44
+ promise.then(discard, () => {});
45
+ resolve(null);
46
+ };
47
+ signal.addEventListener("abort", onAbort, { once: true });
48
+ promise.then((value) => {
49
+ signal.removeEventListener("abort", onAbort);
50
+ if (signal.aborted) discard(value);
51
+ else resolve(value);
52
+ }, (error) => {
53
+ signal.removeEventListener("abort", onAbort);
54
+ reject(error);
55
+ });
56
+ });
57
+ }
58
+ /** Read the document, or cancel it and settle with `null` once `signal` aborts. */
59
+ async function readDocumentHtml(stream, signal) {
60
+ if (!signal) return readStreamAsText(stream);
61
+ const reader = stream.getReader();
62
+ const decoder = new TextDecoder();
63
+ const chunks = [];
64
+ for (;;) {
65
+ const result = await unlessExpired(reader.read(), signal, () => {});
66
+ if (result === null) {
67
+ reader.cancel().catch(() => {});
68
+ return null;
69
+ }
70
+ if (result.done) break;
71
+ chunks.push(decoder.decode(result.value, { stream: true }));
72
+ }
73
+ chunks.push(decoder.decode());
74
+ return chunks.join("");
75
+ }
76
+ /**
77
+ * A document given up at its deadline isn't stored. A render without a
78
+ * special error is still a 200, but one with a special error needs its shell.
79
+ */
80
+ function expiredDocumentStatus(options) {
81
+ return options.getCapturedSpecialError() === null ? PAGE_STATUS : { kind: "unstorable" };
82
+ }
83
+ async function renderAppPageRscRenderStatus(options, signal) {
17
84
  const rscData = await options.capturedRscData;
18
- if (options.getCapturedSpecialError() === null) return PAGE_STATUS;
85
+ const documentOptions = options.document;
86
+ if (!documentOptions && options.getCapturedSpecialError() === null) return PAGE_STATUS;
19
87
  const ssrHandler = await options.loadSsrHandler();
88
+ if (signal?.aborted) return expiredDocumentStatus(options);
89
+ const fontData = documentOptions?.getFontData() ?? {
90
+ links: [],
91
+ preloads: [],
92
+ styles: []
93
+ };
94
+ const clientTraceMetadataMarker = documentOptions?.clientTraceMetadata && documentOptions.clientTraceMetadata.length > 0 ? crypto.randomUUID() : void 0;
20
95
  let rendered;
21
96
  try {
22
- rendered = await ssrHandler.handleSsr(new Response(rscData).body, options.navigationContext, {
23
- links: [],
24
- preloads: [],
25
- styles: []
26
- }, {
97
+ rendered = await unlessExpired(ssrHandler.handleSsr(new Response(rscData).body, options.navigationContext, fontData, {
27
98
  basePath: options.basePath,
28
99
  isForceStatic: options.isForceStatic,
29
- isStaticGeneration: true,
30
- onSsrError: () => void 0,
31
100
  preloadRoutePattern: options.routePattern,
32
- rootParams: options.rootParams
101
+ rootParams: options.rootParams,
102
+ ...documentOptions ? {
103
+ clientTraceMetadata: documentOptions.clientTraceMetadata,
104
+ clientTraceMetadataMarker,
105
+ getInitialNavigationCacheMetadata: documentOptions.getInitialNavigationCacheMetadata,
106
+ isCacheCandidate: true,
107
+ mirrorNextFlight: documentOptions.mirrorNextFlight,
108
+ onSsrError: documentOptions.onSsrError,
109
+ reactMaxHeadersLength: documentOptions.reactMaxHeadersLength,
110
+ signal
111
+ } : {
112
+ isStaticGeneration: true,
113
+ onSsrError: () => void 0
114
+ }
115
+ }), signal, (late) => {
116
+ (isAppSsrRenderResult(late) ? late.htmlStream : late).cancel().catch(() => {});
33
117
  });
34
118
  } catch (error) {
35
119
  const specialError = resolveAppPageShellSpecialError(error, options.getCapturedSpecialErrors(error));
@@ -41,8 +125,32 @@ async function resolveAppPageRscRenderStatus(options) {
41
125
  ...headers ? { headers } : {}
42
126
  };
43
127
  }
44
- (isAppSsrRenderResult(rendered) ? rendered.htmlStream : rendered).cancel().catch(() => {});
45
- return PAGE_STATUS;
128
+ if (rendered === null) return expiredDocumentStatus(options);
129
+ const htmlStream = isAppSsrRenderResult(rendered) ? rendered.htmlStream : rendered;
130
+ if (!documentOptions) {
131
+ htmlStream.cancel().catch(() => {});
132
+ return PAGE_STATUS;
133
+ }
134
+ let html;
135
+ try {
136
+ html = await readDocumentHtml(htmlStream, signal);
137
+ } catch {
138
+ return PAGE_STATUS;
139
+ }
140
+ if (html === null) return expiredDocumentStatus(options);
141
+ if (isRenderDynamicLatched()) return PAGE_STATUS;
142
+ const linkHeader = buildAppPageLinkHeader(isAppSsrRenderResult(rendered) ? rendered.linkHeader : void 0, buildAppPageFontLinkHeader(fontData.preloads), documentOptions.reactMaxHeadersLength);
143
+ if (clientTraceMetadataMarker) {
144
+ const { stripClientTraceMetadataBlock } = await import("./client-trace-metadata.js");
145
+ html = stripClientTraceMetadataBlock(html, clientTraceMetadataMarker);
146
+ }
147
+ return {
148
+ kind: "page",
149
+ document: {
150
+ html,
151
+ ...linkHeader ? { linkHeader } : {}
152
+ }
153
+ };
46
154
  }
47
155
  /**
48
156
  * The status Next.js sends an RSC request for a render with this status. Its
@@ -77,6 +77,8 @@ export type AppPageSsrHandler = {
77
77
  };
78
78
  /** Abort signal for a build-time PPR fallback-shell static render. */
79
79
  pprFallbackShellSignal?: AbortSignal;
80
+ /** Aborts a streaming render whose document is given up. */
81
+ signal?: AbortSignal;
80
82
  /** Route whose client references load before the render; null for none. */
81
83
  preloadRoutePattern?: string | null;
82
84
  /** When true, wait for the full React tree before emitting bytes. */
@@ -94,10 +96,13 @@ export type AppPageSsrHandler = {
94
96
  /** Mirror inline Flight chunks into Next.js's `self.__next_f` transport, or start
95
97
  * mirroring once a function returns true. */
96
98
  mirrorNextFlight?: boolean | (() => boolean);
97
- /** When true, an SSR-phase-only shell render error resolves to the
98
- * default `__next_error__` error-document shell (with the original
99
- * 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. */
100
103
  fallbackToErrorDocumentOnShellError?: boolean;
104
+ /** Whether the RSC render threw the given shell error. See handleSsr. */
105
+ isServerComponentError?: (error: unknown) => boolean;
101
106
  /** The parallel slot not-found boundaries that catch a shell error. See handleSsr. */
102
107
  resolveCaughtNotFoundBoundaries?: (error: unknown) => readonly string[] | null;
103
108
  dynamicStaleTimeSeconds?: number;
@@ -151,8 +156,11 @@ type RenderAppPageHtmlStreamOptions = {
151
156
  isForceStatic?: boolean;
152
157
  /** Production render that may be stored under a query-free key. */
153
158
  isCacheCandidate?: boolean;
154
- /** 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. */
155
161
  fallbackToErrorDocumentOnShellError?: boolean;
162
+ /** Whether the RSC render threw the given shell error. See handleSsr. */
163
+ isServerComponentError?: (error: unknown) => boolean;
156
164
  /** The parallel slot not-found boundaries that catch a shell error. See handleSsr. */
157
165
  resolveCaughtNotFoundBoundaries?: (error: unknown) => readonly string[] | null;
158
166
  /** Dev-only: original server error to surface in the browser overlay. */
@@ -162,10 +170,6 @@ type RenderAppPageHtmlStreamOptions = {
162
170
  /** Mirror inline Flight chunks into Next.js's `self.__next_f` transport, or start
163
171
  * mirroring once a function returns true. */
164
172
  mirrorNextFlight?: boolean | (() => boolean);
165
- /** True when the app supplies a custom global-error.tsx. Disables the
166
- * default error-document shell fallback so SSR shell errors keep driving
167
- * the server-rendered global-error boundary re-render. */
168
- hasCustomGlobalError?: boolean;
169
173
  };
170
174
  type RenderAppPageHtmlResponseOptions = {
171
175
  clearRequestContext: () => void;
@@ -1,9 +1,9 @@
1
1
  import { deferUntilStreamConsumed } from "./defer-until-stream-consumed.js";
2
2
  import { VINEXT_RSC_VARY_HEADER } from "./app-rsc-vary.js";
3
3
  import { isNavigationSignalError } from "../utils/navigation-signal.js";
4
- import "./app-rsc-cache-busting.js";
5
- import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
6
4
  import { markFrameworkLinkHeaders } from "./app-response-header-provenance.js";
5
+ import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
6
+ import "./app-rsc-cache-busting.js";
7
7
  import { applyEdgeRuntimeHeader } from "./app-page-response.js";
8
8
  import { getNextErrorDigest } from "./next-error-digest.js";
9
9
  import { isAppRenderAbortError } from "./app-render-abort-error.js";
@@ -86,7 +86,8 @@ async function renderAppPageHtmlStream(options) {
86
86
  initialDevServerError: options.initialDevServerError,
87
87
  onSsrError: options.onSsrError,
88
88
  mirrorNextFlight: options.mirrorNextFlight,
89
- fallbackToErrorDocumentOnShellError: options.fallbackToErrorDocumentOnShellError ?? (options.waitForAllReady !== true && options.hasCustomGlobalError === false),
89
+ fallbackToErrorDocumentOnShellError: options.fallbackToErrorDocumentOnShellError,
90
+ isServerComponentError: options.isServerComponentError,
90
91
  resolveCaughtNotFoundBoundaries: options.resolveCaughtNotFoundBoundaries,
91
92
  dynamicStaleTimeSeconds: options.dynamicStaleTimeSeconds,
92
93
  getInitialNavigationCacheMetadata: options.getInitialNavigationCacheMetadata