vinext 1.3.1 → 1.4.0

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 (234) hide show
  1. package/README.md +5 -2
  2. package/THIRD_PARTY_LICENSES.md +47 -0
  3. package/dist/_virtual/_rolldown/runtime.js +3 -2
  4. package/dist/build/prerender.js +1 -1
  5. package/dist/build/report.d.ts +0 -14
  6. package/dist/build/report.js +1 -51
  7. package/dist/check.js +1 -1
  8. package/dist/cli.js +3 -0
  9. package/dist/client/dev-error-overlay.js +1 -1
  10. package/dist/client/navigation-runtime.d.ts +13 -1
  11. package/dist/client/navigation-runtime.js +1 -1
  12. package/dist/config/next-config.d.ts +19 -0
  13. package/dist/config/next-config.js +64 -1
  14. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-codec.js +327 -0
  15. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-data.js +123 -0
  16. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/index.js +29 -0
  17. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/internal.js +134 -0
  18. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-codec.js +50 -0
  19. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data-generated.js +455 -0
  20. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data.js +147 -0
  21. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/big5-added.json.js +169 -0
  22. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp936.json.js +2584 -0
  23. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp949.json.js +2220 -0
  24. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp950.json.js +227 -0
  25. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/eucjp.json.js +425 -0
  26. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gb18030-ranges.json.js +427 -0
  27. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gbk-added.json.js +229 -0
  28. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/shiftjis.json.js +292 -0
  29. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf16.js +106 -0
  30. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf7.js +182 -0
  31. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/bom-handling.js +42 -0
  32. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/extend-node.js +147 -0
  33. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/index.js +94 -0
  34. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/streams.js +95 -0
  35. package/dist/deps/.pnpm/safer-buffer@2.1.2/deps/safer-buffer/safer.js +45 -0
  36. package/dist/entries/app-rsc-entry.js +17 -48
  37. package/dist/entries/app-rsc-manifest.js +3 -4
  38. package/dist/entries/app-ssr-entry.js +1 -0
  39. package/dist/entries/pages-server-entry.js +33 -8
  40. package/dist/index.js +83 -70
  41. package/dist/init-cloudflare.d.ts +1 -3
  42. package/dist/init-cloudflare.js +10 -48
  43. package/dist/init-platform.d.ts +2 -0
  44. package/dist/init-platform.js +4 -0
  45. package/dist/plugins/extensionless-dynamic-import.js +6 -2
  46. package/dist/plugins/next-intl-catalog.d.ts +12 -0
  47. package/dist/plugins/next-intl-catalog.js +85 -0
  48. package/dist/plugins/pages-node-externals.d.ts +1 -0
  49. package/dist/plugins/pages-node-externals.js +1 -1
  50. package/dist/plugins/use-cache-callable.d.ts +1 -1
  51. package/dist/plugins/use-cache-callable.js +136 -94
  52. package/dist/routing/app-route-graph.d.ts +6 -0
  53. package/dist/routing/app-route-graph.js +9 -1
  54. package/dist/server/api-handler.js +19 -4
  55. package/dist/server/app-bfcache-identity.js +1 -1
  56. package/dist/server/app-browser-entry.js +79 -63
  57. package/dist/server/app-browser-history-controller.d.ts +5 -4
  58. package/dist/server/app-browser-history-controller.js +93 -15
  59. package/dist/server/app-browser-navigation-controller.d.ts +2 -2
  60. package/dist/server/app-browser-navigation-controller.js +1 -2
  61. package/dist/server/app-browser-rsc-redirect.js +4 -4
  62. package/dist/server/app-browser-server-action-client.d.ts +2 -0
  63. package/dist/server/app-browser-server-action-client.js +4 -3
  64. package/dist/server/app-browser-state.d.ts +2 -4
  65. package/dist/server/app-browser-state.js +9 -4
  66. package/dist/server/app-browser-supplemental-refresh.js +16 -1
  67. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  68. package/dist/server/app-browser-visible-commit.js +2 -12
  69. package/dist/server/app-elements-wire.d.ts +6 -38
  70. package/dist/server/app-elements-wire.js +4 -41
  71. package/dist/server/app-elements.d.ts +2 -2
  72. package/dist/server/app-elements.js +2 -2
  73. package/dist/server/app-history-state.d.ts +2 -2
  74. package/dist/server/app-history-state.js +5 -2
  75. package/dist/server/app-mounted-layouts.d.ts +21 -0
  76. package/dist/server/app-mounted-layouts.js +67 -0
  77. package/dist/server/app-optimistic-routing.d.ts +19 -1
  78. package/dist/server/app-optimistic-routing.js +42 -2
  79. package/dist/server/app-page-boundary-render.js +7 -6
  80. package/dist/server/app-page-boundary.js +2 -2
  81. package/dist/server/app-page-cache-finalizer.d.ts +12 -1
  82. package/dist/server/app-page-cache-finalizer.js +70 -29
  83. package/dist/server/app-page-cache.d.ts +13 -1
  84. package/dist/server/app-page-cache.js +31 -9
  85. package/dist/server/app-page-dispatch.d.ts +5 -10
  86. package/dist/server/app-page-dispatch.js +84 -154
  87. package/dist/server/app-page-element-builder.d.ts +2 -2
  88. package/dist/server/app-page-element-builder.js +10 -9
  89. package/dist/server/app-page-execution.d.ts +17 -52
  90. package/dist/server/app-page-execution.js +99 -95
  91. package/dist/server/app-page-head.d.ts +13 -0
  92. package/dist/server/app-page-head.js +44 -16
  93. package/dist/server/app-page-http-access-fallback-metadata.d.ts +1 -0
  94. package/dist/server/app-page-http-access-fallback-metadata.js +2 -2
  95. package/dist/server/app-page-layout-gate.d.ts +35 -0
  96. package/dist/server/app-page-layout-gate.js +50 -0
  97. package/dist/server/app-page-params.d.ts +0 -1
  98. package/dist/server/app-page-params.js +1 -14
  99. package/dist/server/app-page-probe.d.ts +3 -21
  100. package/dist/server/app-page-probe.js +4 -37
  101. package/dist/server/app-page-render.d.ts +2 -12
  102. package/dist/server/app-page-render.js +89 -220
  103. package/dist/server/app-page-request.d.ts +1 -3
  104. package/dist/server/app-page-request.js +1 -1
  105. package/dist/server/app-page-response.js +3 -3
  106. package/dist/server/app-page-route-wiring.d.ts +13 -6
  107. package/dist/server/app-page-route-wiring.js +67 -61
  108. package/dist/server/app-page-rsc-render-status.d.ts +27 -1
  109. package/dist/server/app-page-rsc-render-status.js +121 -13
  110. package/dist/server/app-page-stream.d.ts +2 -0
  111. package/dist/server/app-page-stream.js +2 -2
  112. package/dist/server/app-pages-bridge.d.ts +4 -0
  113. package/dist/server/app-pages-bridge.js +7 -3
  114. package/dist/server/app-render-dependency.d.ts +11 -4
  115. package/dist/server/app-render-dependency.js +86 -38
  116. package/dist/server/app-request-stage-dispatch.js +1 -1
  117. package/dist/server/app-route-handler-cache.js +2 -2
  118. package/dist/server/app-route-handler-dispatch.js +14 -9
  119. package/dist/server/app-route-handler-execution.js +8 -3
  120. package/dist/server/app-route-handler-policy.d.ts +1 -1
  121. package/dist/server/app-route-handler-policy.js +3 -3
  122. package/dist/server/app-route-handler-response.js +1 -1
  123. package/dist/server/app-route-handler-runtime.js +17 -4
  124. package/dist/server/app-rsc-cache-busting.d.ts +2 -2
  125. package/dist/server/app-rsc-cache-busting.js +4 -4
  126. package/dist/server/app-rsc-error-handler.d.ts +2 -1
  127. package/dist/server/app-rsc-error-handler.js +9 -1
  128. package/dist/server/app-rsc-errors.d.ts +8 -0
  129. package/dist/server/app-rsc-errors.js +8 -1
  130. package/dist/server/app-rsc-handler.d.ts +5 -5
  131. package/dist/server/app-rsc-handler.js +30 -24
  132. package/dist/server/app-rsc-request-normalization.d.ts +0 -4
  133. package/dist/server/app-rsc-request-normalization.js +1 -4
  134. package/dist/server/app-rsc-response-finalizer.js +2 -2
  135. package/dist/server/app-rsc-response-stage.js +5 -3
  136. package/dist/server/app-server-action-execution.d.ts +3 -1
  137. package/dist/server/app-server-action-execution.js +7 -4
  138. package/dist/server/app-ssr-entry.d.ts +2 -0
  139. package/dist/server/app-ssr-entry.js +9 -8
  140. package/dist/server/app-ssr-stream.js +1 -1
  141. package/dist/server/app-visited-response-cache.d.ts +36 -1
  142. package/dist/server/app-visited-response-cache.js +46 -5
  143. package/dist/server/app-worker-stages.d.ts +25 -3
  144. package/dist/server/app-worker-stages.js +45 -6
  145. package/dist/server/artifact-compatibility.d.ts +0 -1
  146. package/dist/server/artifact-compatibility.js +1 -10
  147. package/dist/server/cache-proof.d.ts +1 -115
  148. package/dist/server/cache-proof.js +1 -544
  149. package/dist/server/cacheability-request.js +13 -8
  150. package/dist/server/dev-server.js +4 -3
  151. package/dist/server/headers.d.ts +6 -3
  152. package/dist/server/headers.js +9 -5
  153. package/dist/server/isr-cache.d.ts +8 -2
  154. package/dist/server/isr-cache.js +26 -1
  155. package/dist/server/metadata-route-response.js +32 -15
  156. package/dist/server/multi-stage.d.ts +52 -11
  157. package/dist/server/multi-stage.js +13 -1
  158. package/dist/server/navigation-planner.js +1 -1
  159. package/dist/server/pages-api-route.js +28 -19
  160. package/dist/server/pages-media-type.d.ts +21 -1
  161. package/dist/server/pages-media-type.js +69 -5
  162. package/dist/server/pages-node-compat.js +52 -27
  163. package/dist/server/pages-page-handler.js +11 -20
  164. package/dist/server/pages-request-pipeline.d.ts +8 -0
  165. package/dist/server/pages-request-pipeline.js +2 -2
  166. package/dist/server/pages-request-stage-entry.js +14 -12
  167. package/dist/server/pages-response-stage-entry.js +9 -3
  168. package/dist/server/prod-server.js +5 -1
  169. package/dist/server/request-pipeline.d.ts +6 -0
  170. package/dist/server/request-pipeline.js +21 -1
  171. package/dist/server/shared-cache-identity.d.ts +23 -0
  172. package/dist/server/shared-cache-identity.js +36 -0
  173. package/dist/server/worker-stages.d.ts +16 -3
  174. package/dist/server/worker-stages.js +23 -4
  175. package/dist/shims/cache-handler.js +2 -2
  176. package/dist/shims/cache-request-state.d.ts +17 -20
  177. package/dist/shims/cache-request-state.js +16 -21
  178. package/dist/shims/cache-runtime.js +27 -11
  179. package/dist/shims/cache.d.ts +8 -7
  180. package/dist/shims/cache.js +17 -19
  181. package/dist/shims/cacheability-classification.d.ts +2 -0
  182. package/dist/shims/cdn-cache.d.ts +22 -5
  183. package/dist/shims/client-page-root.js +2 -2
  184. package/dist/shims/dynamic.js +9 -6
  185. package/dist/shims/error-boundary.js +2 -2
  186. package/dist/shims/fetch-cache-state.d.ts +5 -1
  187. package/dist/shims/fetch-cache-state.js +1 -3
  188. package/dist/shims/fetch-cache.d.ts +2 -1
  189. package/dist/shims/fetch-cache.js +34 -24
  190. package/dist/shims/form.js +2 -2
  191. package/dist/shims/headers.d.ts +8 -23
  192. package/dist/shims/headers.js +17 -67
  193. package/dist/shims/internal/navigation-untracked.js +1 -1
  194. package/dist/shims/link.js +3 -1
  195. package/dist/shims/metadata.d.ts +23 -21
  196. package/dist/shims/metadata.js +62 -24
  197. package/dist/shims/navigation-server.js +1 -1
  198. package/dist/shims/navigation.d.ts +74 -7
  199. package/dist/shims/navigation.js +183 -29
  200. package/dist/shims/navigation.react-server.js +2 -2
  201. package/dist/shims/request-state-types.d.ts +2 -2
  202. package/dist/shims/slot.d.ts +12 -0
  203. package/dist/shims/slot.js +37 -9
  204. package/dist/shims/unified-request-context.d.ts +1 -1
  205. package/dist/shims/unified-request-context.js +2 -3
  206. package/dist/utils/cache-control-metadata.d.ts +5 -0
  207. package/dist/utils/cache-control-metadata.js +9 -1
  208. package/dist/utils/project.d.ts +2 -2
  209. package/dist/utils/project.js +3 -16
  210. package/dist/utils/regex.d.ts +5 -0
  211. package/dist/utils/regex.js +11 -1
  212. package/dist/utils/text-stream.d.ts +10 -5
  213. package/dist/utils/text-stream.js +6 -6
  214. package/package.json +2 -1
  215. package/dist/build/layout-classification-types.d.ts +0 -60
  216. package/dist/build/layout-classification-types.js +0 -1
  217. package/dist/build/layout-classification.d.ts +0 -62
  218. package/dist/build/layout-classification.js +0 -98
  219. package/dist/build/route-classification-injector.d.ts +0 -33
  220. package/dist/build/route-classification-injector.js +0 -59
  221. package/dist/build/route-classification-manifest.d.ts +0 -54
  222. package/dist/build/route-classification-manifest.js +0 -143
  223. package/dist/server/app-browser-client-reuse-manifest.d.ts +0 -11
  224. package/dist/server/app-browser-client-reuse-manifest.js +0 -101
  225. package/dist/server/app-layout-param-observation.d.ts +0 -42
  226. package/dist/server/app-layout-param-observation.js +0 -168
  227. package/dist/server/client-reuse-manifest.d.ts +0 -103
  228. package/dist/server/client-reuse-manifest.js +0 -209
  229. package/dist/server/skip-cache-proof.d.ts +0 -60
  230. package/dist/server/skip-cache-proof.js +0 -168
  231. package/dist/server/static-layout-client-reuse-proof.d.ts +0 -14
  232. package/dist/server/static-layout-client-reuse-proof.js +0 -33
  233. package/dist/utils/sorted-array.d.ts +0 -8
  234. package/dist/utils/sorted-array.js +0 -20
