vinext 0.2.1 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/README.md +67 -30
  2. package/dist/build/assets-ignore.js +1 -1
  3. package/dist/build/clean-output.js +1 -1
  4. package/dist/build/client-build-config.d.ts +13 -92
  5. package/dist/build/client-build-config.js +23 -99
  6. package/dist/build/css-url-assets.js +3 -3
  7. package/dist/build/google-fonts/fallback-metrics.js +1 -1
  8. package/dist/build/google-fonts/font-metadata.js +1 -1
  9. package/dist/build/inject-pregenerated-paths.js +1 -1
  10. package/dist/build/inline-css.js +2 -5
  11. package/dist/build/module-dependency-cache.d.ts +4 -0
  12. package/dist/build/module-dependency-cache.js +13 -0
  13. package/dist/build/next-client-runtime-manifests.js +1 -1
  14. package/dist/build/pages-client-assets-module.js +1 -1
  15. package/dist/build/precompress.js +7 -7
  16. package/dist/build/prerender-paths.d.ts +23 -0
  17. package/dist/build/prerender-paths.js +291 -0
  18. package/dist/build/prerender-server-pool.js +2 -2
  19. package/dist/build/prerender.d.ts +12 -1
  20. package/dist/build/prerender.js +2 -2
  21. package/dist/build/preview-credentials.d.ts +11 -0
  22. package/dist/build/preview-credentials.js +19 -0
  23. package/dist/build/report.d.ts +0 -3
  24. package/dist/build/report.js +2 -4
  25. package/dist/build/run-prerender.d.ts +2 -11
  26. package/dist/build/run-prerender.js +2 -10
  27. package/dist/build/server-manifest.js +1 -1
  28. package/dist/build/ssr-manifest.d.ts +4 -0
  29. package/dist/build/ssr-manifest.js +10 -6
  30. package/dist/build/standalone.js +37 -37
  31. package/dist/cache/cache-adapters-virtual.d.ts +7 -17
  32. package/dist/cache/cache-adapters-virtual.js +35 -2
  33. package/dist/check.d.ts +0 -16
  34. package/dist/check.js +17 -34
  35. package/dist/cli.js +86 -101
  36. package/dist/client/instrumentation-client-inject.js +1 -1
  37. package/dist/client/pages-router-link-navigation.js +1 -1
  38. package/dist/config/config-matchers.js +2 -2
  39. package/dist/config/dotenv.js +1 -1
  40. package/dist/config/next-config.d.ts +4 -1
  41. package/dist/config/next-config.js +82 -16
  42. package/dist/config/prerender.d.ts +12 -2
  43. package/dist/config/prerender.js +22 -12
  44. package/dist/config/server-external-packages.js +1 -1
  45. package/dist/config/tsconfig-paths.js +7 -3
  46. package/dist/{node_modules/.pnpm/am-i-vibing@0.5.0/node_modules → deps/.pnpm/am-i-vibing@0.5.0/deps}/am-i-vibing/dist/detector-1yx2Hoe0.js +2 -2
  47. package/dist/deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js +50 -0
  48. package/dist/entries/app-rsc-entry.js +12 -5
  49. package/dist/entries/app-rsc-manifest.js +4 -4
  50. package/dist/entries/pages-client-entry.js +6 -5
  51. package/dist/entries/pages-server-entry.js +15 -26
  52. package/dist/entries/runtime-entry-module.js +2 -2
  53. package/dist/image/image-adapters-virtual.js +1 -0
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.js +500 -338
  56. package/dist/init-cloudflare.js +13 -4
  57. package/dist/init-platform.d.ts +10 -1
  58. package/dist/init-platform.js +79 -13
  59. package/dist/init.d.ts +11 -3
  60. package/dist/init.js +133 -35
  61. package/dist/plugins/ast-utils.js +1 -1
  62. package/dist/plugins/dynamic-preload-metadata.js +7 -7
  63. package/dist/plugins/extensionless-dynamic-import.js +1 -1
  64. package/dist/plugins/fonts.js +3 -3
  65. package/dist/plugins/ignore-dynamic-requests.js +13 -8
  66. package/dist/plugins/import-meta-url.js +16 -17
  67. package/dist/plugins/middleware-export-validation.js +1 -1
  68. package/dist/plugins/middleware-server-only.js +2 -2
  69. package/dist/plugins/og-asset-ownership.js +7 -6
  70. package/dist/plugins/og-assets.js +4 -5
  71. package/dist/plugins/optimize-imports.js +12 -13
  72. package/dist/plugins/postcss.js +1 -1
  73. package/dist/plugins/remove-console.js +1 -1
  74. package/dist/plugins/require-context.js +1 -1
  75. package/dist/plugins/sass.js +2 -2
  76. package/dist/plugins/server-externals-manifest.js +1 -1
  77. package/dist/plugins/strip-server-exports.js +1 -1
  78. package/dist/plugins/styled-jsx.js +3 -2
  79. package/dist/plugins/typeof-window.js +1 -1
  80. package/dist/routing/app-route-graph.d.ts +3 -10
  81. package/dist/routing/app-route-graph.js +28 -76
  82. package/dist/routing/app-router.d.ts +0 -5
  83. package/dist/routing/app-router.js +0 -5
  84. package/dist/routing/file-matcher.d.ts +2 -6
  85. package/dist/routing/file-matcher.js +10 -11
  86. package/dist/routing/pages-router.js +3 -3
  87. package/dist/server/api-handler.js +41 -33
  88. package/dist/server/app-browser-entry.js +15 -11
  89. package/dist/server/app-browser-navigation-controller.js +9 -4
  90. package/dist/server/app-browser-server-action-client.js +1 -1
  91. package/dist/server/app-elements-wire.d.ts +2 -1
  92. package/dist/server/app-fallback-renderer.d.ts +8 -0
  93. package/dist/server/app-fallback-renderer.js +13 -1
  94. package/dist/server/app-layout-param-observation.d.ts +1 -1
  95. package/dist/server/app-page-boundary-render.d.ts +14 -1
  96. package/dist/server/app-page-boundary-render.js +107 -54
  97. package/dist/server/app-page-cache-render.d.ts +1 -1
  98. package/dist/server/app-page-dispatch.d.ts +2 -1
  99. package/dist/server/app-page-dispatch.js +29 -40
  100. package/dist/server/app-page-element-builder.d.ts +2 -1
  101. package/dist/server/app-page-element-builder.js +3 -3
  102. package/dist/server/app-page-execution.d.ts +3 -1
  103. package/dist/server/app-page-execution.js +31 -39
  104. package/dist/server/app-page-render.d.ts +1 -1
  105. package/dist/server/app-page-request.js +1 -1
  106. package/dist/server/app-page-route-wiring.js +4 -4
  107. package/dist/server/app-route-handler-execution.d.ts +1 -1
  108. package/dist/server/app-route-tree-prefetch.js +1 -1
  109. package/dist/server/app-rsc-cache-busting.js +1 -2
  110. package/dist/server/app-rsc-handler.js +2 -2
  111. package/dist/server/app-rsc-redirect-flight.d.ts +66 -0
  112. package/dist/server/app-rsc-redirect-flight.js +71 -0
  113. package/dist/server/app-rsc-render-mode.d.ts +2 -5
  114. package/dist/server/app-rsc-render-mode.js +2 -12
  115. package/dist/server/app-segment-config.js +2 -2
  116. package/dist/server/app-server-action-execution.js +3 -3
  117. package/dist/server/app-ssr-entry.js +4 -1
  118. package/dist/server/app-ssr-stream.d.ts +2 -1
  119. package/dist/server/app-ssr-stream.js +4 -1
  120. package/dist/server/cache-proof.js +1 -1
  121. package/dist/server/default-global-error-module.d.ts +3 -1
  122. package/dist/server/default-not-found-module.d.ts +3 -1
  123. package/dist/server/dev-lockfile.js +2 -3
  124. package/dist/server/dev-module-runner.js +1 -1
  125. package/dist/server/dev-origin-check.d.ts +2 -2
  126. package/dist/server/dev-origin-check.js +2 -2
  127. package/dist/server/dev-route-files.js +1 -1
  128. package/dist/server/dev-server.js +256 -164
  129. package/dist/server/dev-stack-sourcemap.js +4 -3
  130. package/dist/server/headers.d.ts +3 -1
  131. package/dist/server/headers.js +3 -1
  132. package/dist/server/image-optimization.d.ts +12 -1
  133. package/dist/server/image-optimization.js +15 -3
  134. package/dist/server/implicit-tags.js +2 -1
  135. package/dist/server/instrumentation.js +2 -2
  136. package/dist/server/isr-cache.d.ts +12 -4
  137. package/dist/server/isr-cache.js +3 -3
  138. package/dist/server/metadata-routes.js +4 -4
  139. package/dist/server/middleware-response-headers.js +1 -1
  140. package/dist/server/middleware-runtime.js +1 -1
  141. package/dist/server/middleware.d.ts +1 -0
  142. package/dist/server/middleware.js +1 -1
  143. package/dist/server/navigation-planner.d.ts +2 -0
  144. package/dist/server/navigation-planner.js +8 -2
  145. package/dist/server/next-error-digest.d.ts +9 -24
  146. package/dist/server/next-error-digest.js +23 -15
  147. package/dist/server/operation-token.js +1 -1
  148. package/dist/server/pages-body-parser-config.js +2 -1
  149. package/dist/server/pages-dev-hydration.d.ts +14 -0
  150. package/dist/server/pages-dev-hydration.js +83 -0
  151. package/dist/server/pages-dev-module-url.js +2 -2
  152. package/dist/server/pages-node-compat.d.ts +13 -6
  153. package/dist/server/pages-node-compat.js +44 -45
  154. package/dist/server/pages-page-data.d.ts +1 -1
  155. package/dist/server/pages-page-data.js +5 -4
  156. package/dist/server/pages-page-handler.js +61 -29
  157. package/dist/server/pages-page-response.d.ts +2 -2
  158. package/dist/server/pages-page-response.js +2 -2
  159. package/dist/server/pages-preview.d.ts +26 -0
  160. package/dist/server/pages-preview.js +165 -0
  161. package/dist/server/pages-readiness.d.ts +2 -1
  162. package/dist/server/pages-readiness.js +4 -2
  163. package/dist/server/pages-request-pipeline.js +1 -1
  164. package/dist/server/prerender-manifest.d.ts +15 -1
  165. package/dist/server/prerender-manifest.js +29 -1
  166. package/dist/server/prod-server.d.ts +6 -5
  167. package/dist/server/prod-server.js +15 -15
  168. package/dist/server/request-pipeline.js +1 -1
  169. package/dist/server/seed-cache.js +1 -1
  170. package/dist/server/socket-error-backstop.js +1 -1
  171. package/dist/server/static-file-cache.js +8 -9
  172. package/dist/server/worker-utils.js +1 -1
  173. package/dist/shims/cache.d.ts +1 -1
  174. package/dist/shims/cache.js +2 -1
  175. package/dist/shims/cdn-cache.d.ts +2 -7
  176. package/dist/shims/cdn-cache.js +2 -14
  177. package/dist/shims/constants.js +1 -1
  178. package/dist/shims/error-boundary.d.ts +4 -4
  179. package/dist/shims/error.d.ts +21 -5
  180. package/dist/shims/error.js +52 -14
  181. package/dist/shims/fetch-cache.js +1 -1
  182. package/dist/shims/font-utils.js +1 -1
  183. package/dist/shims/form.d.ts +1 -1
  184. package/dist/shims/head.js +3 -3
  185. package/dist/shims/headers.js +6 -4
  186. package/dist/shims/internal/pages-data-fetch-dedup.js +5 -3
  187. package/dist/shims/layout-segment-context.d.ts +1 -1
  188. package/dist/shims/link.d.ts +3 -1
  189. package/dist/shims/link.js +6 -3
  190. package/dist/shims/navigation-errors.d.ts +2 -1
  191. package/dist/shims/navigation-errors.js +9 -13
  192. package/dist/shims/navigation.d.ts +1 -1
  193. package/dist/shims/navigation.js +5 -5
  194. package/dist/shims/request-state-types.d.ts +2 -2
  195. package/dist/shims/router-state.d.ts +1 -0
  196. package/dist/shims/router.d.ts +2 -1
  197. package/dist/shims/router.js +18 -11
  198. package/dist/shims/script.js +1 -1
  199. package/dist/shims/server.js +2 -2
  200. package/dist/shims/slot.d.ts +1 -1
  201. package/dist/shims/thenable-params.js +1 -1
  202. package/dist/shims/unified-request-context.d.ts +1 -1
  203. package/dist/typegen.js +9 -11
  204. package/dist/utils/client-build-manifest.js +2 -2
  205. package/dist/utils/client-entry-manifest.js +1 -1
  206. package/dist/utils/client-runtime-metadata.js +1 -1
  207. package/dist/utils/commonjs-loader.js +3 -3
  208. package/dist/utils/mdx-scan.js +1 -1
  209. package/dist/utils/path.d.ts +1 -13
  210. package/dist/utils/path.js +1 -15
  211. package/dist/utils/plugin-options.d.ts +4 -0
  212. package/dist/utils/plugin-options.js +8 -0
  213. package/dist/utils/project.d.ts +1 -7
  214. package/dist/utils/project.js +2 -8
  215. package/dist/utils/public-routes.js +2 -2
  216. package/dist/utils/react-version.js +1 -1
  217. package/dist/utils/redirect-digest.d.ts +9 -0
  218. package/dist/utils/redirect-digest.js +25 -0
  219. package/dist/utils/vinext-root.js +1 -1
  220. package/dist/utils/vite-version.d.ts +6 -13
  221. package/dist/utils/vite-version.js +68 -34
  222. package/package.json +33 -14
  223. package/dist/cloudflare/index.d.ts +0 -3
  224. package/dist/cloudflare/index.js +0 -3
  225. package/dist/packages/cloudflare/src/cache/cdn-adapter.runtime.js +0 -102
  226. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +0 -126
  227. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +0 -442
  228. package/dist/packages/cloudflare/src/deploy-config.js +0 -150
  229. package/dist/packages/cloudflare/src/deploy-help.js +0 -55
  230. package/dist/packages/cloudflare/src/deploy.js +0 -272
  231. package/dist/packages/cloudflare/src/tpr.d.ts +0 -45
  232. package/dist/packages/cloudflare/src/tpr.js +0 -582
  233. package/dist/packages/cloudflare/src/utils/cache-control-metadata.js +0 -20
  234. /package/dist/build/google-fonts/{fallback-metrics-data.js → fallback-metrics-data.json.js} +0 -0
  235. /package/dist/build/google-fonts/{font-data.js → font-data.json.js} +0 -0
  236. /package/dist/{node_modules/.pnpm/process-ancestry@0.1.0/node_modules → deps/.pnpm/process-ancestry@0.1.0/deps}/process-ancestry/dist/index.js +0 -0
