vinext 0.1.8 → 0.2.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 (237) hide show
  1. package/README.md +51 -30
  2. package/dist/build/inject-pregenerated-paths.d.ts +4 -0
  3. package/dist/build/inject-pregenerated-paths.js +18 -0
  4. package/dist/build/layout-classification.js +1 -1
  5. package/dist/build/nitro-route-rules.js +1 -1
  6. package/dist/build/pages-client-assets-module.d.ts +11 -0
  7. package/dist/build/pages-client-assets-module.js +27 -0
  8. package/dist/build/prerender.d.ts +2 -1
  9. package/dist/build/prerender.js +13 -6
  10. package/dist/build/report.d.ts +13 -2
  11. package/dist/build/report.js +99 -2
  12. package/dist/build/run-prerender.d.ts +7 -0
  13. package/dist/build/run-prerender.js +10 -1
  14. package/dist/build/standalone.js +2 -0
  15. package/dist/cache/cache-adapters-virtual.js +3 -3
  16. package/dist/check.js +40 -2
  17. package/dist/cli-args.d.ts +1 -2
  18. package/dist/cli-args.js +1 -1
  19. package/dist/cli-dev-config.d.ts +12 -0
  20. package/dist/cli-dev-config.js +23 -0
  21. package/dist/cli.js +111 -95
  22. package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
  23. package/dist/{server → client}/dev-error-overlay-store.js +1 -1
  24. package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
  25. package/dist/{server → client}/dev-error-overlay.js +2 -2
  26. package/dist/client/navigation-runtime.d.ts +2 -0
  27. package/dist/client/navigation-runtime.js +3 -1
  28. package/dist/client/window-next.d.ts +2 -1
  29. package/dist/cloudflare/index.d.ts +2 -2
  30. package/dist/cloudflare/index.js +2 -2
  31. package/dist/config/config-matchers.js +14 -10
  32. package/dist/config/next-config.d.ts +5 -3
  33. package/dist/config/next-config.js +44 -1
  34. package/dist/config/prerender.d.ts +34 -0
  35. package/dist/config/prerender.js +55 -0
  36. package/dist/entries/app-rsc-entry.d.ts +2 -1
  37. package/dist/entries/app-rsc-entry.js +40 -10
  38. package/dist/entries/app-rsc-manifest.js +2 -0
  39. package/dist/entries/pages-client-entry.d.ts +3 -0
  40. package/dist/entries/pages-client-entry.js +15 -1
  41. package/dist/entries/pages-server-entry.js +5 -2
  42. package/dist/image/image-adapters-virtual.d.ts +59 -0
  43. package/dist/image/image-adapters-virtual.js +59 -0
  44. package/dist/index.d.ts +33 -0
  45. package/dist/index.js +167 -100
  46. package/dist/init-cloudflare.d.ts +43 -0
  47. package/dist/init-cloudflare.js +839 -0
  48. package/dist/init-platform.d.ts +40 -0
  49. package/dist/init-platform.js +195 -0
  50. package/dist/init.d.ts +16 -38
  51. package/dist/init.js +209 -99
  52. package/dist/node_modules/.pnpm/am-i-vibing@0.5.0/node_modules/am-i-vibing/dist/detector-1yx2Hoe0.js +294 -0
  53. package/dist/node_modules/.pnpm/process-ancestry@0.1.0/node_modules/process-ancestry/dist/index.js +94 -0
  54. package/dist/{cloudflare → packages/cloudflare}/src/cache/cdn-adapter.runtime.js +1 -1
  55. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.d.ts +2 -2
  56. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.js +1 -1
  57. package/dist/packages/cloudflare/src/deploy-config.js +150 -0
  58. package/dist/packages/cloudflare/src/deploy-help.js +55 -0
  59. package/dist/packages/cloudflare/src/deploy.js +276 -0
  60. package/dist/{cloudflare → packages/cloudflare/src}/tpr.d.ts +4 -41
  61. package/dist/{cloudflare → packages/cloudflare/src}/tpr.js +18 -15
  62. package/dist/plugins/ast-scope.d.ts +16 -0
  63. package/dist/plugins/ast-scope.js +62 -0
  64. package/dist/plugins/ast-utils.d.ts +27 -1
  65. package/dist/plugins/ast-utils.js +32 -1
  66. package/dist/plugins/css-module-imports.d.ts +14 -0
  67. package/dist/plugins/css-module-imports.js +59 -0
  68. package/dist/plugins/extensionless-dynamic-import.js +2 -2
  69. package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
  70. package/dist/plugins/ignore-dynamic-requests.js +530 -0
  71. package/dist/plugins/typeof-window.d.ts +1 -1
  72. package/dist/plugins/typeof-window.js +28 -56
  73. package/dist/routing/app-route-graph.d.ts +23 -2
  74. package/dist/routing/app-route-graph.js +126 -32
  75. package/dist/routing/file-matcher.d.ts +3 -0
  76. package/dist/routing/file-matcher.js +3 -0
  77. package/dist/routing/pages-router.js +2 -2
  78. package/dist/server/api-handler.js +9 -14
  79. package/dist/server/app-browser-action-result.d.ts +4 -1
  80. package/dist/server/app-browser-action-result.js +8 -1
  81. package/dist/server/app-browser-entry.js +161 -52
  82. package/dist/server/app-browser-interception-context.d.ts +1 -0
  83. package/dist/server/app-browser-interception-context.js +4 -2
  84. package/dist/server/app-browser-navigation-controller.d.ts +2 -0
  85. package/dist/server/app-browser-navigation-controller.js +60 -31
  86. package/dist/server/app-browser-server-action-client.d.ts +1 -1
  87. package/dist/server/app-browser-server-action-client.js +3 -4
  88. package/dist/server/app-browser-state.d.ts +11 -1
  89. package/dist/server/app-browser-state.js +25 -8
  90. package/dist/server/app-browser-visible-commit.d.ts +6 -1
  91. package/dist/server/app-browser-visible-commit.js +43 -25
  92. package/dist/server/app-elements-wire.d.ts +9 -2
  93. package/dist/server/app-elements-wire.js +8 -1
  94. package/dist/server/app-elements.d.ts +2 -2
  95. package/dist/server/app-elements.js +4 -3
  96. package/dist/server/app-fallback-renderer.d.ts +2 -1
  97. package/dist/server/app-fallback-renderer.js +10 -6
  98. package/dist/server/app-hydration-cache-publication.d.ts +11 -0
  99. package/dist/server/app-hydration-cache-publication.js +43 -0
  100. package/dist/server/app-middleware.d.ts +4 -3
  101. package/dist/server/app-middleware.js +8 -3
  102. package/dist/server/app-optimistic-routing.js +11 -4
  103. package/dist/server/app-page-cache-finalizer.js +2 -1
  104. package/dist/server/app-page-cache-render.d.ts +1 -0
  105. package/dist/server/app-page-cache-render.js +8 -4
  106. package/dist/server/app-page-cache.d.ts +1 -0
  107. package/dist/server/app-page-cache.js +5 -2
  108. package/dist/server/app-page-dispatch.js +1 -0
  109. package/dist/server/app-page-element-builder.d.ts +2 -1
  110. package/dist/server/app-page-element-builder.js +58 -4
  111. package/dist/server/app-page-probe.js +5 -0
  112. package/dist/server/app-page-render-identity.d.ts +1 -0
  113. package/dist/server/app-page-render-identity.js +1 -1
  114. package/dist/server/app-page-render.js +18 -0
  115. package/dist/server/app-page-request.d.ts +14 -0
  116. package/dist/server/app-page-request.js +69 -4
  117. package/dist/server/app-page-route-wiring.d.ts +8 -1
  118. package/dist/server/app-page-route-wiring.js +32 -18
  119. package/dist/server/app-page-segment-state.d.ts +2 -1
  120. package/dist/server/app-page-segment-state.js +3 -1
  121. package/dist/server/app-page-stream.d.ts +5 -0
  122. package/dist/server/app-page-stream.js +3 -1
  123. package/dist/server/app-route-handler-execution.js +5 -1
  124. package/dist/server/app-route-handler-response.js +1 -0
  125. package/dist/server/app-route-handler-runtime.js +1 -1
  126. package/dist/server/app-router-entry.js +9 -2
  127. package/dist/server/app-rsc-handler.d.ts +1 -0
  128. package/dist/server/app-rsc-handler.js +62 -31
  129. package/dist/server/app-rsc-request-normalization.d.ts +3 -2
  130. package/dist/server/app-rsc-request-normalization.js +6 -3
  131. package/dist/server/app-segment-config.d.ts +11 -0
  132. package/dist/server/app-segment-config.js +62 -6
  133. package/dist/server/app-server-action-execution.d.ts +1 -0
  134. package/dist/server/app-server-action-execution.js +58 -28
  135. package/dist/server/app-ssr-entry.d.ts +3 -0
  136. package/dist/server/app-ssr-entry.js +15 -7
  137. package/dist/server/app-ssr-stream.d.ts +7 -3
  138. package/dist/server/app-ssr-stream.js +10 -6
  139. package/dist/server/app-visited-response-cache.d.ts +6 -0
  140. package/dist/server/app-visited-response-cache.js +3 -1
  141. package/dist/server/client-trace-metadata.js +26 -0
  142. package/dist/server/default-global-not-found-module.d.ts +14 -0
  143. package/dist/server/default-global-not-found-module.js +14 -0
  144. package/dist/server/dev-route-files.js +4 -0
  145. package/dist/server/dev-server.js +19 -18
  146. package/dist/server/dev-stack-sourcemap.d.ts +1 -1
  147. package/dist/server/dev-stack-sourcemap.js +1 -1
  148. package/dist/server/fetch-handler.d.ts +2 -0
  149. package/dist/server/fetch-handler.js +18 -0
  150. package/dist/server/headers.d.ts +3 -15
  151. package/dist/server/headers.js +2 -15
  152. package/dist/server/image-optimization.d.ts +51 -1
  153. package/dist/server/image-optimization.js +52 -2
  154. package/dist/server/implicit-tags.js +1 -1
  155. package/dist/server/isr-cache.d.ts +1 -1
  156. package/dist/server/isr-cache.js +2 -2
  157. package/dist/server/metadata-routes.d.ts +3 -1
  158. package/dist/server/metadata-routes.js +12 -2
  159. package/dist/server/middleware-runtime.js +6 -1
  160. package/dist/server/navigation-planner.d.ts +1 -0
  161. package/dist/server/navigation-planner.js +14 -3
  162. package/dist/server/pages-api-route.js +2 -2
  163. package/dist/server/pages-asset-tags.d.ts +4 -6
  164. package/dist/server/pages-asset-tags.js +12 -12
  165. package/dist/server/pages-client-assets.d.ts +12 -0
  166. package/dist/server/pages-client-assets.js +10 -0
  167. package/dist/server/pages-node-compat.d.ts +8 -1
  168. package/dist/server/pages-node-compat.js +32 -4
  169. package/dist/server/pages-page-data.d.ts +24 -1
  170. package/dist/server/pages-page-data.js +45 -24
  171. package/dist/server/pages-page-handler.d.ts +2 -1
  172. package/dist/server/pages-page-handler.js +19 -12
  173. package/dist/server/pages-page-response.d.ts +7 -0
  174. package/dist/server/pages-page-response.js +4 -1
  175. package/dist/server/pages-request-pipeline.d.ts +2 -0
  176. package/dist/server/pages-request-pipeline.js +25 -1
  177. package/dist/server/pages-router-entry.d.ts +30 -0
  178. package/dist/server/pages-router-entry.js +108 -0
  179. package/dist/server/prerender-manifest.d.ts +3 -1
  180. package/dist/server/prerender-route-params.js +1 -1
  181. package/dist/server/prod-server.d.ts +1 -1
  182. package/dist/server/prod-server.js +33 -20
  183. package/dist/server/request-pipeline.js +1 -0
  184. package/dist/server/seed-cache.js +4 -4
  185. package/dist/shims/app-router-scroll-state.d.ts +1 -0
  186. package/dist/shims/app-router-scroll-state.js +1 -0
  187. package/dist/shims/app-router-scroll.js +2 -1
  188. package/dist/shims/cache.js +19 -15
  189. package/dist/shims/cdn-cache.js +1 -1
  190. package/dist/shims/dynamic-preload-chunks.js +2 -1
  191. package/dist/shims/form.d.ts +3 -1
  192. package/dist/shims/form.js +37 -43
  193. package/dist/shims/headers.d.ts +7 -1
  194. package/dist/shims/headers.js +17 -3
  195. package/dist/shims/image.js +75 -14
  196. package/dist/shims/internal/app-route-detection.d.ts +2 -17
  197. package/dist/shims/internal/app-route-detection.js +4 -17
  198. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +23 -0
  199. package/dist/shims/internal/hybrid-client-route-owner-direct.js +51 -0
  200. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -15
  201. package/dist/shims/internal/hybrid-client-route-owner.js +9 -60
  202. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +9 -5
  203. package/dist/shims/internal/pages-data-fetch-dedup.js +73 -14
  204. package/dist/shims/internal/pages-data-target.d.ts +10 -9
  205. package/dist/shims/internal/pages-data-target.js +91 -12
  206. package/dist/shims/internal/pages-router-components.d.ts +7 -0
  207. package/dist/shims/internal/pages-router-components.js +13 -0
  208. package/dist/shims/layout-segment-context.d.ts +4 -1
  209. package/dist/shims/layout-segment-context.js +18 -4
  210. package/dist/shims/link.js +55 -26
  211. package/dist/shims/navigation.d.ts +5 -1
  212. package/dist/shims/navigation.js +45 -10
  213. package/dist/shims/navigation.react-server.js +1 -1
  214. package/dist/shims/root-params.d.ts +15 -1
  215. package/dist/shims/root-params.js +21 -1
  216. package/dist/shims/router.d.ts +5 -7
  217. package/dist/shims/router.js +362 -93
  218. package/dist/shims/server.js +3 -2
  219. package/dist/shims/slot.js +3 -6
  220. package/dist/utils/client-runtime-metadata.d.ts +2 -18
  221. package/dist/utils/client-runtime-metadata.js +31 -22
  222. package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
  223. package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
  224. package/dist/utils/domain-locale.d.ts +6 -3
  225. package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
  226. package/dist/{server → utils}/middleware-request-headers.js +2 -2
  227. package/dist/utils/project.d.ts +48 -2
  228. package/dist/utils/project.js +225 -4
  229. package/dist/utils/protocol-headers.d.ts +17 -0
  230. package/dist/utils/protocol-headers.js +17 -0
  231. package/dist/utils/react-version.d.ts +4 -0
  232. package/dist/utils/react-version.js +44 -0
  233. package/package.json +47 -2
  234. package/dist/deploy.d.ts +0 -197
  235. package/dist/deploy.js +0 -1040
  236. package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
  237. /package/dist/{cloudflare → packages/cloudflare}/src/utils/cache-control-metadata.js +0 -0