@@ -0,0 +1,67 @@
1
+ import { fnv1a64 } from "../utils/hash.js";
2
+ import { AppElementsWire } from "./app-elements-wire.js";
3
+ import "./app-elements.js";
4
+ //#region src/server/app-mounted-layouts.ts
5
+ /**
6
+ * A client navigation names the layouts it already holds, so a render that's
7
+ * never stored can leave them out, as Next.js leaves out the segments above
8
+ * where a navigation diverges from its router state tree.
9
+ *
10
+ * Each entry hashes a layout's segment identity, which binds the layout to its
11
+ * route prefix and that prefix's params, so a layout whose params changed never
12
+ * matches.
13
+ */
14
+ const MAX_MOUNTED_LAYOUTS = 64;
15
+ const MOUNTED_LAYOUT_HASH = /^[0-9a-f]{16}$/;
16
+ function hashMountedLayoutIdentity(identity) {
17
+ return fnv1a64(identity);
18
+ }
19
+ function serializeMountedLayouts(identities) {
20
+ if (identities.length === 0) return null;
21
+ return identities.slice(0, MAX_MOUNTED_LAYOUTS).map(hashMountedLayoutIdentity).join(",");
22
+ }
23
+ function parseMountedLayoutsHeader(value) {
24
+ if (!value) return null;
25
+ const hashes = /* @__PURE__ */ new Set();
26
+ for (const entry of value.split(",", 65)) {
27
+ const hash = entry.trim();
28
+ if (!MOUNTED_LAYOUT_HASH.test(hash)) return null;
29
+ hashes.add(hash);
30
+ }
31
+ if (hashes.size === 0 || hashes.size > MAX_MOUNTED_LAYOUTS) return null;
32
+ return hashes;
33
+ }
34
+ /**
35
+ * The header for the outermost layouts a client state holds. Only that prefix
36
+ * can be left out of a render, as a layout below one that re-renders renders
37
+ * inside it.
38
+ */
39
+ function createMountedLayoutsHeader(elements, layoutIds) {
40
+ const identities = AppElementsWire.readMetadata(elements).bfcacheSegmentIdentities;
41
+ const held = [];
42
+ for (const layoutId of layoutIds) {
43
+ const identity = identities[layoutId];
44
+ if (identity === void 0 || !Object.hasOwn(elements, layoutId)) break;
45
+ held.push(identity);
46
+ }
47
+ return serializeMountedLayouts(held);
48
+ }
49
+ /**
50
+ * Whether a client state still holds, unchanged, every layout a payload left
51
+ * out. A navigation that started from another state, or a payload stored for
52
+ * another client, can name layouts this one doesn't hold.
53
+ */
54
+ function holdsSkippedLayouts(current, next) {
55
+ const nextMetadata = AppElementsWire.readMetadata(next);
56
+ if (nextMetadata.skippedLayoutIds.length === 0) return true;
57
+ const currentIdentities = AppElementsWire.readMetadata(current.elements).bfcacheSegmentIdentities;
58
+ const currentLayoutIds = new Set(current.layoutIds);
59
+ for (const layoutId of nextMetadata.skippedLayoutIds) {
60
+ if (!currentLayoutIds.has(layoutId) || !Object.hasOwn(current.elements, layoutId)) return false;
61
+ const identity = nextMetadata.bfcacheSegmentIdentities[layoutId];
62
+ if (identity === void 0 || identity !== currentIdentities[layoutId]) return false;
63
+ }
64
+ return true;
65
+ }
66
+ //#endregion
67
+ export { createMountedLayoutsHeader, hashMountedLayoutIdentity, holdsSkippedLayouts, parseMountedLayoutsHeader };
@@ -1,7 +1,7 @@
1
1
  import { BrowserRouteManifest, BrowserRouteManifestRoute } from "../routing/browser-route-manifest.js";