@@ -1,6 +1,7 @@
1
1
  import { ArtifactCompatibilityEnvelope } from "./artifact-compatibility.js";
2
2
  import { CacheEntryReuseProof, RenderObservation } from "./cache-proof.js";
3
3
  import { ClientReuseManifestSkipDisposition } from "./client-reuse-manifest.js";
4
+ import { compareStrings } from "../utils/compare.js";
4
5
  import { ReactNode } from "react";
5
6
 
6
7
  //#region src/server/app-elements-wire.d.ts
@@ -42,7 +43,7 @@ type AppElementsInterception = Readonly<{
42
43
  targetMatchedUrl: string;
43
44
  targetRouteId: string;
44
45
  }>;
45
- declare const compareAppElementsSlotIds: (left: string, right: string) => number;
46
+ declare const compareAppElementsSlotIds: typeof compareStrings;
46
47
  declare function normalizeAppElementsSlotBindings(slotBindings: readonly AppElementsSlotBinding[], options?: {
47
48
  layoutIds?: readonly string[];
48
49
  }): readonly AppElementsSlotBinding[];
@@ -32,6 +32,7 @@ type AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> =
32
32
  clearRequestContext: () => void;
33
33
  createRscOnErrorHandler: (request: Request, pathname: string, routePath: string) => AppPageBoundaryOnError;