@@ -8,6 +8,7 @@ import { isValidElement } from "react";
8
8
  const APP_INTERCEPTION_SEPARATOR = "\0";
9
9
  const APP_ARTIFACT_COMPATIBILITY_KEY = "__artifactCompatibility";
10
10
  const APP_CACHE_ENTRY_REUSE_PROOF_KEY = "__cacheEntryReuseProof";
11
+ const APP_DYNAMIC_STALE_TIME_KEY = "__dynamicStaleTime";
11
12
  const APP_INTERCEPTION_KEY = "__interception";
12
13
  const APP_INTERCEPTION_CONTEXT_KEY = "__interceptionContext";
13
14
  const APP_LAYOUT_IDS_KEY = "__layoutIds";
@@ -177,6 +178,7 @@ function createAppElementsWireMetadataEntries(input) {
177
178
  [APP_INTERCEPTION_CONTEXT_KEY]: input.interceptionContext,
178
179
  [APP_LAYOUT_IDS_KEY]: layoutIds,
179
180
  [APP_ROOT_LAYOUT_KEY]: input.rootLayoutTreePath,
181
+ ...input.dynamicStaleTimeSeconds === void 0 ? {} : { [APP_DYNAMIC_STALE_TIME_KEY]: input.dynamicStaleTimeSeconds },
180
182
  ...input.sourcePage === null || input.sourcePage === void 0 ? {} : { [APP_SOURCE_PAGE_KEY]: input.sourcePage }
181
183
  };