2
- import { OptimisticRouteTrieMatch } from "../routing/optimistic-route-trie.js";
3
2
  import { AppElements } from "./app-elements-wire.js";
4
3
  import "./app-elements.js";
4
+ import { OptimisticRouteTrieMatch } from "../routing/optimistic-route-trie.js";
5
5
  //#region src/server/app-optimistic-routing.d.ts
6
6
  type OptimisticRouteMatch = OptimisticRouteTrieMatch<BrowserRouteManifestRoute>;
7
7
  export type OptimisticRouteTemplate = {
@@ -90,5 +90,23 @@ export declare function resolveOptimisticNavigationPayload(options: {
90
90
  routeManifest: BrowserRouteManifest;
91
91
  templates: ReadonlyMap<string, OptimisticRouteTemplate>;
92
92
  }): OptimisticNavigationPayload | null;
93
+ export type LearnedOptimisticRouteTemplate = {
94
+ key: string;
95
+ template: OptimisticRouteTemplate;
96
+ };
97
+ /**
98
+ * Optimistic route templates and the prefetch sources they were learned from.
99
+ * `clear()` retires every template, so none can outlive the prefetch payload
100
+ * it was derived from, and drops any result a learning started before the
101
+ * clear resolves to. Sources that are still cached are learned again on the
102
+ * next optimistic navigation.
103
+ */
104
+ export declare function createOptimisticRouteTemplateStore(): {
105
+ templates: ReadonlyMap<string, OptimisticRouteTemplate>;
106
+ clear(): void;
107
+ hasSource(sourceKey: string): boolean;
108
+ learn(sourceKey: string, learnTemplate: () => Promise<LearnedOptimisticRouteTemplate | null>): Promise<void>;
109
+ pendingLearning(): Promise<void>[];
110
+ };
93
111
  //#endregion
94
112
  export {};
@@ -3,10 +3,10 @@ import { stripBasePath } from "../utils/base-path.js";
3
3
  import { decodeMatchedParams, splitPathnameForRouteMatch } from "../routing/utils.js";
4
4
  import { extractRawRoutePatternParams, matchRoutePattern } from "../routing/route-pattern.js";
5
5
  import { buildOptimisticRouteTrie, matchOptimisticRouteTrie } from "../routing/optimistic-route-trie.js";
6
+ import { stripRscCacheBustingSearchParam, stripRscSuffix } from "./app-rsc-cache-busting.js";
6
7
  import { AppElementsWire } from "./app-elements-wire.js";
7
8
  import "./app-elements.js";
8
9
  import { createNestedBfcacheSlotSegmentId, deriveBfcacheSegmentIdentity, isNestedBfcacheSlotSegmentId } from "./bfcache-identity.js";
9
- import { stripRscCacheBustingSearchParam, stripRscSuffix } from "./app-rsc-cache-busting.js";
10
10
  import { canonicalizeAppPageParams, resolveAppPagePatternStateKey, resolveAppPageSemanticSegmentStateKey } from "./app-page-segment-state.js";
11
11
  import { Suspense, createElement, isValidElement } from "react";
12
12
  //#region src/server/app-optimistic-routing.ts
@@ -310,5 +310,45 @@ function resolveOptimisticNavigationPayload(options) {
310
310
  urlParts: urlParts.normalized
311
311
  };
312
312
  }