34
34
  fontProviders: AppFallbackRendererFontProviders;
35
+ getAndClearPendingCookies?: () => string[];
35
36
  getNavigationContext: () => NavigationContext | null;
36
37
  globalErrorModule?: TModule | null; /** Whether experimental.globalNotFound is enabled for route-miss 404s. */
37
38
  globalNotFoundEnabled?: boolean;
@@ -57,6 +58,13 @@ type AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> =
57
58
  metadataRoutes: MetadataFileRoute[]; /** Configured next.config `basePath`, threaded into file-based metadata href emission. */
58
59
  basePath?: string; /** Configured next.config `trailingSlash`, threaded into canonical URL rendering. */
59
60
  trailingSlash?: boolean;
61
+ /**
62
+ * Serialized next.config `htmlLimitedBots` regexp source. Used to decide, per
63
+ * request user-agent, whether a `generateMetadata()` redirect thrown from a
64
+ * fallback boundary should stream (200) or block (307) — matching the
65
+ * matched-page dispatch path via `shouldServeStreamingMetadata`.
66
+ */
67
+ htmlLimitedBots?: string;
60
68
  resolveChildSegments: (routeSegments: readonly string[], treePosition: number, params: AppPageParams) => string[];
61
69
  rootBoundaries: AppFallbackRendererRootBoundaries<TModule>;