182
184
  const entriesWithInterception = input.interception ? {
@@ -319,6 +321,7 @@ function buildOutgoingAppPayload(input) {
319
321
  if (skippedLayoutIds.size > 0) payload[APP_SKIPPED_LAYOUT_IDS_KEY] = [...skippedLayoutIds];
320
322
  payload[APP_ARTIFACT_COMPATIBILITY_KEY] = input.artifactCompatibility ?? createArtifactCompatibilityEnvelope();
321
323
  if (input.cacheEntryReuseProof) payload[APP_CACHE_ENTRY_REUSE_PROOF_KEY] = input.cacheEntryReuseProof;
324
+ if (input.dynamicStaleTimeSeconds !== void 0) payload[APP_DYNAMIC_STALE_TIME_KEY] = input.dynamicStaleTimeSeconds;
322
325
  if (input.renderObservation) payload[APP_RENDER_OBSERVATION_KEY] = input.renderObservation;
323
326
  return payload;
324
327
  }
@@ -394,10 +397,13 @@ function readAppElementsMetadata(elements) {
394
397
  const interception = parseInterceptionMetadata(elements[APP_INTERCEPTION_KEY]);
395
398
  const artifactCompatibility = readArtifactCompatibilityMetadata(elements[APP_ARTIFACT_COMPATIBILITY_KEY]);
396
399
  const cacheEntryReuseProof = parseCacheEntryReuseProofMetadata(elements[APP_CACHE_ENTRY_REUSE_PROOF_KEY]);
400
+ const dynamicStaleTime = elements[APP_DYNAMIC_STALE_TIME_KEY];
401
+ const dynamicStaleTimeSeconds = typeof dynamicStaleTime === "number" && Number.isFinite(dynamicStaleTime) && dynamicStaleTime >= 0 ? dynamicStaleTime : void 0;
397
402
  const sourcePage = readSourcePageMetadata(elements[APP_SOURCE_PAGE_KEY]);
398
403
  return {
399
404
  artifactCompatibility,
400
405
  ...cacheEntryReuseProof ? { cacheEntryReuseProof } : {},
406
+ ...dynamicStaleTimeSeconds === void 0 ? {} : { dynamicStaleTimeSeconds },
401
407
  interception,
402
408
  interceptionContext: interceptionContext ?? null,
403
409
  layoutIds,
@@ -413,6 +419,7 @@ const AppElementsWire = {
413
419
  keys: {
414
420
  artifactCompatibility: APP_ARTIFACT_COMPATIBILITY_KEY,
415
421
  cacheEntryReuseProof: APP_CACHE_ENTRY_REUSE_PROOF_KEY,
422
+ dynamicStaleTime: APP_DYNAMIC_STALE_TIME_KEY,
416
423
  interception: APP_INTERCEPTION_KEY,
417
424
  interceptionContext: APP_INTERCEPTION_CONTEXT_KEY,
418
425
  layoutIds: APP_LAYOUT_IDS_KEY,
@@ -440,4 +447,4 @@ const AppElementsWire = {
440
447
  withLayoutFlags
441
448
  };
442
449
  //#endregion
443
- export { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementsWire, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, withLayoutFlags };
450
+ export { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_DYNAMIC_STALE_TIME_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementsWire, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, withLayoutFlags };
@@ -1,4 +1,4 @@
1
- import { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementValue, AppElements, AppElementsInterception, AppElementsSlotBinding, AppElementsWire, AppOutgoingElements, AppWireElements, LayoutFlags, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, withLayoutFlags } from "./app-elements-wire.js";
1
+ import { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_DYNAMIC_STALE_TIME_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementValue, AppElements, AppElementsInterception, AppElementsSlotBinding, AppElementsWire, AppOutgoingElements, AppWireElements, LayoutFlags, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, withLayoutFlags } from "./app-elements-wire.js";
2
2
 
3
3
  //#region src/server/app-elements.d.ts
4
4
  declare const APP_PREFETCH_LOADING_SHELL_MARKER_KEY = "__prefetchLoadingShell";
@@ -6,4 +6,4 @@ declare function getMountedSlotIds(elements: AppElements): string[];
6
6
  declare function getMountedSlotIdsHeader(elements: AppElements): string | null;
7
7
  declare function resolveVisitedResponseInterceptionContext(requestInterceptionContext: string | null, payloadInterceptionContext: string | null): string | null;
8
8
  //#endregion
9
- export { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_PREFETCH_LOADING_SHELL_MARKER_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, type AppElementValue, type AppElements, type AppElementsInterception, type AppElementsSlotBinding, AppElementsWire, type AppOutgoingElements, type AppWireElements, type LayoutFlags, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, getMountedSlotIds, getMountedSlotIdsHeader, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, resolveVisitedResponseInterceptionContext, withLayoutFlags };
9
+ export { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_DYNAMIC_STALE_TIME_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_PREFETCH_LOADING_SHELL_MARKER_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, type AppElementValue, type AppElements, type AppElementsInterception, type AppElementsSlotBinding, AppElementsWire, type AppOutgoingElements, type AppWireElements, type LayoutFlags, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, getMountedSlotIds, getMountedSlotIdsHeader, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, resolveVisitedResponseInterceptionContext, withLayoutFlags };
@@ -1,11 +1,12 @@
1
- import { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementsWire, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, withLayoutFlags } from "./app-elements-wire.js";
1
+ import { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_DYNAMIC_STALE_TIME_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementsWire, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, withLayoutFlags } from "./app-elements-wire.js";
2
2
  import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
3
3
  //#region src/server/app-elements.ts
4
4
  const APP_PREFETCH_LOADING_SHELL_MARKER_KEY = "__prefetchLoadingShell";
5
5
  function getMountedSlotIds(elements) {
6
6
  return Object.keys(elements).filter((key) => {
7
7
  const value = elements[key];
8
- return AppElementsWire.isSlotId(key) && value !== null && value !== void 0 && value !== UNMATCHED_SLOT;
8
+ const parsed = AppElementsWire.parseElementKey(key);
9
+ return parsed?.kind === "slot" && parsed.name !== "children" && value !== null && value !== void 0 && value !== UNMATCHED_SLOT;
9
10
  }).sort();
10
11
  }
11
12
  function getMountedSlotIdsHeader(elements) {
@@ -15,4 +16,4 @@ function resolveVisitedResponseInterceptionContext(requestInterceptionContext, p
15
16
  return payloadInterceptionContext ?? requestInterceptionContext;
16
17
  }
17
18
  //#endregion
18
- export { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_PREFETCH_LOADING_SHELL_MARKER_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementsWire, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, getMountedSlotIds, getMountedSlotIdsHeader, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, resolveVisitedResponseInterceptionContext, withLayoutFlags };
19
+ export { APP_ARTIFACT_COMPATIBILITY_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_DYNAMIC_STALE_TIME_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_FLAGS_KEY, APP_LAYOUT_IDS_KEY, APP_PREFETCH_LOADING_SHELL_MARKER_KEY, APP_RENDER_OBSERVATION_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementsWire, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, getMountedSlotIds, getMountedSlotIdsHeader, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, resolveVisitedResponseInterceptionContext, withLayoutFlags };
@@ -33,7 +33,8 @@ type AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> =
33
33
  createRscOnErrorHandler: (request: Request, pathname: string, routePath: string) => AppPageBoundaryOnError;
34
34
  fontProviders: AppFallbackRendererFontProviders;
35
35
  getNavigationContext: () => NavigationContext | null;
36
- globalErrorModule?: TModule | null;
36
+ globalErrorModule?: TModule | null; /** Whether experimental.globalNotFound is enabled for route-miss 404s. */
37
+ globalNotFoundEnabled?: boolean;
37
38
  /**
38
39
  * Loader for the user's `app/global-not-found.tsx` module. When provided,
39
40
  * route-miss 404s render this module as a standalone document (skipping the
@@ -1,5 +1,6 @@
1
1
  import { renderAppPageErrorBoundary, renderAppPageHttpAccessFallback } from "./app-page-boundary-render.js";
2
2
  import { DEFAULT_GLOBAL_ERROR_MODULE } from "./default-global-error-module.js";
3
+ import { DEFAULT_GLOBAL_NOT_FOUND_MODULE } from "./default-global-not-found-module.js";
3
4
  import { DEFAULT_NOT_FOUND_MODULE } from "./default-not-found-module.js";
4
5
  //#region src/server/app-fallback-renderer.ts
5
6
  const EMPTY_MW_CTX = {
@@ -7,7 +8,7 @@ const EMPTY_MW_CTX = {
7
8
  status: null
8
9
  };
9
10
  function createAppFallbackRenderer(options) {
10
- const { applyFileBasedMetadata, basePath = "", clearRequestContext, createRscOnErrorHandler: buildRscOnErrorHandler, fontProviders, getNavigationContext, globalErrorModule, loadGlobalNotFoundModule, makeThenableParams, metadataRoutes, resolveChildSegments, rootBoundaries, rscRenderer, sanitizer, ssrLoader, trailingSlash } = options;
11
+ const { applyFileBasedMetadata, basePath = "", clearRequestContext, createRscOnErrorHandler: buildRscOnErrorHandler, fontProviders, getNavigationContext, globalErrorModule, globalNotFoundEnabled = false, loadGlobalNotFoundModule, makeThenableParams, metadataRoutes, resolveChildSegments, rootBoundaries, rscRenderer, sanitizer, ssrLoader, trailingSlash } = options;
11
12
  const { rootForbiddenModule, rootLayouts, rootNotFoundModule, rootUnauthorizedModule } = rootBoundaries;
12
13
  const effectiveGlobalErrorModule = globalErrorModule ?? DEFAULT_GLOBAL_ERROR_MODULE;
13
14
  const effectiveRootNotFoundModule = rootNotFoundModule ?? DEFAULT_NOT_FOUND_MODULE;
@@ -19,7 +20,8 @@ function createAppFallbackRenderer(options) {
19
20
  }
20
21
  return {
21
22
  async renderHttpAccessFallback(route, statusCode, isRscRequest, request, opts, scriptNonce, middlewareContext, callContext) {
22
- if (statusCode === 404 && !!loadGlobalNotFoundModule && !route && !opts?.boundaryComponent) {
23
+ const useGlobalNotFound = statusCode === 404 && globalNotFoundEnabled && !route && !opts?.boundaryComponent;
24
+ if (useGlobalNotFound && loadGlobalNotFoundModule) {
23
25
  const globalNotFoundModule = await resolveGlobalNotFoundModule();
24
26
  const globalNotFoundComponent = globalNotFoundModule?.default ?? null;
25
27
  if (globalNotFoundComponent) return renderAppPageHttpAccessFallback({
@@ -57,6 +59,7 @@ function createAppFallbackRenderer(options) {
57
59
  statusCode
58
60
  });
59
61
  }
62
+ const routeMissRootNotFoundModule = useGlobalNotFound ? DEFAULT_GLOBAL_NOT_FOUND_MODULE : effectiveRootNotFoundModule;
60
63
  return renderAppPageHttpAccessFallback({
61
64
  applyFileBasedMetadata,
62
65
  basePath,
@@ -75,7 +78,7 @@ function createAppFallbackRenderer(options) {
75
78
  globalErrorModule: effectiveGlobalErrorModule,
76
79
  isEdgeRuntime: callContext?.isEdgeRuntime,
77
80
  isRscRequest,
78
- layoutModules: opts?.layouts ?? null,
81
+ layoutModules: useGlobalNotFound ? [] : opts?.layouts ?? null,
79
82
  loadSsrHandler: ssrLoader,
80
83
  makeThenableParams,
81
84
  matchedParams: opts?.matchedParams ?? route?.params ?? {},
@@ -84,12 +87,13 @@ function createAppFallbackRenderer(options) {
84
87
  requestUrl: request.url,
85
88
  resolveChildSegments,
86
89
  rootForbiddenModule,
87
- rootLayouts,
88
- rootNotFoundModule: effectiveRootNotFoundModule,
90
+ rootLayouts: useGlobalNotFound ? [] : rootLayouts,
91
+ rootNotFoundModule: routeMissRootNotFoundModule,
89
92
  rootUnauthorizedModule,
90
- route,
93
+ route: useGlobalNotFound ? null : route,
91
94
  renderToReadableStream: rscRenderer,
92
95
  scriptNonce,
96
+ skipLayoutWrapping: useGlobalNotFound,
93
97
  sourcePageSegments: callContext?.sourcePageSegments,
94
98
  statusCode
95
99
  });
@@ -0,0 +1,11 @@
1
+ //#region src/server/app-hydration-cache-publication.d.ts
2
+ type HydrationCachePublication = {
3
+ commit(): void;
4
+ complete(): void;
5
+ fail(): void;
6
+ invalidate(): void;
7
+ publish(publishCandidate: () => () => void): void;
8
+ };
9
+ declare function createHydrationCachePublication(): HydrationCachePublication;
10
+ //#endregion
11
+ export { HydrationCachePublication, createHydrationCachePublication };
@@ -0,0 +1,43 @@
1
+ //#region src/server/app-hydration-cache-publication.ts
2
+ function createHydrationCachePublication() {
3
+ let state = "pending";
4
+ let pendingPublication = null;
5
+ let invalidatePublishedCandidate = null;
6
+ const publishPendingCandidate = () => {
7
+ if (state !== "committed" && state !== "complete" || pendingPublication === null) return;
8
+ const publishCandidate = pendingPublication;
9
+ pendingPublication = null;
10
+ invalidatePublishedCandidate = publishCandidate();
11
+ };
12
+ return {
13
+ commit() {
14
+ if (state !== "pending") return;
15
+ state = "committed";
16
+ publishPendingCandidate();
17
+ },
18
+ complete() {
19
+ if (state === "committed") state = "complete";
20
+ },
21
+ fail() {
22
+ if (state === "complete" || state === "invalidated") return;
23
+ state = "invalidated";
24
+ pendingPublication = null;
25
+ invalidatePublishedCandidate?.();
26
+ invalidatePublishedCandidate = null;
27
+ },
28
+ invalidate() {
29
+ if (state === "invalidated") return;
30
+ state = "invalidated";
31
+ pendingPublication = null;
32
+ invalidatePublishedCandidate?.();
33
+ invalidatePublishedCandidate = null;
34
+ },
35
+ publish(publishCandidate) {
36
+ if (state === "invalidated") return;
37
+ pendingPublication = publishCandidate;
38
+ publishPendingCandidate();
39
+ }
40
+ };
41
+ }
42
+ //#endregion
43
+ export { createHydrationCachePublication };
@@ -12,11 +12,11 @@ type ApplyAppMiddlewareOptions = {
12
12
  basePath?: string;
13
13
  cleanPathname: string;
14
14
  context: AppMiddlewareContext;
15
+ hadBasePath?: boolean;
15
16
  i18nConfig?: NextI18nConfig | null;
16
17
  /**
17
- * Whether the inbound request was a `_next/data` fetch. Captured from the
18
- * raw incoming headers by the caller, because `x-nextjs-data` is in
19
- * INTERNAL_HEADERS and is stripped before this function runs.
18
+ * Whether the inbound request was recognized as a `_next/data` fetch from
19
+ * trusted URL normalization before internal headers were stripped.
20
20
  */
21
21
  isDataRequest?: boolean;
22
22
  filePath?: string;
@@ -34,6 +34,7 @@ type ApplyAppMiddlewareOptions = {
34
34
  type ApplyAppMiddlewareResult = {
35
35
  kind: "continue";
36
36
  cleanPathname: string;
37
+ rewritten: boolean;
37
38
  search: string | null;
38
39
  } | {
39
40
  kind: "response";
@@ -1,5 +1,6 @@
1
- import { FLIGHT_HEADERS, VINEXT_MW_CTX_HEADER } from "./headers.js";
2
- import { buildRequestHeadersFromMiddlewareResponse } from "./middleware-request-headers.js";
1
+ import { VINEXT_MW_CTX_HEADER } from "../utils/protocol-headers.js";
2
+ import { FLIGHT_HEADERS } from "./headers.js";
3
+ import { buildRequestHeadersFromMiddlewareResponse } from "../utils/middleware-request-headers.js";
3
4
  import { isExternalUrl, proxyExternalRequest } from "../config/config-matchers.js";
4
5
  import { internalServerErrorResponse } from "./http-error-responses.js";
5
6
  import { cloneRequestWithHeaders, processMiddlewareHeaders } from "./request-pipeline.js";
@@ -100,6 +101,7 @@ async function applyAppMiddleware(options) {
100
101
  const forwarded = applyForwardedMiddlewareContext(options.request, options.context);
101
102
  const middlewareRequest = requestWithoutFlightHeaders(options.request);
102
103
  let cleanPathname = options.cleanPathname;
104
+ let rewritten = false;
103
105
  let search = null;
104
106
  if (forwarded.rewriteUrl) try {
105
107
  if (isExternalMiddlewareRewrite(forwarded.rewriteUrl, middlewareRequest)) return {
@@ -108,6 +110,7 @@ async function applyAppMiddleware(options) {
108
110
  };
109
111
  const rewriteParsed = new URL(forwarded.rewriteUrl, middlewareRequest.url);
110
112
  cleanPathname = rewriteParsed.pathname;
113
+ rewritten = true;
111
114
  search = rewriteParsed.search;
112
115
  } catch (e) {
113
116
  console.error("[vinext] Failed to apply forwarded middleware rewrite:", e);
@@ -116,7 +119,7 @@ async function applyAppMiddleware(options) {
116
119
  if (!forwarded.applied) {
117
120
  const result = await executeMiddleware({
118
121
  basePath: options.basePath,
119
- hadBasePath: true,
122
+ hadBasePath: options.hadBasePath ?? true,
120
123
  filePath: options.filePath,
121
124
  i18nConfig: options.i18nConfig,
122
125
  isDataRequest: options.isDataRequest,
@@ -150,6 +153,7 @@ async function applyAppMiddleware(options) {
150
153
  };
151
154
  const rewriteParsed = new URL(result.rewriteUrl, middlewareRequest.url);
152
155
  cleanPathname = rewriteParsed.pathname;
156
+ rewritten = true;
153
157
  search = rewriteParsed.search;
154
158
  }
155
159
  }
@@ -161,6 +165,7 @@ async function applyAppMiddleware(options) {
161
165
  return {
162
166
  kind: "continue",
163
167
  cleanPathname,
168
+ rewritten,
164
169
  search
165
170
  };
166
171
  }
@@ -1,9 +1,9 @@
1
1
  import { buildParams, decodeMatchedParams, splitPathnameForRouteMatch } from "../routing/utils.js";
2
2
  import { stripBasePath } from "../utils/base-path.js";
3
- import { matchRoutePattern } from "../routing/route-pattern.js";
4
3
  import { isUnknownRecord } from "../utils/record.js";
5
4
  import { AppElementsWire } from "./app-elements-wire.js";
6
5
  import "./app-elements.js";
6
+ import { matchRoutePattern } from "../routing/route-pattern.js";
7
7
  import { stripRscCacheBustingSearchParam, stripRscSuffix } from "./app-rsc-cache-busting.js";
8
8
  import { Suspense, createElement, isValidElement } from "react";
9
9
  //#region src/server/app-optimistic-routing.ts
@@ -162,8 +162,15 @@ function elementHasSuspenseFallback(value, depth = 0) {
162
162
  if (!isUnknownRecord(props)) return false;
163
163
  return elementHasSuspenseFallback(Reflect.get(props, "children"), depth + 1);
164
164
  }
165
- function getPageElementIds(elements) {
166
- return Object.keys(elements).filter((key) => AppElementsWire.parseElementKey(key)?.kind === "page").sort();
165
+ function getPageElementIds(elements, route) {
166
+ const pageElementIds = /* @__PURE__ */ new Set();
167
+ if (route.pageId && Object.hasOwn(elements, route.pageId)) pageElementIds.add(route.pageId);
168
+ for (const slotId of route.slotIds) {
169
+ const parsed = AppElementsWire.parseElementKey(slotId);
170
+ if (parsed?.kind === "slot" && parsed.name === "children" && Object.hasOwn(elements, slotId)) pageElementIds.add(slotId);
171
+ }
172
+ for (const key of Object.keys(elements)) if (AppElementsWire.parseElementKey(key)?.kind === "page") pageElementIds.add(key);
173
+ return Array.from(pageElementIds).sort();
167
174
  }
168
175
  function OptimisticRouteSegment() {
169
176
  throw OPTIMISTIC_ROUTE_SEGMENT_SUSPENSE_TRIGGER;
@@ -182,7 +189,7 @@ function createOptimisticRouteTemplate(options) {
182
189
  if (!options.allowLoadingShell && !elementHasSuspenseFallback(routeElement)) return null;
183
190
  if (options.allowLoadingShell && options.elements["__prefetchLoadingShell"] !== "LoadingBoundary") return null;
184
191
  if (options.allowLoadingShell && (routeElement === void 0 || routeElement === null)) return null;
185
- const pageElementIds = getPageElementIds(options.elements);
192
+ const pageElementIds = getPageElementIds(options.elements, match.route);
186
193
  if (pageElementIds.length === 0) return null;
187
194
  return {
188
195
  elements: options.elements,
@@ -63,7 +63,8 @@ function finalizeAppPageHtmlCacheResponse(response, options) {
63
63
  cacheTags: pageTags,
64
64
  state: observationState
65
65
  });
66
- const writes = [options.isrSet(htmlKey, buildAppPageCacheValue(cachedHtml, void 0, 200, htmlRenderObservation), cachePolicy.revalidateSeconds, pageTags, cachePolicy.expireSeconds)];
66
+ const linkHeader = response.headers.get("link");
67
+ const writes = [options.isrSet(htmlKey, buildAppPageCacheValue(cachedHtml, void 0, 200, htmlRenderObservation, linkHeader ? { link: linkHeader } : void 0), cachePolicy.revalidateSeconds, pageTags, cachePolicy.expireSeconds)];
67
68
  if (options.capturedRscDataPromise) writes.push(options.capturedRscDataPromise.then((rscData) => options.isrSet(rscKey, buildAppPageCacheValue("", rscData, 200, rscRenderObservation), cachePolicy.revalidateSeconds, pageTags, cachePolicy.expireSeconds)));
68
69
  await Promise.all(writes);
69
70
  options.isrDebug?.("HTML cache written", htmlKey);
@@ -38,6 +38,7 @@ type RenderAppPageCacheArtifactsResult = {
38
38
  cacheControl?: CacheControlMetadata;
39
39
  html: string;
40
40
  htmlRenderObservation: ReturnType<typeof createAppPageRenderObservation>;
41
+ linkHeader?: string;
41
42
  rscData?: ArrayBuffer;
42
43
  rscRenderObservation?: ReturnType<typeof createAppPageRenderObservation>;
43
44
  tags: string[];
@@ -2,9 +2,9 @@ import { consumeDynamicUsage, consumeInvalidDynamicUsageError } from "../shims/h
2
2
  import { _consumeRequestScopedCacheLife } from "../shims/cache-request-state.js";
3
3
  import { getCollectedFetchTags } from "../shims/fetch-cache.js";
4
4
  import { readStreamAsText } from "../utils/text-stream.js";
5
- import { teeAppPageRscStreamForCapture } from "./app-page-execution.js";
5
+ import { buildAppPageFontLinkHeader, teeAppPageRscStreamForCapture } from "./app-page-execution.js";
6
6
  import { consumeAppPageRenderObservationState, createAppPageHtmlOutputScope, createAppPageRenderObservation, createAppPageRscOutputScope } from "./app-page-render-observation.js";
7
- import { isAppSsrRenderResult } from "./app-page-stream.js";
7
+ import { buildAppPageLinkHeader, isAppSsrRenderResult } from "./app-page-stream.js";
8
8
  import { buildAppPageTags } from "./implicit-tags.js";
9
9
  //#region src/server/app-page-cache-render.ts
10
10
  /**
@@ -16,10 +16,11 @@ import { buildAppPageTags } from "./implicit-tags.js";
16
16
  async function renderAppPageCacheArtifacts(options) {
17
17
  const rscCapture = teeAppPageRscStreamForCapture(options.renderToReadableStream(options.element, { onError: options.onError }), options.captureRscData);
18
18
  const capturedRscDataRef = { value: null };
19
+ const fontPreloads = options.getFontPreloads();
19
20
  const htmlResult = await (await options.loadSsrHandler()).handleSsr(rscCapture.ssrStream, options.getNavigationContext(), {
20
21
  links: options.getFontLinks(),
21
22
  styles: options.getFontStyles(),
22
- preloads: options.getFontPreloads()
23
+ preloads: fontPreloads
23
24
  }, {
24
25
  basePath: options.basePath,
25
26
  clientTraceMetadata: options.clientTraceMetadata,
@@ -31,7 +32,9 @@ async function renderAppPageCacheArtifacts(options) {
31
32
  capturedRscDataRef
32
33
  } : {}
33
34
  });
34
- const html = await readStreamAsText(isAppSsrRenderResult(htmlResult) ? htmlResult.htmlStream : htmlResult);
35
+ const htmlStream = isAppSsrRenderResult(htmlResult) ? htmlResult.htmlStream : htmlResult;
36
+ const linkHeader = buildAppPageLinkHeader(isAppSsrRenderResult(htmlResult) ? htmlResult.linkHeader : void 0, buildAppPageFontLinkHeader(fontPreloads), options.reactMaxHeadersLength);
37
+ const html = await readStreamAsText(htmlStream);
35
38
  let rscData;
36
39
  if (options.captureRscData) {
37
40
  const capturedPromise = capturedRscDataRef.value;
@@ -60,6 +63,7 @@ async function renderAppPageCacheArtifacts(options) {
60
63
  params: options.navigationParams,
61
64
  state: observationState
62
65
  }),
66
+ ...linkHeader ? { linkHeader } : {},
63
67
  tags,
64
68
  cacheControl: typeof cacheLife?.revalidate === "number" ? {
65
69
  revalidate: cacheLife.revalidate,
@@ -25,6 +25,7 @@ type AppPageCacheRenderResult = {
25
25
  cacheControl?: CacheControlMetadata;
26
26
  html: string;
27
27
  htmlRenderObservation?: RenderObservation;
28
+ linkHeader?: string;
28
29
  rscData: ArrayBuffer;
29
30
  rscRenderObservation?: RenderObservation;
30
31
  tags: string[];
@@ -19,7 +19,7 @@ function recordAppPageCacheOutcome(recordCacheOutcome, input) {
19
19
  function buildAppPageCacheTags(pathname, extraTags) {
20
20
  const tags = [
21
21
  pathname,
22
- `_N_T_${pathname}`,
22
+ `_N_T_${pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname}`,
23
23
  "_N_T_/layout"
24
24
  ];
25
25
  const segments = pathname.split("/");
@@ -43,6 +43,8 @@ function buildAppPageCachedHeaders(options) {
43
43
  applyCdnResponseHeaders(headers, { cacheControl: options.cacheControl });
44
44
  setCacheStateHeaders(headers, options.cacheState);
45
45
  applyEdgeRuntimeHeader(headers, options.isEdgeRuntime);
46
+ if (options.linkHeader) if (Array.isArray(options.linkHeader)) for (const value of options.linkHeader) headers.append("Link", value);
47
+ else headers.set("Link", options.linkHeader);
46
48
  if (options.mountedSlotsHeader) headers.set(VINEXT_MOUNTED_SLOTS_HEADER, options.mountedSlotsHeader);
47
49
  mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders ?? null);
48
50
  return headers;
@@ -84,6 +86,7 @@ function buildAppPageCachedResponse(cachedValue, options) {
84
86
  cacheState: options.cacheState,
85
87
  contentType: "text/html; charset=utf-8",
86
88
  isEdgeRuntime: options.isEdgeRuntime,
89
+ linkHeader: cachedValue.headers?.link,
87
90
  middlewareHeaders: options.middlewareHeaders
88
91
  });
89
92
  return new Response(cachedValue.html, {
@@ -177,7 +180,7 @@ async function readAppPageCacheResponse(options) {
177
180
  const revalidateSeconds = revalidatedPage.cacheControl?.revalidate ?? options.revalidateSeconds;
178
181
  const expireSeconds = revalidatedPage.cacheControl?.expire ?? options.expireSeconds;
179
182
  const writes = [options.isrSet(options.isRscRequest ? isrKey : options.isrRscKey(options.cleanPathname, options.mountedSlotsHeader, options.renderMode, options.interceptionContext), buildAppPageCacheValue("", revalidatedPage.rscData, 200, revalidatedPage.rscRenderObservation), revalidateSeconds, revalidatedPage.tags, expireSeconds)];
180
- if (!options.isRscRequest) writes.push(options.isrSet(isrKey, buildAppPageCacheValue(revalidatedPage.html, void 0, 200, revalidatedPage.htmlRenderObservation), revalidateSeconds, revalidatedPage.tags, expireSeconds));
183
+ if (!options.isRscRequest) writes.push(options.isrSet(isrKey, buildAppPageCacheValue(revalidatedPage.html, void 0, 200, revalidatedPage.htmlRenderObservation, revalidatedPage.linkHeader ? { link: revalidatedPage.linkHeader } : void 0), revalidateSeconds, revalidatedPage.tags, expireSeconds));
181
184
  await Promise.all(writes);
182
185
  options.isrDebug?.("regen complete", options.cleanPathname);
183
186
  });
@@ -284,6 +284,7 @@ async function dispatchAppPageInner(options) {
284
284
  return {
285
285
  html: rendered.html,
286
286
  htmlRenderObservation: rendered.htmlRenderObservation,
287
+ linkHeader: rendered.linkHeader,
287
288
  rscData: rendered.rscData,
288
289
  rscRenderObservation: rendered.rscRenderObservation,
289
290
  tags: rendered.tags,
@@ -95,6 +95,7 @@ type AppPageNavigationParamInterceptOptions = {
95
95
  * {@link https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/create-metadata.tsx|create-metadata.tsx}.
96
96
  */
97
97
  declare function buildPageElements<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule>(options: BuildPageElementsOptions<TModule, TErrorModule>): Promise<AppElements>;
98
+ declare function resolveInterceptedSlotSegments(sourcePageSegments: readonly string[] | null | undefined, slotKey: string): readonly string[] | null;
98
99
  declare function resolveAppPageNavigationParams(route: AppPageNavigationParamRoute, routeParams: AppPageParams, routePath: string, opts?: AppPageNavigationParamInterceptOptions | null): AppPageParams;
99
100
  //#endregion
100
- export { AppPageBuildRoute, type AppPageErrorModule, AppPageInterceptOptions, AppPagePageRequest, type AppPageRouteWiringRoute, BuildPageElementsOptions, buildPageElements, resolveAppPageNavigationParams };
101
+ export { AppPageBuildRoute, type AppPageErrorModule, AppPageInterceptOptions, AppPagePageRequest, type AppPageRouteWiringRoute, BuildPageElementsOptions, buildPageElements, resolveAppPageNavigationParams, resolveInterceptedSlotSegments };
@@ -1,8 +1,8 @@
1
- import { matchRoutePattern } from "../routing/route-pattern.js";
2
1
  import { AppElementsWire } from "./app-elements-wire.js";
3
2
  import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
4
3
  import "./app-elements.js";
5
4
  import { createAppPageRenderIdentity } from "./app-page-render-identity.js";
5
+ import { matchRoutePattern } from "../routing/route-pattern.js";
6
6
  import { makeThenableParams } from "../shims/thenable-params.js";
7
7
  import { resolveAppPageBranchParams } from "./app-page-params.js";
8
8
  import { resolveActiveParallelRouteHeadInputs, resolveAppPageHead } from "./app-page-head.js";
@@ -51,6 +51,7 @@ async function buildPageElements(options) {
51
51
  const hasPageModule = !!pageModule;
52
52
  const renderIdentity = createAppPageRenderIdentity({
53
53
  displayPathname,
54
+ targetMatchedPathname: routePath,
54
55
  interceptionContext: opts?.interceptionContext ?? null,
55
56
  interceptSourceMatchedUrl: opts?.interceptSourceMatchedUrl ?? null,
56
57
  interceptSlotId: isSiblingIntercept ? null : opts?.interceptSlotId ?? null
@@ -181,18 +182,71 @@ function buildSlotOverrides(route, routeParams, routePath, opts) {
181
182
  layoutModules: opts.interceptLayouts || null,
182
183
  layoutSegments: opts.interceptLayoutSegments ?? null,
183
184
  pageModule: opts.interceptPage,
184
- params: opts.interceptParams || routeParams
185
+ params: opts.interceptParams || routeParams,
186
+ routeSegments: resolveInterceptedSlotSegments(opts.interceptSourcePageSegments, opts.interceptSlotKey)
185
187
  };
186
188
  const slotParamOverrides = resolveSlotParamOverrides(route, routePath);
187
189
  for (const [slotKey, params] of Object.entries(slotParamOverrides ?? {})) {
188
190
  const existing = overrides[slotKey];
189
191
  overrides[slotKey] = existing ? {
190
192
  ...existing,
191
- params
193
+ params: existing.params ?? params
192
194
  } : { params };
193
195
  }
194
196
  return Object.keys(overrides).length > 0 ? overrides : null;
195
197
  }
198
+ function resolveInterceptedSlotSegments(sourcePageSegments, slotKey) {
199
+ if (!sourcePageSegments) return null;
200
+ const markerTraversals = [
201
+ {
202
+ prefix: "(...)",
203
+ levels: Number.POSITIVE_INFINITY
204
+ },
205
+ {
206
+ prefix: "(..)(..)",
207
+ levels: 2
208
+ },
209
+ {
210
+ prefix: "(..)",
211
+ levels: 1
212
+ },
213
+ {
214
+ prefix: "(.)",
215
+ levels: 0
216
+ }
217
+ ];
218
+ const markerIndex = sourcePageSegments.findIndex((segment) => markerTraversals.some(({ prefix }) => segment.startsWith(prefix)));
219
+ if (markerIndex < 0) return null;
220
+ const slotPathSeparator = slotKey.indexOf("@");
221
+ const ownerSegments = (slotPathSeparator >= 0 ? slotKey.slice(slotPathSeparator + 1) : "").split("/").filter(Boolean);
222
+ let segmentStart = ownerSegments.length;
223
+ if (segmentStart === 0 || segmentStart > markerIndex || !ownerSegments.every((segment, index) => sourcePageSegments[index] === segment)) {
224
+ let slotName = null;
225
+ for (let index = ownerSegments.length - 1; index >= 0; index--) if (ownerSegments[index].startsWith("@")) {
226
+ slotName = ownerSegments[index];
227
+ break;
228
+ }
229
+ let slotIndex = -1;
230
+ if (slotName) {
231
+ for (let index = markerIndex - 1; index >= 0; index--) if (sourcePageSegments[index] === slotName) {
232
+ slotIndex = index;
233
+ break;
234
+ }
235
+ }
236
+ if (slotIndex < 0) return null;
237
+ segmentStart = slotIndex + 1;
238
+ }
239
+ const routeSegments = sourcePageSegments.slice(segmentStart, markerIndex).filter((segment) => !segment.startsWith("@") && !(segment.startsWith("(") && segment.endsWith(")")));
240
+ const markerSegment = sourcePageSegments[markerIndex];
241
+ const marker = markerTraversals.find(({ prefix }) => markerSegment.startsWith(prefix));
242
+ if (!marker) return null;
243
+ if (Number.isFinite(marker.levels)) routeSegments.splice(Math.max(0, routeSegments.length - marker.levels), marker.levels);
244
+ else routeSegments.length = 0;
245
+ const targetSegment = markerSegment.slice(marker.prefix.length);
246
+ if (targetSegment) routeSegments.push(targetSegment);
247
+ routeSegments.push(...sourcePageSegments.slice(markerIndex + 1).filter((segment) => !segment.startsWith("@") && !(segment.startsWith("(") && segment.endsWith(")"))));
248
+ return routeSegments;
249
+ }
196
250
  function resolveSlotParamOverrides(route, routePath) {
197
251
  const overrides = {};
198
252
  const slots = route.slots;
@@ -238,4 +292,4 @@ function collectParamNameSet(params) {
238
292
  return set;
239
293
  }
240
294
  //#endregion
241
- export { buildPageElements, resolveAppPageNavigationParams };
295
+ export { buildPageElements, resolveAppPageNavigationParams, resolveInterceptedSlotSegments };
@@ -12,6 +12,7 @@ const DEFAULT_SUBTREE_PROBE_MAX_NODES = 1e3;
12
12
  const REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
13
13
  const REACT_LAZY_TYPE = Symbol.for("react.lazy");
14
14
  const REACT_MEMO_TYPE = Symbol.for("react.memo");
15
+ const REACT_CLIENT_REFERENCE_TYPE = Symbol.for("react.client.reference");
15
16
  var AppPageSubtreeProbeLimitError = class extends Error {
16
17
  constructor(message) {
17
18
  super(message);
@@ -36,6 +37,9 @@ function isObjectLike(value) {
36
37
  function isUnknownFunction(value) {
37
38
  return typeof value === "function";
38
39
  }
40
+ function isReactClientReference(value) {
41
+ return isObjectLike(value) && Reflect.get(value, "$$typeof") === REACT_CLIENT_REFERENCE_TYPE;
42
+ }
39
43
  function readReactMemoType(value) {
40
44
  if (!isObjectLike(value) || Reflect.get(value, "$$typeof") !== REACT_MEMO_TYPE) return null;
41
45
  return { innerType: Reflect.get(value, "type") };
@@ -80,6 +84,7 @@ async function probeReactServerSubtree(node, options = {}) {
80
84
  };
81
85
  const renderElementType = async (type, props, depth, wrapperDepth = 0) => {
82
86
  if (wrapperDepth > maxDepth) throw new AppPageSubtreeProbeLimitError("App page layout subtree probe exceeded max depth");
87
+ if (isReactClientReference(type)) return false;
83
88
  if (isUnknownFunction(type)) {
84
89
  await visit(type(props), depth + 1);
85
90
  return true;
@@ -2,6 +2,7 @@ import { AppElementsInterception } from "./app-elements-wire.js";
2
2
  //#region src/server/app-page-render-identity.d.ts
3
3
  type AppPageRenderIdentityInput = {
4
4
  displayPathname: string;
5
+ targetMatchedPathname?: string;
5
6
  interceptionContext?: string | null;
6
7
  interceptSourceMatchedUrl?: string | null;
7
8
  interceptSlotId?: string | null;
@@ -13,7 +13,7 @@ function normalizeAppPageInterceptionProofPathname(pathname) {
13
13
  }
14
14
  function createAppPageRenderIdentity(input) {
15
15
  const interceptionContext = input.interceptionContext ?? null;
16
- const targetMatchedPathname = normalizeAppPageRenderMatchedPathname(input.displayPathname);
16
+ const targetMatchedPathname = normalizeAppPageRenderMatchedPathname(input.targetMatchedPathname ?? input.displayPathname);
17
17
  const sourceMatchedPathname = normalizeAppPageInterceptionProofPathname(input.interceptSourceMatchedUrl ?? null);
18
18
  const slotId = input.interceptSlotId ?? null;
19
19
  const matchedRoutePathname = sourceMatchedPathname ?? targetMatchedPathname;