313
+ /**
314
+ * Optimistic route templates and the prefetch sources they were learned from.
315
+ * `clear()` retires every template, so none can outlive the prefetch payload
316
+ * it was derived from, and drops any result a learning started before the
317
+ * clear resolves to. Sources that are still cached are learned again on the
318
+ * next optimistic navigation.
319
+ */
320
+ function createOptimisticRouteTemplateStore() {
321
+ const templates = /* @__PURE__ */ new Map();
322
+ const learnedSources = /* @__PURE__ */ new Set();
323
+ const learning = /* @__PURE__ */ new Map();
324
+ let generation = 0;
325
+ return {
326
+ templates,
327
+ clear() {
328
+ generation += 1;
329
+ templates.clear();
330
+ learnedSources.clear();
331
+ learning.clear();
332
+ },
333
+ hasSource(sourceKey) {
334
+ return learnedSources.has(sourceKey) || learning.has(sourceKey);
335
+ },
336
+ learn(sourceKey, learnTemplate) {
337
+ const learnedGeneration = generation;
338
+ const promise = learnTemplate().then((learned) => {
339
+ if (learned === null || learnedGeneration !== generation) return;
340
+ templates.set(learned.key, learned.template);
341
+ learnedSources.add(sourceKey);
342
+ }).finally(() => {
343
+ if (learning.get(sourceKey) === promise) learning.delete(sourceKey);
344
+ });
345
+ learning.set(sourceKey, promise);
346
+ return promise;
347
+ },
348
+ pendingLearning() {
349
+ return [...learning.values()];
350
+ }
351
+ };
352
+ }
313
353
  //#endregion
314
- export { canCommitOptimisticRouteTemplate, createOptimisticRouteElements, createOptimisticRouteTemplate, getOptimisticPrefetchSourceKey, getOptimisticRouteTemplateKey, keepOptimisticStreamingMetadata, matchOptimisticRouteManifestRoute, resolveOptimisticNavigationParamsForHref, resolveOptimisticNavigationPayload };
354
+ export { canCommitOptimisticRouteTemplate, createOptimisticRouteElements, createOptimisticRouteTemplate, createOptimisticRouteTemplateStore, getOptimisticPrefetchSourceKey, getOptimisticRouteTemplateKey, keepOptimisticStreamingMetadata, matchOptimisticRouteManifestRoute, resolveOptimisticNavigationParamsForHref, resolveOptimisticNavigationPayload };
@@ -2,26 +2,26 @@ import { stripBasePath } from "../utils/base-path.js";
2
2
  import { isNavigationSignalError } from "../utils/navigation-signal.js";