62
70
  rscRenderer: (element: ReactNode | AppElements, options: {
@@ -2,13 +2,14 @@ import { renderAppPageErrorBoundary, renderAppPageHttpAccessFallback } from "./a
2
2
  import { DEFAULT_GLOBAL_ERROR_MODULE } from "./default-global-error-module.js";
3
3
  import { DEFAULT_GLOBAL_NOT_FOUND_MODULE } from "./default-global-not-found-module.js";
4
4
  import { DEFAULT_NOT_FOUND_MODULE } from "./default-not-found-module.js";
5
+ import { shouldServeStreamingMetadata } from "./streaming-metadata.js";
5
6
  //#region src/server/app-fallback-renderer.ts
6
7
  const EMPTY_MW_CTX = {
7
8
  headers: null,
8
9
  status: null
9
10
  };
10
11
  function createAppFallbackRenderer(options) {
11
- const { applyFileBasedMetadata, basePath = "", clearRequestContext, createRscOnErrorHandler: buildRscOnErrorHandler, fontProviders, getNavigationContext, globalErrorModule, globalNotFoundEnabled = false, loadGlobalNotFoundModule, makeThenableParams, metadataRoutes, resolveChildSegments, rootBoundaries, rscRenderer, sanitizer, ssrLoader, trailingSlash } = options;
12
+ const { applyFileBasedMetadata, basePath = "", htmlLimitedBots, clearRequestContext, createRscOnErrorHandler: buildRscOnErrorHandler, fontProviders, getAndClearPendingCookies, getNavigationContext, globalErrorModule, globalNotFoundEnabled = false, loadGlobalNotFoundModule, makeThenableParams, metadataRoutes, resolveChildSegments, rootBoundaries, rscRenderer, sanitizer, ssrLoader, trailingSlash } = options;
12
13
  const { rootForbiddenModule, rootLayouts, rootNotFoundModule, rootUnauthorizedModule } = rootBoundaries;
13
14
  const effectiveGlobalErrorModule = globalErrorModule ?? DEFAULT_GLOBAL_ERROR_MODULE;
14
15
  const effectiveRootNotFoundModule = rootNotFoundModule ?? DEFAULT_NOT_FOUND_MODULE;
@@ -20,12 +21,15 @@ function createAppFallbackRenderer(options) {
20
21
  }
21
22
  return {
22
23
  async renderHttpAccessFallback(route, statusCode, isRscRequest, request, opts, scriptNonce, middlewareContext, callContext) {
24
+ const serveStreamingMetadata = shouldServeStreamingMetadata(request.headers.get("user-agent") ?? "", htmlLimitedBots);
23
25
  const useGlobalNotFound = statusCode === 404 && globalNotFoundEnabled && !route && !opts?.boundaryComponent;
24
26
  if (useGlobalNotFound && loadGlobalNotFoundModule) {
25
27
  const globalNotFoundModule = await resolveGlobalNotFoundModule();
26
28
  const globalNotFoundComponent = globalNotFoundModule?.default ?? null;
27
29
  if (globalNotFoundComponent) return renderAppPageHttpAccessFallback({
28
30
  applyFileBasedMetadata,
31
+ basePath,
32
+ trailingSlash,
29
33
  boundaryComponent: globalNotFoundComponent,
30
34
  boundaryModule: globalNotFoundModule ?? null,
31
35
  buildFontLinkHeader: fontProviders.buildFontLinkHeader,
@@ -36,6 +40,7 @@ function createAppFallbackRenderer(options) {
36
40
  getFontLinks: fontProviders.getFontLinks,
37
41
  getFontPreloads: fontProviders.getFontPreloads,
38
42
  getFontStyles: fontProviders.getFontStyles,
43
+ getAndClearPendingCookies,
39
44
  getNavigationContext,
40
45
  globalErrorModule: effectiveGlobalErrorModule,
41
46
  isEdgeRuntime: callContext?.isEdgeRuntime,
@@ -46,6 +51,7 @@ function createAppFallbackRenderer(options) {
46
51
  matchedParams: opts?.matchedParams ?? {},
47
52
  middlewareContext: middlewareContext ?? EMPTY_MW_CTX,
48
53
  metadataRoutes,
54
+ request,
49
55
  requestUrl: request.url,
50
56
  resolveChildSegments,
51
57
  rootForbiddenModule: null,
@@ -55,6 +61,7 @@ function createAppFallbackRenderer(options) {
55
61
  route: null,
56
62
  renderToReadableStream: rscRenderer,
57
63
  scriptNonce,
64
+ serveStreamingMetadata,
58
65
  skipLayoutWrapping: true,
59
66
  statusCode
60
67
  });
@@ -74,6 +81,7 @@ function createAppFallbackRenderer(options) {
74
81
  getFontLinks: fontProviders.getFontLinks,
75
82
  getFontPreloads: fontProviders.getFontPreloads,
76
83
  getFontStyles: fontProviders.getFontStyles,
84
+ getAndClearPendingCookies,
77
85
  getNavigationContext,
78
86
  globalErrorModule: effectiveGlobalErrorModule,
79
87
  isEdgeRuntime: callContext?.isEdgeRuntime,
@@ -84,6 +92,7 @@ function createAppFallbackRenderer(options) {
84
92
  matchedParams: opts?.matchedParams ?? route?.params ?? {},
85
93
  middlewareContext: middlewareContext ?? EMPTY_MW_CTX,
86
94
  metadataRoutes,
95
+ request,
87
96
  requestUrl: request.url,
88
97
  resolveChildSegments,
89
98
  rootForbiddenModule,
@@ -93,6 +102,7 @@ function createAppFallbackRenderer(options) {
93
102
  route: useGlobalNotFound ? null : route,
94
103
  renderToReadableStream: rscRenderer,
95
104
  scriptNonce,
105
+ serveStreamingMetadata,
96
106
  skipLayoutWrapping: useGlobalNotFound,
97
107
  sourcePageSegments: callContext?.sourcePageSegments,
98
108
  statusCode
@@ -116,6 +126,7 @@ function createAppFallbackRenderer(options) {
116
126
  getFontLinks: fontProviders.getFontLinks,
117
127
  getFontPreloads: fontProviders.getFontPreloads,
118
128
  getFontStyles: fontProviders.getFontStyles,
129
+ getAndClearPendingCookies,
119
130
  getNavigationContext,
120
131
  globalErrorModule: effectiveGlobalErrorModule,
121
132
  isEdgeRuntime: callContext?.isEdgeRuntime,
@@ -125,6 +136,7 @@ function createAppFallbackRenderer(options) {
125
136
  matchedParams: matchedParams ?? route?.params ?? {},
126
137
  middlewareContext: middlewareContext ?? EMPTY_MW_CTX,
127
138
  metadataRoutes,
139
+ request,
128
140
  requestUrl: request.url,
129
141
  resolveChildSegments,
130
142
  rootLayouts,
@@ -1,7 +1,7 @@
1
- import { UnstableCacheObservation } from "../shims/cache-request-state.js";
2
1
  import { RenderRequestApiKind } from "./cache-proof.js";
3
2
  import { ClientReuseManifestRejectionCode, ClientReuseManifestTraceFields } from "./client-reuse-manifest.js";
4
3
  import { ThenableParamsObserver } from "../shims/thenable-params.js";
4
+ import { UnstableCacheObservation } from "../shims/cache-request-state.js";
5
5
 
6
6
  //#region src/server/app-layout-param-observation.d.ts
7
7
  type AppLayoutParamAccessObservation = Readonly<{
@@ -32,6 +32,7 @@ type AppPageBoundaryRenderCommonOptions<TModule extends AppPageModule = AppPageM
32
32
  buildFontLinkHeader: (preloads: readonly AppPageFontPreload[] | null | undefined) => string;
33
33
  clearRequestContext: () => void;
34
34
  createRscOnErrorHandler: (pathname: string, routePath: string) => AppPageBoundaryOnError;
35
+ getAndClearPendingCookies?: () => string[];
35
36
  getFontLinks: () => string[];
36
37
  getFontPreloads: () => AppPageFontPreload[];
37
38
  getFontStyles: () => string[];
@@ -42,12 +43,24 @@ type AppPageBoundaryRenderCommonOptions<TModule extends AppPageModule = AppPageM
42
43
  loadSsrHandler: () => Promise<AppPageSsrHandler>;
43
44
  makeThenableParams: (params: AppPageParams) => unknown;
44
45
  middlewareContext: AppPageMiddlewareContext;
45
- metadataRoutes: MetadataFileRoute[]; /** Configured next.config `basePath`, threaded into file-based metadata href emission. */
46
+ metadataRoutes: MetadataFileRoute[];
47
+ /**
48
+ * Whether metadata-origin redirects should ride as a 200 streaming response
49
+ * (HTML meta-refresh / RSC flight) rather than a blocking 307. Mirrors the
50
+ * matched-page dispatch decision `shouldServeStreamingMetadata(userAgent,
51
+ * htmlLimitedBots)`: html-limited bots get the blocking 307, so a
52
+ * `generateMetadata()` redirect thrown from a fallback boundary matches the
53
+ * matched-page path instead of always defaulting to streaming. Undefined
54
+ * means "not computed" and is treated as streaming, preserving prior behavior
55
+ * for callers that never hit metadata redirects.
56
+ */
57
+ serveStreamingMetadata?: boolean; /** Configured next.config `basePath`, threaded into file-based metadata href emission. */
46
58
  basePath?: string; /** Configured next.config `trailingSlash`, threaded into canonical URL rendering. */
47
59
  trailingSlash?: boolean;
48
60
  renderToReadableStream: (element: ReactNode | AppElements, options: {
49
61
  onError: AppPageBoundaryOnError;
50
62
  }) => ReadableStream<Uint8Array>;
63
+ request: Request;
51
64
  requestUrl: string;
52
65
  resolveChildSegments: (routeSegments: readonly string[], treePosition: number, params: AppPageParams) => string[];
53
66
  rootLayouts: readonly (TModule | null | undefined)[];
@@ -6,12 +6,13 @@ import { isNavigationSignalError } from "../utils/navigation-signal.js";
6
6
  import { ErrorBoundary, GlobalErrorBoundary, SerializedErrorBoundary } from "../shims/error-boundary.js";
7
7
  import { LayoutSegmentProvider } from "../shims/layout-segment-context.js";
8
8
  import { MetadataHead, ViewportHead } from "../shims/metadata.js";
9
- import { resolveAppPageSpecialError } from "./app-page-execution.js";
9
+ import { buildRscRedirectFlightStream } from "./app-rsc-redirect-flight.js";
10
+ import { bufferAppPageBinaryStream, buildAppPageSpecialErrorResponse, resolveAppPageSpecialError } from "./app-page-execution.js";
10
11
  import { resolveAppPageHead } from "./app-page-head.js";
11
12
  import { createAppPageLayoutEntries, createAppPageSourcePage } from "./app-page-route-wiring.js";
12
13
  import { buildClientHookErrorMessage } from "../shims/client-hook-error.js";
13
14
  import { renderAppPageBoundaryResponse, resolveAppPageErrorBoundary, resolveAppPageHttpAccessBoundaryModule, wrapAppPageBoundaryElement } from "./app-page-boundary.js";
14
- import { createAppPageFontData, renderAppPageHtmlResponse } from "./app-page-stream.js";
15
+ import { createAppPageFontData, createAppPageRscErrorTracker, renderAppPageHtmlResponse } from "./app-page-stream.js";
15
16
  import { Fragment, createElement } from "react";
16
17
  //#region src/server/app-page-boundary-render.ts
17
18
  const DEFAULT_GLOBAL_ERROR_COMPONENT = DefaultGlobalError;
@@ -89,52 +90,95 @@ function createAppPageBoundaryRscPayload(options) {
89
90
  [routeId]: options.element
90
91
  };
91
92
  }
92
- async function renderAppPageBoundaryElementResponse(options) {
93
- const requestUrl = new URL(options.requestUrl);
94
- const pathname = requestUrl.pathname;
95
- return renderAppPageBoundaryResponse({
96
- async createHtmlResponse(rscStream, responseStatus) {
97
- const fontData = createAppPageFontData({
98
- getLinks: options.getFontLinks,
99
- getPreloads: options.getFontPreloads,
100
- getStyles: options.getFontStyles
101
- });
102
- const ssrHandler = await options.loadSsrHandler();
103
- return renderAppPageHtmlResponse({
104
- clearRequestContext: options.clearRequestContext,
105
- fontData,
106
- fontLinkHeader: options.buildFontLinkHeader(fontData.preloads),
107
- isEdgeRuntime: options.isEdgeRuntime,
108
- middlewareHeaders: options.middlewareContext.headers,
109
- navigationContext: options.getNavigationContext() ?? {
110
- pathname,
111
- searchParams: requestUrl.searchParams,
112
- params: options.navigationParams ?? options.route?.params ?? {}
113
- },
114
- rscStream,
115
- scriptNonce: options.scriptNonce,
116
- ssrHandler,
117
- status: responseStatus,
118
- initialDevServerError: options.initialDevServerError
119
- });
120
- },
121
- createRscOnErrorHandler() {
122
- return options.createRscOnErrorHandler(pathname, options.routePattern ?? pathname);
123
- },
124
- element: createAppPageBoundaryRscPayload({
125
- element: options.element,
126
- layoutModules: options.layoutModules,
127
- pathname,
128
- route: options.route,
129
- sourcePageSegments: options.sourcePageSegments
93
+ function renderBoundarySpecialErrorResponse(options, specialError) {
94
+ return buildAppPageSpecialErrorResponse({
95
+ basePath: options.basePath,
96
+ buildRscRedirectFlightStream: (rscOptions) => buildRscRedirectFlightStream({
97
+ renderToReadableStream: options.renderToReadableStream,
98
+ digest: rscOptions.digest
130
99
  }),
100
+ clearRequestContext: options.clearRequestContext,
101
+ getAndClearPendingCookies: options.getAndClearPendingCookies,
131
102
  isEdgeRuntime: options.isEdgeRuntime,
132
103
  isRscRequest: options.isRscRequest,
133
- middlewareHeaders: options.middlewareContext.headers,
134
- renderToReadableStream: options.renderToReadableStream,
135
- status: options.status
104
+ middlewareContext: options.middlewareContext,
105
+ serveStreamingMetadata: options.serveStreamingMetadata,
106
+ request: options.request,
107
+ specialError
136
108
  });
137
109
  }
110
+ async function renderAppPageBoundaryElementResponse(options) {
111
+ const requestUrl = new URL(options.requestUrl);
112
+ const pathname = requestUrl.pathname;
113
+ const payload = createAppPageBoundaryRscPayload({
114
+ element: options.element,
115
+ layoutModules: options.layoutModules,
116
+ pathname,
117
+ route: options.route,
118
+ sourcePageSegments: options.sourcePageSegments
119
+ });
120
+ const rscErrorTracker = createAppPageRscErrorTracker(options.createRscOnErrorHandler(pathname, options.routePattern ?? pathname));
121
+ const resolveCapturedSpecialError = (error) => resolveAppPageSpecialError(error) ?? resolveAppPageSpecialError(rscErrorTracker.getCapturedSpecialError());
122
+ const renderSpecialErrorResponse = (specialError) => renderBoundarySpecialErrorResponse(options, specialError);
123
+ const handleSpecialErrors = options.handleSpecialErrors === true;
124
+ let response;
125
+ try {
126
+ response = await renderAppPageBoundaryResponse({
127
+ async createHtmlResponse(rscStream, responseStatus) {
128
+ const fontData = createAppPageFontData({
129
+ getLinks: options.getFontLinks,
130
+ getPreloads: options.getFontPreloads,
131
+ getStyles: options.getFontStyles
132
+ });
133
+ const ssrHandler = await options.loadSsrHandler();
134
+ return renderAppPageHtmlResponse({
135
+ clearRequestContext: options.clearRequestContext,
136
+ fontData,
137
+ fontLinkHeader: options.buildFontLinkHeader(fontData.preloads),
138
+ isEdgeRuntime: options.isEdgeRuntime,
139
+ middlewareHeaders: options.middlewareContext.headers,
140
+ navigationContext: options.getNavigationContext() ?? {
141
+ pathname,
142
+ searchParams: requestUrl.searchParams,
143
+ params: options.navigationParams ?? options.route?.params ?? {}
144
+ },
145
+ rscStream,
146
+ scriptNonce: options.scriptNonce,
147
+ ssrHandler,
148
+ status: responseStatus,
149
+ initialDevServerError: options.initialDevServerError
150
+ });
151
+ },
152
+ createRscOnErrorHandler() {
153
+ return rscErrorTracker.onRenderError;
154
+ },
155
+ element: payload,
156
+ isEdgeRuntime: options.isEdgeRuntime,
157
+ isRscRequest: options.isRscRequest,
158
+ middlewareHeaders: options.middlewareContext.headers,
159
+ renderToReadableStream: options.renderToReadableStream,
160
+ status: options.status
161
+ });
162
+ } catch (error) {
163
+ const specialError = handleSpecialErrors ? resolveCapturedSpecialError(error) : null;
164
+ if (specialError !== null) return renderSpecialErrorResponse(specialError);
165
+ throw error;
166
+ }
167
+ if (!handleSpecialErrors) return response;
168
+ if (options.isRscRequest && response.body) {
169
+ const bufferedStream = await bufferAppPageBinaryStream(response.body);
170
+ response = new Response(bufferedStream, {
171
+ status: response.status,
172
+ headers: response.headers
173
+ });
174
+ }
175
+ const specialError = resolveCapturedSpecialError();
176
+ if (!specialError) return response;
177
+ if (response.body) try {
178
+ await response.body.cancel();
179
+ } catch {}
180
+ return renderSpecialErrorResponse(specialError);
181
+ }
138
182
  async function renderAppPageHttpAccessFallback(options) {
139
183
  const resolvedBoundaryModule = resolveAppPageHttpAccessBoundaryModule({
140
184
  rootForbiddenModule: options.rootForbiddenModule,
@@ -151,17 +195,25 @@ async function renderAppPageHttpAccessFallback(options) {
151
195
  const layoutModules = options.layoutModules ?? options.route?.layouts ?? options.rootLayouts;
152
196
  const pathname = new URL(options.requestUrl).pathname;
153
197
  const routeSegments = resolveHttpAccessFallbackHeadRouteSegments(options.route, layoutModules);
154
- const { metadata, viewport } = await resolveAppPageHead({
155
- applyFileBasedMetadata: options.applyFileBasedMetadata,
156
- basePath: options.basePath ?? "",
157
- layoutModules,
158
- layoutTreePositions: resolveHttpAccessFallbackHeadLayoutTreePositions(options.route, layoutModules),
159
- metadataRoutes: options.metadataRoutes,
160
- pageModule: boundaryModule,
161
- params: options.matchedParams,
162
- routePath: options.route?.pattern ?? pathname,
163
- routeSegments
164
- });
198
+ let head;
199
+ try {
200
+ head = await resolveAppPageHead({
201
+ applyFileBasedMetadata: options.applyFileBasedMetadata,
202
+ basePath: options.basePath ?? "",
203
+ layoutModules,
204
+ layoutTreePositions: resolveHttpAccessFallbackHeadLayoutTreePositions(options.route, layoutModules),
205
+ metadataRoutes: options.metadataRoutes,
206
+ pageModule: boundaryModule,
207
+ params: options.matchedParams,
208
+ routePath: options.route?.pattern ?? pathname,
209
+ routeSegments
210
+ });
211
+ } catch (error) {
212
+ const specialError = resolveAppPageSpecialError(error);
213
+ if (specialError) return renderBoundarySpecialErrorResponse(options, specialError);
214
+ throw error;
215
+ }
216
+ const { metadata, viewport } = head;
165
217
  const headElements = [createElement("meta", {
166
218
  charSet: "utf-8",
167
219
  key: "charset"
@@ -197,6 +249,7 @@ async function renderAppPageHttpAccessFallback(options) {
197
249
  return renderAppPageBoundaryElementResponse({
198
250
  ...options,
199
251
  element,
252
+ handleSpecialErrors: true,
200
253
  layoutModules: skipLayoutWrapping ? [] : layoutModules,
201
254
  navigationParams: options.matchedParams,
202
255
  route: skipLayoutWrapping ? null : options.route,
@@ -1,8 +1,8 @@
1
- import { CacheControlMetadata } from "../shims/cache-handler.js";
2
1
  import { NavigationContext } from "../shims/navigation-context-state.js";
3
2
  import { AppPageFontPreload } from "./app-page-execution.js";
4
3
  import { RootParams } from "../shims/root-params.js";
5
4
  import { AppPageSsrHandler } from "./app-page-stream.js";
5
+ import { CacheControlMetadata } from "../shims/cache-handler.js";
6
6
  import { createAppPageRenderObservation } from "./app-page-render-observation.js";
7
7
  import { ReactNode } from "react";
8
8
 
@@ -1,5 +1,4 @@
1
1
  import { ClassificationReason } from "../build/layout-classification-types.js";
2
- import { CachedAppPageValue } from "../shims/cache-handler.js";
3
2
  import { ClientReuseManifestParseResult } from "./client-reuse-manifest.js";
4
3
  import { AppOutgoingElements } from "./app-elements-wire.js";
5
4
  import { NavigationContext } from "../shims/navigation-context-state.js";
@@ -8,6 +7,7 @@ import { AppPageMiddlewareContext } from "./app-page-response.js";
8
7
  import { RootParams } from "../shims/root-params.js";
9
8
  import { AppPageSsrHandler } from "./app-page-stream.js";
10
9
  import { AppLayoutParamAccessTracker } from "./app-layout-param-observation.js";
10
+ import { CachedAppPageValue } from "../shims/cache-handler.js";
11
11
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
12
12
  import { ISRCacheEntry } from "./isr-cache.js";
13
13
  import { PprFallbackShellState } from "../shims/ppr-fallback-shell.js";
@@ -112,6 +112,7 @@ type DispatchAppPageOptions<TRoute extends AppPageDispatchRoute> = {
112
112
  buildPageElement: (route: TRoute, params: AppPageParams, opts: AppPageDispatchInterceptOptions | undefined, searchParams: URLSearchParams, layoutParamAccess?: AppLayoutParamAccessTracker, options?: {
113
113
  observeMetadataSearchParamsAccess?: boolean;
114
114
  observePageSearchParamsAccess?: boolean;
115
+ serveStreamingMetadata?: boolean;
115
116
  }) => Promise<AppPageElement>;
116
117
  clientReuseManifest?: ClientReuseManifestParseResult;
117
118
  cleanPathname: string;
@@ -2,7 +2,7 @@ import { createRequestContext, runWithRequestContext } from "../shims/unified-re
2
2
  import { getRequestExecutionContext } from "../shims/request-context.js";
3
3
  import "../utils/protocol-headers.js";
4
4
  import { AppElementsWire } from "./app-elements-wire.js";
5
- import { APP_RSC_RENDER_MODE_PREFETCH_DYNAMIC_SHELL, shouldSuppressLoadingBoundaries } from "./app-rsc-render-mode.js";
5
+ import { APP_RSC_RENDER_MODE_PREFETCH_DYNAMIC_SHELL } from "./app-rsc-render-mode.js";
6
6
  import "./app-elements.js";
7
7
  import { consumeDynamicUsage, consumeInvalidDynamicUsageError, getAndClearPendingCookies, getDraftModeCookieHeader, getHeadersContext, isDraftModeRequest, markDynamicUsage, peekDynamicUsage, peekRenderRequestApiUsage, setHeadersContext } from "../shims/headers.js";
8
8
  import { _consumeRequestScopedCacheLife, _peekRequestScopedCacheLife } from "../shims/cache-request-state.js";
@@ -10,18 +10,18 @@ import { ensureFetchPatch, getCollectedFetchTags, peekDynamicFetchObservations,
10
10
  import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
11
11
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
12
12
  import "./app-page-response.js";
13
+ import { buildRscRedirectFlightStream } from "./app-rsc-redirect-flight.js";
13
14
  import { buildAppPageSpecialErrorResponse, probeAppPageThrownError, resolveAppPageSpecialError } from "./app-page-execution.js";
14
15
  import { createAppPageTreePath } from "./app-page-route-wiring.js";
15
16
  import { consumeAppPageRenderObservationState, discardAppPageRenderState } from "./app-page-render-observation.js";
16
17
  import { resolveAppPageParentHttpAccessBoundary, resolveAppPageParentHttpAccessBoundaryModule } from "./app-page-boundary.js";
18
+ import { shouldServeStreamingMetadata } from "./streaming-metadata.js";
17
19
  import { createAppLayoutParamAccessTracker, isAppLayoutObservationUnsafeForStaticReuse } from "./app-layout-param-observation.js";
18
20
  import { buildAppPageTags } from "./implicit-tags.js";
19
21
  import { resolveAppPageMethodResponse } from "./app-page-method.js";
20
- import { shouldServeStreamingMetadata } from "./streaming-metadata.js";
21
22
  import { resolveAppPageNavigationParams } from "./app-page-element-builder.js";
22
23
  import { buildAppPageElement, resolveAppPageIntercept, resolveAppPageInterceptionRerenderTarget, validateAppPageDynamicParams } from "./app-page-request.js";
23
24
  import { renderAppPageLifecycle } from "./app-page-render.js";
24
- import React from "react";
25
25
  //#region src/server/app-page-dispatch.ts
26
26
  function resolveAppPageRouteBoundaryModule(route, statusCode) {
27
27
  if (statusCode === 403) return route.forbidden ?? null;
@@ -160,6 +160,9 @@ async function dispatchAppPageInner(options) {
160
160
  const isForceStatic = dynamicConfig === "force-static";
161
161
  const isDynamicError = dynamicConfig === "error";
162
162
  const isForceDynamic = dynamicConfig === "force-dynamic";
163
+ const isPrerender = process.env.VINEXT_PRERENDER === "1";
164
+ const serveStreamingMetadata = shouldServeStreamingMetadata(options.request.headers.get("user-agent") ?? "", options.htmlLimitedBots);
165
+ const placeGeneratedMetadataInBody = (!isPrerender || options.pprFallbackShell !== void 0) && serveStreamingMetadata;
163
166
  const isPrefetchDynamicShell = options.renderMode === APP_RSC_RENDER_MODE_PREFETCH_DYNAMIC_SHELL;
164
167
  const isDraftMode = isDraftModeRequest(options.request, options.draftModeSecret);
165
168
  const requestHeadersContext = getHeadersContext();
@@ -167,7 +170,7 @@ async function dispatchAppPageInner(options) {
167
170
  const hasRequestSearchParams = !shouldUseEmptySearchParams && hasSearchParams(options.searchParams);
168
171
  const pageSearchParams = shouldUseEmptySearchParams ? new URLSearchParams() : options.searchParams;
169
172
  const layoutParamAccess = createAppLayoutParamAccessTracker();
170
- const hasActiveLoadingBoundary = shouldSuppressLoadingBoundaries(options.renderMode ?? "navigation") ? false : Boolean(route.loading?.default);
173
+ const hasActiveLoadingBoundary = Boolean(route.loading?.default);
171
174
  setCurrentFetchSoftTags(buildAppPageTags(options.cleanPathname, [], route.routeSegments));
172
175
  setCurrentFetchCacheMode(options.fetchCache ?? null);
173
176
  setCurrentForceDynamicFetchDefault(isForceDynamic);
@@ -269,7 +272,8 @@ async function dispatchAppPageInner(options) {
269
272
  const { renderAppPageCacheArtifacts } = await import("./app-page-cache-render.js");
270
273
  const revalidatedElement = await options.buildPageElement(revalidationTarget.route, revalidationTarget.params, revalidationTarget.interceptOpts, new URLSearchParams(), void 0, {
271
274
  observeMetadataSearchParamsAccess: revalidationDynamicConfig !== "force-static",
272
- observePageSearchParamsAccess: revalidationDynamicConfig !== "force-static"
275
+ observePageSearchParamsAccess: revalidationDynamicConfig !== "force-static",
276
+ serveStreamingMetadata: false
273
277
  });
274
278
  const revalidatedOnError = options.createRscOnErrorHandler(options.cleanPathname, revalidationTarget.route.pattern);
275
279
  const rendered = await renderAppPageCacheArtifacts({
@@ -345,7 +349,8 @@ async function dispatchAppPageInner(options) {
345
349
  setCurrentForceDynamicFetchDefault(sourceDynamicConfig === "force-dynamic");
346
350
  return options.buildPageElement(interceptRoute, interceptParams, interceptOpts, interceptSearchParams, interceptLayoutParamAccess, {
347
351
  observeMetadataSearchParamsAccess: sourceDynamicConfig !== "force-static",
348
- observePageSearchParamsAccess: sourceDynamicConfig !== "force-static"
352
+ observePageSearchParamsAccess: sourceDynamicConfig !== "force-static",
353
+ serveStreamingMetadata: placeGeneratedMetadataInBody
349
354
  });
350
355
  },
351
356
  cleanPathname: options.cleanPathname,
@@ -397,11 +402,12 @@ async function dispatchAppPageInner(options) {
397
402
  if (options.actionFailed) throw options.actionError;
398
403
  return options.buildPageElement(route, options.params, interceptResult.interceptOpts, pageSearchParams, layoutParamAccess, {
399
404
  observeMetadataSearchParamsAccess: !isForceStatic,
400
- observePageSearchParamsAccess: !isForceStatic
405
+ observePageSearchParamsAccess: !isForceStatic,
406
+ serveStreamingMetadata: placeGeneratedMetadataInBody
401
407
  });
402
408
  },
403
409
  async probePageSpecialError() {
404
- if (!shouldSuppressLoadingBoundaries(options.renderMode ?? "navigation") && route.loading?.default) return null;
410
+ if (route.loading?.default) return null;
405
411
  return resolveAppPageSpecialError(await probeAppPageThrownError({
406
412
  probePage: () => options.probePage(pageSearchParams),
407
413
  runWithSuppressedHookWarning(probe) {
@@ -413,7 +419,7 @@ async function dispatchAppPageInner(options) {
413
419
  return options.renderErrorBoundaryPage(buildError);
414
420
  },
415
421
  renderSpecialError(specialError) {
416
- return renderPageSpecialError(options, specialError);
422
+ return renderPageSpecialError(options, specialError, serveStreamingMetadata);
417
423
  },
418
424
  resolveSpecialError: resolveAppPageSpecialError
419
425
  });
@@ -441,7 +447,6 @@ async function dispatchAppPageInner(options) {
441
447
  const activeFallbackShellState = options.pprRuntime?.getState() ?? null;
442
448
  const pprFallbackShellSignal = activeFallbackShellState?.abortController.signal;
443
449
  const pprFallbackShellReactSignal = activeFallbackShellState?.reactAbortController.signal;
444
- const isPrerender = process.env.VINEXT_PRERENDER === "1";
445
450
  const isSpeculativePrerender = isPrerender && options.request.headers.get("x-vinext-prerender-speculative") === "1";
446
451
  return renderAppPageLifecycle({
447
452
  basePath: options.basePath,
@@ -550,10 +555,10 @@ async function dispatchAppPageInner(options) {
550
555
  return options.renderErrorBoundaryPage(renderError, errorOrigin);
551
556
  },
552
557
  renderLayoutSpecialError(specialError, layoutIndex) {
553
- return renderLayoutSpecialError(options, specialError, layoutIndex);
558
+ return renderLayoutSpecialError(options, specialError, layoutIndex, serveStreamingMetadata);
554
559
  },
555
560
  renderPageSpecialError(specialError) {
556
- return renderPageSpecialError(options, specialError);
561
+ return renderPageSpecialError(options, specialError, serveStreamingMetadata);
557
562
  },
558
563
  renderToReadableStream: options.renderToReadableStream,
559
564
  hasCustomGlobalError: options.hasCustomGlobalError,
@@ -568,35 +573,16 @@ async function dispatchAppPageInner(options) {
568
573
  }
569
574
  });
570
575
  }
571
- /**
572
- * Builds an RSC flight payload that encodes a redirect as a React error chunk.
573
- * We render a tiny element that immediately throws an `Error` whose `digest`
574
- * is the canonical `NEXT_REDIRECT;...` string. `renderToReadableStream`'s
575
- * `onError` returns that digest, react-server-dom-webpack serializes the
576
- * error into the stream, and the client's `RedirectErrorBoundary` decodes it
577
- * via `getURLFromRedirectError` / `getRedirectTypeFromError`.
578
- *
579
- * The thrown error's digest matches Next.js's well-known router error format,
580
- * so neither vinext's RSC error handler nor Next.js's reporter logs it as a
581
- * "real" server error. Mirrors `app-render.tsx generateDynamicFlightRenderResult`
582
- * where a redirect thrown during RSC rendering propagates through
583
- * `renderToFlightStream`'s `onError` callback into the flight payload.
584
- */
585
- function buildRscRedirectFlightStream(options, digest) {
586
- const throwingElement = React.createElement(function NextRedirectFlightThrower() {
587
- const err = /* @__PURE__ */ new Error("NEXT_REDIRECT");
588
- err.digest = digest;
589
- throw err;
590
- });
591
- return options.renderToReadableStream(throwingElement, { onError: () => digest });
592
- }
593
- async function renderLayoutSpecialError(options, specialError, layoutIndex) {
576
+ async function renderLayoutSpecialError(options, specialError, layoutIndex, serveStreamingMetadata) {
594
577
  return buildAppPageSpecialErrorResponse({
595
578
  basePath: options.basePath,
596
- buildRscRedirectFlightStream: (rscOptions) => buildRscRedirectFlightStream(options, rscOptions.digest),
579
+ buildRscRedirectFlightStream: (rscOptions) => buildRscRedirectFlightStream({
580
+ renderToReadableStream: options.renderToReadableStream,
581
+ digest: rscOptions.digest
582
+ }),
597
583
  clearRequestContext: options.clearRequestContext,
598
584
  getAndClearPendingCookies,
599
- serveStreamingMetadata: shouldServeStreamingMetadata(options.request.headers.get("user-agent") ?? "", options.htmlLimitedBots),
585
+ serveStreamingMetadata,
600
586
  isEdgeRuntime: options.isEdgeRuntime,
601
587
  isRscRequest: options.isRscRequest,
602
588
  middlewareContext: options.middlewareContext,
@@ -625,13 +611,16 @@ async function renderLayoutSpecialError(options, specialError, layoutIndex) {
625
611
  specialError
626
612
  });
627
613
  }
628
- async function renderPageSpecialError(options, specialError) {
614
+ async function renderPageSpecialError(options, specialError, serveStreamingMetadata) {
629
615
  return buildAppPageSpecialErrorResponse({
630
616
  basePath: options.basePath,
631
- buildRscRedirectFlightStream: (rscOptions) => buildRscRedirectFlightStream(options, rscOptions.digest),
617
+ buildRscRedirectFlightStream: (rscOptions) => buildRscRedirectFlightStream({
618
+ renderToReadableStream: options.renderToReadableStream,
619
+ digest: rscOptions.digest
620
+ }),
632
621
  clearRequestContext: options.clearRequestContext,
633
622
  getAndClearPendingCookies,
634
- serveStreamingMetadata: shouldServeStreamingMetadata(options.request.headers.get("user-agent") ?? "", options.htmlLimitedBots),
623
+ serveStreamingMetadata,
635
624
  isEdgeRuntime: options.isEdgeRuntime,
636
625
  isRscRequest: options.isRscRequest,
637
626
  middlewareContext: options.middlewareContext,
@@ -37,7 +37,8 @@ type AppPagePageRequest<TModule extends AppPageModule = AppPageModule> = {
37
37
  mountedSlotsHeader: string | null; /** Semantic RSC payload mode for this page render. */
38
38
  renderMode?: AppRscRenderMode; /** Observe page `searchParams` access for cache-safety classification. */
39
39
  observePageSearchParamsAccess?: boolean; /** Observe page metadata `searchParams` access for cache-safety classification. */
40
- observeMetadataSearchParamsAccess?: boolean;
40
+ observeMetadataSearchParamsAccess?: boolean; /** Whether generated metadata may stream into the response body. */
41
+ serveStreamingMetadata?: boolean;
41
42
  };
42
43
  type BuildPageElementsOptions<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = {
43
44
  applyFileBasedMetadata?: ApplyAppPageFileBasedMetadata;
@@ -9,8 +9,8 @@ import { resolveActiveParallelRouteHeadInputs, resolveAppPageHead } from "./app-
9
9
  import { createAppPageSearchParamsObserver, makeObservedAppPageSearchParamsThenable } from "./app-page-search-params-observation.js";
10
10
  import { buildAppPageElements, createAppPageSourcePage, createAppPageTreePath } from "./app-page-route-wiring.js";
11
11
  import { DEFAULT_GLOBAL_ERROR_MODULE } from "./default-global-error-module.js";
12
- import "./app-rsc-route-matching.js";
13
12
  import { shouldServeStreamingMetadata } from "./streaming-metadata.js";
13
+ import "./app-rsc-route-matching.js";
14
14
  import { createElement } from "react";
15
15
  //#region src/server/app-page-element-builder.ts
16
16
  function resolveInterceptLayoutParams(branchSegments, layoutSegments, params) {
@@ -41,7 +41,7 @@ function isReactOwnedPageComponent(component) {
41
41
  async function buildPageElements(options) {
42
42
  const { route, params, routePath, displayPathname = routePath, pageRequest, globalErrorModule, rootNotFoundModule, rootForbiddenModule, rootUnauthorizedModule, metadataRoutes } = options;
43
43
  const slotParamOverrides = resolveSlotParamOverrides(route, routePath);
44
- const { opts, searchParams, isRscRequest, mountedSlotsHeader, renderMode = APP_RSC_RENDER_MODE_NAVIGATION, observeMetadataSearchParamsAccess = false, observePageSearchParamsAccess = false } = pageRequest;
44
+ const { opts, searchParams, isRscRequest, mountedSlotsHeader, renderMode = APP_RSC_RENDER_MODE_NAVIGATION, observeMetadataSearchParamsAccess = false, observePageSearchParamsAccess = false, serveStreamingMetadata } = pageRequest;
45
45
  const pageModule = route.page;
46
46
  const isSiblingIntercept = opts?.interceptSlotKey === "__vinext_page_intercept" && !!opts?.interceptPage;
47
47
  const effectivePageModule = isSiblingIntercept ? opts.interceptPage : pageModule;
@@ -127,7 +127,7 @@ async function buildPageElements(options) {
127
127
  const pageSearchParamsThenable = searchParams ? makeThenableParams(pageSearchParams) : void 0;
128
128
  const mountedSlotIds = mountedSlotsHeader ? new Set(mountedSlotsHeader.split(" ")) : null;
129
129
  const slotOverrides = buildSlotOverrides(route, params, routePath, opts);
130
- const metadataPlacement = hasDynamicMetadata && shouldServeStreamingMetadata(pageRequest.request.headers.get("user-agent") ?? "", options.htmlLimitedBots) ? "body" : "head";
130
+ const metadataPlacement = hasDynamicMetadata && (serveStreamingMetadata ?? shouldServeStreamingMetadata(pageRequest.request.headers.get("user-agent") ?? "", options.htmlLimitedBots)) ? "body" : "head";
131
131
  let siblingInterceptElement = isSiblingIntercept && EffectivePageComponent ? createPageElement(EffectivePageComponent, pageProps) : null;
132
132
  if (isSiblingIntercept && siblingInterceptElement !== null && opts?.interceptLayouts?.length) for (let i = opts.interceptLayouts.length - 1; i >= 0; i--) {
133
133
  const LayoutComponent = opts.interceptLayouts[i]?.default;
@@ -6,6 +6,7 @@ type AppPageSpecialError = {
6
6
  kind: "redirect";
7
7
  location: string;
8
8
  statusCode: number;
9
+ type?: "push" | "replace";
9
10
  fromMetadata?: boolean;
10
11
  } | {
11
12
  kind: "http-access-fallback";
@@ -132,7 +133,8 @@ declare function probeAppPageLayouts(options: ProbeAppPageLayoutsOptions): Promi
132
133
  declare function probeAppPageComponent(options: ProbeAppPageComponentOptions): Promise<Response | null>;
133
134
  declare function probeAppPageThrownError(options: ProbeAppPageThrownErrorOptions): Promise<unknown>;
134
135
  declare function readAppPageBinaryStream(stream: ReadableStream<Uint8Array>): Promise<ArrayBuffer>;
136
+ declare function bufferAppPageBinaryStream(stream: ReadableStream<Uint8Array>): Promise<ReadableStream<Uint8Array>>;
135
137
  declare function teeAppPageRscStreamForCapture(stream: ReadableStream<Uint8Array>, shouldCapture: boolean): AppPageRscStreamCapture;
136
138
  declare function buildAppPageFontLinkHeader(preloads: readonly AppPageFontPreload[] | null | undefined): string;
137
139
  //#endregion
138
- export { AppPageFontPreload, AppPageSpecialError, LayoutClassificationOptions, type LayoutFlags, buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse, probeAppPageComponent, probeAppPageLayouts, probeAppPageThrownError, readAppPageBinaryStream, resolveAppPageSpecialError, tagAppPageMetadataError, teeAppPageRscStreamForCapture };
140
+ export { AppPageFontPreload, AppPageSpecialError, LayoutClassificationOptions, type LayoutFlags, bufferAppPageBinaryStream, buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse, probeAppPageComponent, probeAppPageLayouts, probeAppPageThrownError, readAppPageBinaryStream, resolveAppPageSpecialError, tagAppPageMetadataError, teeAppPageRscStreamForCapture };