3
3
  import { NEVER_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
4
4
  import { isRenderDynamicLatched, isStaticGenerationAccessError } from "../shims/internal/headers-state.js";
5
+ import { copyLinkHeaderProvenance } from "./app-response-header-provenance.js";
6
+ import { stripRscSuffix } from "./app-rsc-cache-busting.js";
5
7
  import { createArtifactCompatibilityEnvelope } from "./artifact-compatibility.js";
6
8
  import { AppElementsWire } from "./app-elements-wire.js";
7
9
  import "./app-elements.js";
8
10
  import DefaultGlobalError from "../shims/default-global-error.js";
9
11
  import { ErrorBoundary, GlobalErrorBoundary, SerializedErrorBoundary } from "../shims/error-boundary.js";
12
+ import { buildClientHookErrorMessage } from "../shims/client-hook-error.js";
10
13
  import { LayoutSegmentProvider } from "../shims/layout-segment-context.js";
11
14
  import { withUseCacheLayoutMarker } from "../shims/internal/app-page-props-cache-key.js";
12
15
  import { MetadataHead, ViewportHead } from "../shims/metadata.js";
13
- import { stripRscSuffix } from "./app-rsc-cache-busting.js";
14
- import { copyLinkHeaderProvenance } from "./app-response-header-provenance.js";
15
16
  import { buildRscRedirectFlightStream } from "./app-rsc-redirect-flight.js";
16
17
  import { bufferAppPageBinaryStream, buildAppPageSpecialErrorResponse, resolveAppPageSpecialError } from "./app-page-execution.js";
17
- import { resolveAppPageBranchParams, resolveAppPageSegmentParams } from "./app-page-params.js";
18
- import { createAppPageLayoutEntries, createAppPageRouteBodyMetadata, createAppPageSourcePage } from "./app-page-route-wiring.js";
19
- import { buildClientHookErrorMessage } from "../shims/client-hook-error.js";
20
18
  import { createAppPageMetadataSearchParamsObserver } from "./app-page-search-params-observation.js";
21
- import { hasAppPageDynamicMetadata, prepareAppPageHead, resolveActiveParallelRouteHeadInputs, resolveAppPageHead, slotInterceptKeepsSourceHead } from "./app-page-head.js";
19
+ import { resolveAppPageBranchParams, resolveAppPageSegmentParams } from "./app-page-params.js";
20
+ import { hasAppPageDynamicMetadata, prepareAppPageHead, resolveActiveParallelRouteHeadInputs, resolveAppPageHead, resolveInterceptLayoutsBesidePage, slotInterceptKeepsSourceHead } from "./app-page-head.js";
22
21
  import { renderAppPageBoundaryResponse, resolveAppPageErrorBoundary, resolveAppPageHttpAccessBoundaryModule, wrapAppPageBoundaryElement } from "./app-page-boundary.js";
23
22
  import { createRouteNotFoundHeadPlanOptions, recoverAppPageRouteHead, resolveHttpAccessFallbackMetadata, resolveHttpAccessFallbackViewport } from "./app-page-http-access-fallback-metadata.js";
24
23
  import "./app-rsc-route-matching.js";
24
+ import { createAppPageLayoutEntries, createAppPageRouteBodyMetadata, createAppPageSourcePage } from "./app-page-route-wiring.js";
25
25
  import { resolveSlotParamOverrides } from "./app-page-element-builder.js";
26
26
  import { createAppPageFontData, createAppPageRscErrorTracker, createAppPageSsrErrorHandler, renderAppPageHtmlResponse } from "./app-page-stream.js";
27
27
  import { Fragment, Suspense, createElement } from "react";
@@ -301,6 +301,7 @@ async function renderAppPageHttpAccessFallback(options) {
301
301
  head: {
302
302
  layoutModules: intercept?.interceptLayouts ?? [],
303
303
  layoutParams: (intercept?.interceptLayoutSegments ?? []).map((layoutSegments) => resolveAppPageBranchParams(intercept?.interceptBranchSegments ?? layoutSegments, layoutSegments.length, effectiveParams, layoutSegments)),
304
+ layoutsBesidePage: resolveInterceptLayoutsBesidePage(intercept?.interceptLayoutSegments, intercept?.interceptBranchSegments),
304
305
  pageModule: intercept?.interceptPage ?? null,
305
306
  params: effectiveParams,
306
307
  routeSegments: intercept?.interceptSourcePageSegments ?? segments
@@ -1,10 +1,10 @@
1
1
  import { VINEXT_RSC_VARY_HEADER } from "./app-rsc-vary.js";
2
2
  import { runWithFetchDedupe } from "../shims/fetch-cache.js";
3
- import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
4
3
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
4
+ import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
5
+ import { isAppPageRouteGroupSegment } from "./app-page-segment-state.js";
5
6
  import { applyEdgeRuntimeHeader } from "./app-page-response.js";
6
7
  import { resolveAppPageSegmentParams } from "./app-page-params.js";
7
- import { isAppPageRouteGroupSegment } from "./app-page-segment-state.js";
8
8
  //#region src/server/app-page-boundary.ts
9
9
  function resolveAppPageHttpAccessBoundaryComponent(options) {
10
10
  return options.getDefaultExport(resolveAppPageHttpAccessBoundaryModule({
@@ -2,6 +2,7 @@ import { RenderObservation } from "./cache-proof.js";
2
2
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
3
3
  import { AppPageRenderObservationState } from "./app-page-render-observation.js";
4
4
  import { AppPageCacheSetter } from "./isr-cache.js";
5
+ import { CdnAppPageDocument } from "../shims/cdn-cache.js";
5
6
  import { AppPageRscRenderStatus } from "./app-page-rsc-render-status.js";
6
7
  //#region src/server/app-page-cache-finalizer.d.ts
7
8
  type AppPageDebugLogger = (event: string, detail: string) => void;
@@ -30,6 +31,8 @@ type FinalizeAppPageCacheabilityEvaluationOptions = {
30
31
  /** An RSC render's status, from its document's shell. */
31
32
  resolveRscRenderStatus?: () => Promise<AppPageRscRenderStatus>;
32
33
  revalidateSeconds: number | null;
34
+ /** Hands the CDN adapter the page's document to store beside the response. */
35
+ captureAdmittedDocument?: (document: CdnAppPageDocument | null) => void;
33
36
  };
34
37
  type FinalizeAppPageHtmlCacheResponseOptions = {
35
38
  bypassInterceptionContextCache?: boolean;
@@ -66,15 +69,20 @@ type FinalizeAppPageHtmlCacheResponseOptions = {
66
69
  };
67
70
  type ScheduleAppPageRscCacheWriteOptions = {
68
71
  bypassInterceptionContextCache?: boolean;
72
+ /** Hands the CDN adapter the page's document to store beside the response. */
73
+ captureAdmittedDocument?: (document: CdnAppPageDocument | null) => void;
69
74
  capturedRscDataPromise: Promise<ArrayBuffer> | null;
70
75
  cleanPathname: string;
71
76
  consumeDynamicUsage: () => boolean;
72
77
  consumeRenderObservationState?: () => AppPageRenderObservationState;
78
+ /** Observes the document the render's status resolution rendered, if any. */
79
+ createHtmlRenderObservation?: BuildAppPageCacheRenderObservation;
73
80
  createRscRenderObservation: BuildAppPageCacheRenderObservation;
74
81
  dynamicUsedDuringBuild: boolean;
75
82
  getPageTags: () => string[];
76
83
  getRequestCacheLife?: () => AppPageRequestCacheLife | null;
77
84
  isrDebug?: AppPageDebugLogger;
85
+ isrHtmlKey?: (pathname: string) => string;
78
86
  isrRscKey: AppPageRscCacheKeyBuilder;
79
87
  isrSet: AppPageCacheSetter;
80
88
  interceptionContext?: string | null;
@@ -82,7 +90,10 @@ type ScheduleAppPageRscCacheWriteOptions = {
82
90
  mountedSlotsHeader?: string | null;
83
91
  omitPendingDynamicCacheState?: boolean;
84
92
  renderMode?: AppRscRenderMode;
85
- /** The render's status, from its document's shell. Without it the render is a 200. */
93
+ /**
94
+ * The render's status, from its document's shell, and the page's document
95
+ * when it rendered one to store. Without it the render is a 200.
96
+ */
86
97
  resolveRscRenderStatus?: () => Promise<AppPageRscRenderStatus>;
87
98
  preserveClientResponseHeaders?: boolean;
88
99
  expireSeconds?: number;
@@ -67,55 +67,79 @@ function applyRscRenderStatus(outcome, renderStatus) {
67
67
  ...renderStatus.headers ? { headers: renderStatus.headers } : {}
68
68
  };
69
69
  }
70
+ /** Hand an adapter the document; its failure never holds back admission. */
71
+ function settleAdmittedDocument(capture, document) {
72
+ try {
73
+ capture?.(document);
74
+ } catch (error) {
75
+ console.error("[vinext] CDN adapter document capture error:", error);
76
+ }
77
+ }
70
78
  function finalizeEvaluatedAppPageResponse(response, options) {
71
79
  if (!isRouteCacheabilityEvaluation()) return null;
72
80
  const complete = deferRouteCacheability();
73
- if (!complete) return response;
81
+ if (!complete) {
82
+ settleAdmittedDocument(options.captureAdmittedDocument, null);
83
+ return response;
84
+ }
74
85
  captureRouteCacheabilityResponsePolicy(captureCdnResponsePolicyHeaders(response.headers));
86
+ const admit = (renderStatus) => {
87
+ const observationState = options.consumeRenderObservationState?.();
88
+ const cacheControl = resolveAppPageCacheControl({
89
+ expireSeconds: options.expireSeconds,
90
+ requestCacheLife: options.getRequestCacheLife?.(),
91
+ revalidateSeconds: options.revalidateSeconds
92
+ });
93
+ const outcome = cacheControl ? {
94
+ cacheable: true,
95
+ cacheControl: appPageCacheControlHeader(cacheControl),
96
+ ...observationState && !observationState.requestApis.includes("searchParams") ? { searchParamsUnread: true } : {},
97
+ ...cacheControl.stale !== void 0 ? { staleTimeSeconds: cacheControl.stale } : {},
98
+ tags: options.getPageTags()
99
+ } : {
100
+ cacheable: false,
101
+ reason: "render did not produce a cache policy"
102
+ };
103
+ const admitted = renderStatus ? applyRscRenderStatus(outcome, renderStatus) : outcome;
104
+ settleAdmittedDocument(options.captureAdmittedDocument, admitted.cacheable && admitted.searchParamsUnread === true && renderStatus?.kind === "page" ? renderStatus.document ?? null : null);
105
+ complete(admitted);
106
+ };
75
107
  let completed = false;
76
108
  const finish = () => {
77
109
  if (completed) return;
78
110
  completed = true;
79
- const observationState = options.consumeRenderObservationState?.();
80
- let outcome;
81
- if (options.capturedDynamicUsageBeforeContextCleanup?.() === true || options.consumeDynamicUsage()) outcome = {
111
+ let ineligible;
112
+ if (options.capturedDynamicUsageBeforeContextCleanup?.() === true || options.consumeDynamicUsage()) ineligible = {
82
113
  cacheable: false,
83
114
  dynamicUsage: true,
84
115
  reason: "dynamic API used during render"
85
116
  };
86
- else if (options.isStaticEligible === false) outcome = {
117
+ else if (options.isStaticEligible === false) ineligible = {
87
118
  cacheable: false,
88
119
  reason: "route is not statically generated"
89
120
  };
90
- else if (response.headers.has("set-cookie") || hasExplicitNonCacheableResponsePolicy(response.headers)) outcome = {
121
+ else if (response.headers.has("set-cookie") || hasExplicitNonCacheableResponsePolicy(response.headers)) ineligible = {
91
122
  cacheable: false,
92
123
  reason: "response explicitly opts out of shared caching"
93
124
  };
94
- else {
95
- const cacheControl = resolveAppPageCacheControl({
96
- expireSeconds: options.expireSeconds,
97
- requestCacheLife: options.getRequestCacheLife?.(),
98
- revalidateSeconds: options.revalidateSeconds
99
- });
100
- outcome = cacheControl ? {
101
- cacheable: true,
102
- cacheControl: appPageCacheControlHeader(cacheControl),
103
- ...observationState && !observationState.requestApis.includes("searchParams") ? { searchParamsUnread: true } : {},
104
- tags: options.getPageTags()
105
- } : {
106
- cacheable: false,
107
- reason: "render did not produce a cache policy"
108
- };
125
+ if (ineligible) {
126
+ options.consumeRenderObservationState?.();
127
+ settleAdmittedDocument(options.captureAdmittedDocument, null);
128
+ complete(ineligible);
129
+ return;
109
130
  }
110
- if (outcome.cacheable && options.resolveRscRenderStatus) {
111
- const cacheableOutcome = outcome;
112
- options.resolveRscRenderStatus().then((renderStatus) => complete(applyRscRenderStatus(cacheableOutcome, renderStatus)), () => complete({
113
- cacheable: false,
114
- reason: "render status could not be resolved"
115
- }));
131
+ if (!options.resolveRscRenderStatus) {
132
+ admit();
116
133
  return;
117
134
  }
118
- complete(outcome);
135
+ options.resolveRscRenderStatus().then(admit, () => {
136
+ options.consumeRenderObservationState?.();
137
+ settleAdmittedDocument(options.captureAdmittedDocument, null);
138
+ complete({
139
+ cacheable: false,
140
+ reason: "render status could not be resolved"
141
+ });
142
+ });
119
143
  };
120
144
  if (!response.body) {
121
145
  finish();
@@ -230,6 +254,7 @@ function finalizeAppPageRscCacheResponse(response, options) {
230
254
  options.capturedRscDataPromise?.catch(() => {});
231
255
  return probeResponse;
232
256
  }
257
+ settleAdmittedDocument(options.captureAdmittedDocument, null);
233
258
  scheduleAppPageRscCacheWrite(options);
234
259
  const isUncacheableVariant = Boolean(options.mountedSlotsHeader) || options.bypassInterceptionContextCache === true;
235
260
  if (options.preserveClientResponseHeaders === true && !isUncacheableVariant) return response;
@@ -253,6 +278,7 @@ function scheduleAppPageRscCacheWrite(options) {
253
278
  options.isrDebug?.("RSC cache write skipped (dynamic usage during render)", rscKey);
254
279
  return;
255
280
  }
281
+ const renderStatus = await options.resolveRscRenderStatus?.() ?? { kind: "page" };
256
282
  const cacheControl = resolveAppPageCacheControl({
257
283
  expireSeconds: options.expireSeconds,
258
284
  requestCacheLife: options.getRequestCacheLife?.(),
@@ -272,7 +298,6 @@ function scheduleAppPageRscCacheWrite(options) {
272
298
  options.isrDebug?.("RSC cache write skipped (searchParams not proven unread)", rscKey);
273
299
  return;
274
300
  }
275
- const renderStatus = await options.resolveRscRenderStatus?.() ?? { kind: "page" };
276
301
  if (renderStatus.kind === "unstorable") {
277
302
  options.isrDebug?.("RSC cache write skipped (unstorable special error)", rscKey);
278
303
  return;
@@ -282,6 +307,22 @@ function scheduleAppPageRscCacheWrite(options) {
282
307
  tags: pageTags
283
308
  });
284
309
  options.isrDebug?.("RSC cache written", rscKey);
310
+ const document = renderStatus.kind === "page" ? renderStatus.document : void 0;
311
+ if (!document || !options.isrHtmlKey || !options.createHtmlRenderObservation) return;
312
+ const htmlKey = options.isrHtmlKey(options.cleanPathname);
313
+ const htmlRenderObservation = options.createHtmlRenderObservation({
314
+ cacheTags: pageTags,
315
+ state: observationState
316
+ });
317
+ if (!hasQueryInvariantRenderProof(htmlRenderObservation)) {
318
+ options.isrDebug?.("HTML cache write skipped (searchParams not proven unread)", htmlKey);
319
+ return;
320
+ }
321
+ await options.isrSet(htmlKey, buildAppPageCacheValue(document.html, void 0, 200, htmlRenderObservation, document.linkHeader ? { link: document.linkHeader } : void 0), {
322
+ cacheControl,
323
+ tags: pageTags
324
+ });
325
+ options.isrDebug?.("HTML cache written", htmlKey);
285
326
  } catch (cacheError) {
286
327
  console.error("[vinext] ISR RSC cache write error:", cacheError);
287
328
  }
@@ -83,7 +83,19 @@ type ReadAppPageCacheResponseOptions = {
83
83
  renderMode?: AppRscRenderMode;
84
84
  expireSeconds?: number;
85
85
  revalidateSeconds: number;
86
- renderFreshPageForCache: () => Promise<AppPageCacheRenderResult>;
86
+ /**
87
+ * Whether this is an RSC request Next.js renders dynamically under PPR (one
88
+ * that is not a prefetch, for a path not generated at build time), and so
89
+ * never regenerates the page's document.
90
+ */
91
+ isDynamicRscRequest?: boolean;
92
+ /**
93
+ * Renders the page for its cache entries. `renderDocument` asks for the
94
+ * document as an HTML request renders it, to be stored beside the RSC payload.
95
+ */
96
+ renderFreshPageForCache: (options: {
97
+ renderDocument: boolean;
98
+ }) => Promise<AppPageCacheRenderResult>;
87
99
  scheduleBackgroundRegeneration: AppPageBackgroundRegenerator;
88
100
  };
89
101
  type ReadAppPageFallbackShellCacheResponseOptions = {
@@ -6,12 +6,12 @@ import { buildAppPageCacheValue, isrCacheControl, resolveRouteExpireSeconds } fr
6
6
  import { resolveClientStaleTimeSeconds } from "../utils/cache-control-metadata.js";
7
7
  import { encodeCacheTag } from "../utils/encode-cache-tag.js";
8
8
  import { setCacheStateHeaders } from "./cache-headers.js";
9
- import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
10
- import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
11
9
  import { markFrameworkLinkHeaders } from "./app-response-header-provenance.js";
12
- import { applyClientStaleTimeHeader, applyEdgeRuntimeHeader } from "./app-page-response.js";
10
+ import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
11
+ import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
13
12
  import { isAppPprDynamicFallbackShellHtml } from "./app-ppr-fallback-shell.js";
14
13
  import { hasQueryInvariantRenderProof } from "./cache-proof.js";
14
+ import { applyClientStaleTimeHeader, applyEdgeRuntimeHeader } from "./app-page-response.js";
15
15
  import { finalizeAppPageHtmlCacheResponse, finalizeAppPageRscCacheResponse, scheduleAppPageRscCacheWrite } from "./app-page-cache-finalizer.js";
16
16
  import { buildPageCacheTags } from "./implicit-tags.js";
17
17
  //#region src/server/app-page-cache.ts
@@ -92,6 +92,25 @@ function resolveRegenerationFailureCacheControl(previous) {
92
92
  ...previous.stale === void 0 ? {} : { stale: previous.stale }
93
93
  };
94
94
  }
95
+ /**
96
+ * Next.js stamps every prerender with the stale time it collected. A render
97
+ * that made no `cacheLife` claim keeps the `INFINITE_CACHE` sentinel, which
98
+ * resolves to `experimental.staleTimes.static` (`createSelectStaleTime` in
99
+ * `server/app-render/stale-time.ts`, applied by
100
+ * `applyMetadataFromPrerenderResult` in `app-render.tsx`). Its client only
101
+ * caches a navigation response for that long under
102
+ * `experimental.cachedNavigations` (`fetch-server-response.ts`); otherwise the
103
+ * value only dates prefetches. vinext's client reuses navigation responses for
104
+ * any advertised stale time, so the unclaimed value is only advertised when
105
+ * cached navigations are on.
106
+ */
107
+ function resolveUnclaimedPrerenderStaleTimeSeconds(isRoutePPREnabled) {
108
+ if (isRoutePPREnabled !== true) return void 0;
109
+ if (String(process.env.__NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS) !== "true") return void 0;
110
+ const raw = process.env.__NEXT_CLIENT_ROUTER_STATIC_STALETIME;
111
+ const seconds = raw === void 0 || raw === "" ? 300 : Number(raw);
112
+ return Number.isFinite(seconds) && seconds >= 0 ? Math.floor(seconds) : void 0;
113
+ }
95
114
  function isRedirectStatus(status) {
96
115
  return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
97
116
  }
@@ -108,7 +127,7 @@ function buildAppPageCachedResponse(cachedValue, options) {
108
127
  expireSeconds: options.expireSeconds,
109
128
  cacheControlMeta: options.cacheControl
110
129
  });
111
- const staleTimeSeconds = resolveClientStaleTimeSeconds(options.cacheControl);
130
+ const staleTimeSeconds = resolveClientStaleTimeSeconds(options.cacheControl) ?? resolveUnclaimedPrerenderStaleTimeSeconds(options.isRoutePPREnabled);
112
131
  if (options.isRscRequest) {
113
132
  if (!cachedValue.rscData) return null;
114
133
  const rscHeaders = buildAppPageCachedHeaders({
@@ -276,8 +295,10 @@ async function readAppPageCacheResponse(options) {
276
295
  console.error(`[vinext] Failed to keep the previous entry for ${key}:`, storeError);
277
296
  }
278
297
  };
298
+ const regeneratesDocument = !options.isRscRequest || options.isDynamicRscRequest !== true && isrKey === options.isrRscKey(options.cleanPathname, null);
299
+ const htmlKey = options.isRscRequest ? regeneratesDocument ? options.isrHtmlKey(options.cleanPathname) : null : isrKey;
279
300
  const regenerate = async () => {
280
- const revalidatedPage = await options.renderFreshPageForCache();
301
+ const revalidatedPage = await options.renderFreshPageForCache({ renderDocument: regeneratesDocument });
281
302
  const cacheControl = resolveRegeneratedAppPageCacheControl({
282
303
  expireSeconds: options.expireSeconds,
283
304
  renderCacheControl: revalidatedPage.cacheControl,
@@ -285,7 +306,8 @@ async function readAppPageCacheResponse(options) {
285
306
  });
286
307
  if (options.isRoutePPREnabled !== true && (revalidatedPage.usedDynamicApi || cacheControl.revalidate === 0)) throw new Error(`Page changed from static to dynamic at runtime ${options.cleanPathname}
287
308
  see more here https://nextjs.org/docs/messages/app-static-to-dynamic-error`);
288
- if (!hasQueryInvariantRenderProof(revalidatedPage.rscRenderObservation) || !options.isRscRequest && !hasQueryInvariantRenderProof(revalidatedPage.htmlRenderObservation)) {
309
+ const documentKey = options.isRscRequest && revalidatedPage.html === "" ? null : htmlKey;
310
+ if (!hasQueryInvariantRenderProof(revalidatedPage.rscRenderObservation) || documentKey !== null && !hasQueryInvariantRenderProof(revalidatedPage.htmlRenderObservation)) {
289
311
  options.isrDebug?.("regen write skipped (searchParams not proven unread)", isrKey);
290
312
  return;
291
313
  }
@@ -304,15 +326,15 @@ see more here https://nextjs.org/docs/messages/app-static-to-dynamic-error`);
304
326
  };
305
327
  const status = revalidatedPage.status ?? 200;
306
328
  if (!await store(rscKey, buildAppPageCacheValue("", revalidatedPage.rscData, status, revalidatedPage.rscRenderObservation, revalidatedPage.headers))) return;
307
- if (!options.isRscRequest) {
308
- if (!await store(isrKey, buildAppPageCacheValue(revalidatedPage.html, void 0, status, revalidatedPage.htmlRenderObservation, revalidatedPage.linkHeader ? {
329
+ if (documentKey !== null) {
330
+ if (!await store(documentKey, buildAppPageCacheValue(revalidatedPage.html, void 0, status, revalidatedPage.htmlRenderObservation, revalidatedPage.linkHeader ? {
309
331
  ...revalidatedPage.headers,
310
332
  link: revalidatedPage.linkHeader
311
333
  } : revalidatedPage.headers))) return;
312
334
  }
313
335
  options.isrDebug?.("regen complete", options.cleanPathname);
314
336
  };
315
- options.scheduleBackgroundRegeneration(isrKey, async () => {
337
+ options.scheduleBackgroundRegeneration(htmlKey ?? isrKey, async () => {
316
338
  try {
317
339
  await regenerate();
318
340
  } catch (error) {
@@ -1,14 +1,11 @@
1
- import { ClassificationReason } from "../build/layout-classification-types.js";
2
- import { ClientReuseManifestParseResult } from "./client-reuse-manifest.js";
3
1
  import { AppOutgoingElements } from "./app-elements-wire.js";
4
2
  import "./app-elements.js";
5
3
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
6
4
  import { NavigationContext } from "../shims/navigation-context-state.js";
7
5
  import "../shims/navigation.js";
8
- import { AppPageFontPreload, LayoutClassificationOptions } from "./app-page-execution.js";
6
+ import { AppPageFontPreload } from "./app-page-execution.js";
9
7
  import { AppPageMiddlewareContext } from "./app-page-response.js";
10
8
  import { AppPageFallbackRouteHead, AppPagePreparedRouteHead } from "./app-page-head.js";
11
- import { AppLayoutParamAccessTracker } from "./app-layout-param-observation.js";
12
9
  import { RootParams } from "../shims/root-params.js";
13
10
  import { AppPageSsrHandler } from "./app-page-stream.js";
14
11
  import { AppRenderErrorContextOverrides } from "./app-rsc-error-handler.js";
@@ -107,8 +104,6 @@ type AppPageDispatchSlot = {
107
104
  slotParamNames?: readonly string[] | null;
108
105
  };
109
106
  export type AppPageDispatchRoute = {
110
- __buildTimeClassifications?: LayoutClassificationOptions["buildTimeClassifications"];
111
- __buildTimeReasons?: LayoutClassificationOptions["buildTimeReasons"];
112
107
  error?: AppPageModule | null;
113
108
  errors?: readonly (AppPageModule | null | undefined)[];
114
109
  forbidden?: AppPageModule | null;
@@ -161,9 +156,11 @@ export type DispatchAppPageOptions<TRoute extends AppPageDispatchRoute> = {
161
156
  * `next.config`. Undefined falls back to the React default downstream.
162
157
  */
163
158
  reactMaxHeadersLength?: number;
164
- buildPageElement: (route: TRoute, params: AppPageParams, opts: AppPageDispatchInterceptOptions | undefined, searchParams: URLSearchParams, layoutParamAccess?: AppLayoutParamAccessTracker, options?: {
159
+ buildPageElement: (route: TRoute, params: AppPageParams, opts: AppPageDispatchInterceptOptions | undefined, searchParams: URLSearchParams, options?: {
165
160
  isForceStatic?: boolean;
166
161
  isUncacheableRender?: boolean;
162
+ /** Layouts the navigating client holds, which the render may leave out. */
163
+ mountedLayouts?: ReadonlySet<string> | null;
167
164
  observeMetadataSearchParamsAccess?: boolean;
168
165
  observePageSearchParamsAccess?: boolean;
169
166
  serveStreamingMetadata?: boolean;
@@ -171,12 +168,10 @@ export type DispatchAppPageOptions<TRoute extends AppPageDispatchRoute> = {
171
168
  /** Receives the route's head once the render starts resolving it. */
172
169
  onHeadPrepared?: (head: AppPagePreparedRouteHead) => void;
173
170
  }) => Promise<AppPageElement>;
174
- clientReuseManifest?: ClientReuseManifestParseResult;
175
171
  cleanPathname: string;
176
172
  displayPathname?: string;
177
173
  clearRequestContext: () => void;
178
174
  createRscOnErrorHandler: (pathname: string, routePath: string, overrides?: AppRenderErrorContextOverrides) => AppPageBoundaryOnError;
179
- debugClassification?: (layoutId: string, reason: ClassificationReason) => void;
180
175
  draftModeSecret: string;
181
176
  dynamicConfig?: string;
182
177
  dynamicStaleTimeSeconds?: number;
@@ -249,7 +244,7 @@ export type DispatchAppPageOptions<TRoute extends AppPageDispatchRoute> = {
249
244
  skipStaticParamsValidation?: boolean;
250
245
  staticParamsValidationParams?: AppPageParams;
251
246
  rootParams?: RootParams;
252
- probeLayoutAt: (layoutIndex: number, layoutParamAccess?: AppLayoutParamAccessTracker) => unknown;
247
+ probeLayoutAt: (layoutIndex: number) => unknown;
253
248
  expireSeconds?: number;
254
249
  renderErrorBoundaryPage: (error: unknown, errorOrigin?: "rsc" | "ssr") => Promise<Response | null>;
255
250
  renderHttpAccessFallbackPage: (statusCode: number, opts: {