vinext 1.0.1 → 1.1.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 (159) hide show
  1. package/README.md +72 -1
  2. package/dist/build/app-page-static-eligibility.d.ts +15 -0
  3. package/dist/build/app-page-static-eligibility.js +82 -0
  4. package/dist/build/prerender-paths.js +3 -78
  5. package/dist/build/prerender.js +33 -8
  6. package/dist/build/server-manifest.d.ts +7 -0
  7. package/dist/build/server-manifest.js +11 -1
  8. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  9. package/dist/cache/cache-adapters-virtual.js +3 -3
  10. package/dist/client/navigation-runtime.d.ts +11 -0
  11. package/dist/client/navigation-runtime.js +1 -1
  12. package/dist/config/config-matchers.js +3 -1
  13. package/dist/config/next-config.d.ts +7 -0
  14. package/dist/config/next-config.js +18 -3
  15. package/dist/entries/app-rsc-entry.d.ts +2 -0
  16. package/dist/entries/app-rsc-entry.js +64 -43
  17. package/dist/index.d.ts +4 -1
  18. package/dist/index.js +59 -30
  19. package/dist/plugins/cache-flight-codec.d.ts +19 -0
  20. package/dist/plugins/cache-flight-codec.js +68 -0
  21. package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
  22. package/dist/plugins/commonjs-esm-facade.js +80 -0
  23. package/dist/plugins/fonts.d.ts +8 -11
  24. package/dist/plugins/fonts.js +71 -34
  25. package/dist/routing/file-matcher.d.ts +1 -0
  26. package/dist/routing/file-matcher.js +1 -1
  27. package/dist/server/api-handler.js +17 -7
  28. package/dist/server/app-browser-entry.js +11 -7
  29. package/dist/server/app-browser-history-controller.d.ts +2 -0
  30. package/dist/server/app-browser-history-controller.js +8 -0
  31. package/dist/server/app-browser-interception-context.js +26 -2
  32. package/dist/server/app-browser-navigation-controller.d.ts +7 -1
  33. package/dist/server/app-browser-navigation-controller.js +93 -10
  34. package/dist/server/app-fallback-renderer.d.ts +2 -0
  35. package/dist/server/app-fallback-renderer.js +1 -0
  36. package/dist/server/app-history-state.d.ts +3 -0
  37. package/dist/server/app-history-state.js +7 -0
  38. package/dist/server/app-middleware.js +2 -1
  39. package/dist/server/app-optimistic-routing.js +1 -1
  40. package/dist/server/app-page-boundary-render.d.ts +5 -0
  41. package/dist/server/app-page-boundary-render.js +6 -4
  42. package/dist/server/app-page-cache-finalizer.d.ts +11 -0
  43. package/dist/server/app-page-cache-finalizer.js +41 -7
  44. package/dist/server/app-page-cache-render.d.ts +18 -0
  45. package/dist/server/app-page-cache-render.js +63 -43
  46. package/dist/server/app-page-cache.d.ts +5 -0
  47. package/dist/server/app-page-cache.js +27 -7
  48. package/dist/server/app-page-dispatch.d.ts +9 -1
  49. package/dist/server/app-page-dispatch.js +47 -30
  50. package/dist/server/app-page-element-builder.d.ts +7 -0
  51. package/dist/server/app-page-element-builder.js +27 -42
  52. package/dist/server/app-page-execution.d.ts +15 -12
  53. package/dist/server/app-page-execution.js +30 -40
  54. package/dist/server/app-page-head.d.ts +5 -0
  55. package/dist/server/app-page-head.js +9 -3
  56. package/dist/server/app-page-probe.d.ts +0 -109
  57. package/dist/server/app-page-probe.js +3 -136
  58. package/dist/server/app-page-render-identity.d.ts +0 -1
  59. package/dist/server/app-page-render-identity.js +1 -1
  60. package/dist/server/app-page-render-observation.js +2 -2
  61. package/dist/server/app-page-render.d.ts +13 -5
  62. package/dist/server/app-page-render.js +219 -119
  63. package/dist/server/app-page-request.d.ts +0 -1
  64. package/dist/server/app-page-request.js +1 -2
  65. package/dist/server/app-page-response.d.ts +4 -3
  66. package/dist/server/app-page-response.js +4 -3
  67. package/dist/server/app-page-route-wiring.d.ts +17 -5
  68. package/dist/server/app-page-route-wiring.js +55 -33
  69. package/dist/server/app-page-rsc-render-status.d.ts +48 -0
  70. package/dist/server/app-page-rsc-render-status.js +54 -0
  71. package/dist/server/app-page-stream.d.ts +15 -8
  72. package/dist/server/app-page-stream.js +6 -0
  73. package/dist/server/app-request-stage-dispatch.js +1 -1
  74. package/dist/server/app-request-stage-independent-entry.js +4 -4
  75. package/dist/server/app-response-header-provenance.d.ts +2 -0
  76. package/dist/server/app-response-header-provenance.js +10 -4
  77. package/dist/server/app-route-handler-dispatch.js +1 -1
  78. package/dist/server/app-router-entry.js +3 -3
  79. package/dist/server/app-rsc-handler.d.ts +4 -0
  80. package/dist/server/app-rsc-handler.js +212 -96
  81. package/dist/server/app-rsc-request-normalization.d.ts +3 -1
  82. package/dist/server/app-rsc-request-normalization.js +4 -2
  83. package/dist/server/app-rsc-response-stage.d.ts +1 -1
  84. package/dist/server/app-rsc-response-stage.js +6 -4
  85. package/dist/server/app-rsc-route-matching.d.ts +9 -1
  86. package/dist/server/app-rsc-route-matching.js +36 -19
  87. package/dist/server/app-server-action-execution.js +1 -1
  88. package/dist/server/app-ssr-entry.d.ts +3 -2
  89. package/dist/server/app-ssr-entry.js +6 -4
  90. package/dist/server/app-ssr-stream.d.ts +6 -1
  91. package/dist/server/app-ssr-stream.js +34 -15
  92. package/dist/server/app-worker-stages.d.ts +2 -2
  93. package/dist/server/app-worker-stages.js +3 -3
  94. package/dist/server/cacheability-request.js +9 -5
  95. package/dist/server/config-headers.js +2 -0
  96. package/dist/server/dev-server.js +12 -2
  97. package/dist/server/headers.d.ts +14 -0
  98. package/dist/server/headers.js +15 -1
  99. package/dist/server/image-optimization.d.ts +16 -0
  100. package/dist/server/image-optimization.js +26 -6
  101. package/dist/server/isr-cache.js +2 -2
  102. package/dist/server/middleware-matcher-pattern.js +8 -4
  103. package/dist/server/middleware-response-headers.js +2 -0
  104. package/dist/server/multi-stage.d.ts +8 -0
  105. package/dist/server/navigation-planner.js +1 -1
  106. package/dist/server/node-response-signal.d.ts +16 -0
  107. package/dist/server/node-response-signal.js +26 -0
  108. package/dist/server/open-redirect.d.ts +47 -0
  109. package/dist/server/open-redirect.js +74 -1
  110. package/dist/server/pages-page-handler.js +21 -4
  111. package/dist/server/pages-request-stage-entry.js +8 -4
  112. package/dist/server/prod-server.d.ts +8 -7
  113. package/dist/server/prod-server.js +153 -69
  114. package/dist/server/request-pipeline.d.ts +20 -8
  115. package/dist/server/request-pipeline.js +21 -7
  116. package/dist/server/response-compression.d.ts +43 -0
  117. package/dist/server/response-compression.js +111 -0
  118. package/dist/server/seed-cache.js +10 -7
  119. package/dist/server/socket-error-backstop.d.ts +14 -0
  120. package/dist/server/socket-error-backstop.js +27 -4
  121. package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
  122. package/dist/shims/cache-adapter-instantiate.js +118 -0
  123. package/dist/shims/cache-callable-runtime.d.ts +2 -40
  124. package/dist/shims/cache-callable-runtime.js +50 -168
  125. package/dist/shims/cache-flight-arguments.d.ts +22 -0
  126. package/dist/shims/cache-flight-arguments.js +56 -0
  127. package/dist/shims/cache-request-state.d.ts +15 -0
  128. package/dist/shims/cache-request-state.js +31 -8
  129. package/dist/shims/cache-runtime.d.ts +6 -19
  130. package/dist/shims/cache-runtime.js +165 -225
  131. package/dist/shims/cache.d.ts +2 -2
  132. package/dist/shims/cache.js +5 -4
  133. package/dist/shims/cacheability-classification.d.ts +7 -0
  134. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  135. package/dist/shims/error-boundary-navigation.js +19 -1
  136. package/dist/shims/error-boundary.d.ts +10 -10
  137. package/dist/shims/error-boundary.js +26 -7
  138. package/dist/shims/fetch-cache.d.ts +6 -0
  139. package/dist/shims/fetch-cache.js +19 -1
  140. package/dist/shims/form.js +1 -1
  141. package/dist/shims/headers.d.ts +13 -0
  142. package/dist/shims/headers.js +47 -4
  143. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
  144. package/dist/shims/internal/app-page-props-cache-key.js +24 -15
  145. package/dist/shims/internal/headers-state.d.ts +0 -1
  146. package/dist/shims/metadata.js +3 -3
  147. package/dist/shims/navigation-errors.d.ts +6 -0
  148. package/dist/shims/navigation-errors.js +11 -1
  149. package/dist/shims/navigation.d.ts +7 -0
  150. package/dist/shims/navigation.js +14 -4
  151. package/dist/shims/request-context.d.ts +9 -0
  152. package/dist/shims/thenable-params.d.ts +4 -0
  153. package/dist/shims/thenable-params.js +25 -3
  154. package/dist/shims/unified-request-context.js +1 -0
  155. package/dist/utils/mdx-scan.d.ts +5 -0
  156. package/dist/utils/mdx-scan.js +8 -1
  157. package/dist/utils/regex-safety.d.ts +10 -0
  158. package/dist/utils/regex-safety.js +70 -18
  159. package/package.json +3 -3
@@ -1,32 +1,36 @@
1
1
  import { preserveFullyBufferedBodyMetadata } from "./fully-buffered-response.js";
2
2
  import { bindRequestContext } from "../shims/unified-request-context.js";
3
- import { VINEXT_RSC_COMPLETION_METADATA_HEADER } from "./headers.js";
3
+ import { getRequestExecutionContext } from "../shims/request-context.js";
4
+ import { VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_RSC_COMPLETION_METADATA_HEADER } from "./headers.js";
4
5
  import { internalServerErrorResponse } from "./http-error-responses.js";
5
6
  import { isRouteCacheabilityEvaluation, recordRouteCacheabilityClientTraceMetadataMarker } from "../shims/cacheability-classification.js";
6
7
  import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders, isCdnResponsePolicyHeader } from "./cache-control.js";
7
- import { createArtifactCompatibilityEnvelope, createArtifactCompatibilityGraphVersion } from "./artifact-compatibility.js";
8
- import { AppElementsWire, isAppElementsRecord } from "./app-elements-wire.js";
9
- import "./app-elements.js";
10
8
  import { resolveClientStaleTimeSeconds } from "../utils/cache-control-metadata.js";
11
- import { isRenderDynamicLatched } from "../shims/internal/headers-state.js";
12
- import { peekDynamicUsage } from "../shims/headers.js";
9
+ import { isRenderDynamicLatched, markDynamicUsage } from "../shims/internal/headers-state.js";
10
+ import { peekDynamicUsage, runWithDetachedDynamicUsage } from "../shims/headers.js";
13
11
  import { runWithFetchDedupe } from "../shims/fetch-cache.js";
12
+ import { setCacheStateHeaders } from "./cache-headers.js";
14
13
  import { deferUntilStreamConsumed } from "./defer-until-stream-consumed.js";
14
+ import { createArtifactCompatibilityEnvelope, createArtifactCompatibilityGraphVersion } from "./artifact-compatibility.js";
15
+ import { AppElementsWire, isAppElementsRecord } from "./app-elements-wire.js";
16
+ import "./app-elements.js";
15
17
  import { isBailoutToCSRError } from "../shims/navigation-errors.js";
16
18
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
17
- import { copyLinkHeaderProvenance } from "./app-response-header-provenance.js";
18
- import { applyEdgeRuntimeHeader, buildAppPageHtmlResponse, buildAppPageRscResponse, resolveAppPageHtmlResponsePolicy, resolveAppPageRscResponsePolicy, resolveUncacheableCacheControl } from "./app-page-response.js";
19
- import { buildAppPageFontLinkHeader, readAppPageBinaryStream, resolveAppPageSpecialError, teeAppPageRscStreamForCapture } from "./app-page-execution.js";
19
+ import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
20
+ import { copyLinkHeaderProvenance, getFrameworkLinkHeader } from "./app-response-header-provenance.js";
21
+ import { applyClientStaleTimeHeader, applyEdgeRuntimeHeader, buildAppPageHtmlResponse, buildAppPageRscResponse, resolveAppPageHtmlResponsePolicy, resolveAppPageRscResponsePolicy, resolveUncacheableCacheControl } from "./app-page-response.js";
22
+ import { buildAppPageFontLinkHeader, readAppPageBinaryStream, resolveAppPageShellSpecialError, resolveAppPageSpecialError, resolveAppPageSpecialErrorStoredHeaders, teeAppPageRscStreamForCapture } from "./app-page-execution.js";
20
23
  import { probeAppPageBeforeRender } from "./app-page-probe.js";
21
24
  import { hasDigest } from "./app-rsc-errors.js";
22
25
  import { recordAppPageRenderError, traceAppPageRender } from "./app-page-tracing.js";
23
26
  import { traceResponseStartWithCompletion } from "./response-start-tracing.js";
24
27
  import { DEFAULT_CACHE_VARIANT_BUDGET, buildCacheVariantWithRouteBudget, buildRenderObservation, buildRenderRequestApiObservations, createStaticLayoutArtifactReuseDecision } from "./cache-proof.js";
25
28
  import { createAppPageHtmlOutputScope, createAppPageRenderObservation, createAppPageRscOutputScope } from "./app-page-render-observation.js";
29
+ import { buildAppPageLinkHeader, createAppPageFontData, createAppPageRscErrorTracker, createAppPageSsrErrorHandler, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery } from "./app-page-stream.js";
30
+ import { createAppPageRscRenderStatusResolver } from "./app-page-rsc-render-status.js";
26
31
  import { finalizeAppPageCacheabilityEvaluationResponse, finalizeAppPageHtmlCacheResponse, finalizeAppPageRscCacheResponse } from "./app-page-cache-finalizer.js";
27
32
  import { createStaticLayoutClientReuseArtifactCompatibility, createStaticLayoutClientReusePayloadHash, createStaticLayoutClientReuseRouteId } from "./static-layout-client-reuse-proof.js";
28
33
  import { appendRscCompletionMetadata } from "./rsc-completion-metadata.js";
29
- import { buildAppPageLinkHeader, createAppPageFontData, createAppPageRscErrorTracker, createAppPageSsrErrorHandler, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery } from "./app-page-stream.js";
30
34
  import { getStaticLayoutObservationSkipRejection } from "./app-layout-param-observation.js";
31
35
  import { createClientReuseSkipTransportPlan, crossCheckClientReuseManifestEntryWithCache } from "./skip-cache-proof.js";
32
36
  //#region src/server/app-page-render.ts
@@ -70,15 +74,32 @@ function applyRequestCacheLife(options) {
70
74
  * its response policy, such as error boundaries and special errors, get the
71
75
  * same header as the normal render.
72
76
  */
73
- function applyIneligibleRouteCachePolicy(response, options) {
77
+ function applyIneligibleRouteCachePolicy(response, options, probeCacheLife = null) {
74
78
  const cacheControl = resolveEarlyResponseCacheControl(options);
75
- if (!cacheControl) return response;
79
+ const isRscRedirect = options.isRscRequest && response.headers.has("X-Vinext-Rsc-Redirect");
80
+ if (!cacheControl) {
81
+ const staleTimeSeconds = isRscRedirect ? resolveClientStaleTimeSeconds(probeCacheLife) : void 0;
82
+ if (staleTimeSeconds === void 0) return response;
83
+ const stamped = cloneForStamping(response);
84
+ applyClientStaleTimeHeader(stamped.headers, staleTimeSeconds);
85
+ return stamped;
86
+ }
87
+ const dynamicStaleTimeSeconds = isRscRedirect ? options.dynamicStaleTimeSeconds ?? resolveConfiguredDynamicStaleTimeSeconds() : void 0;
76
88
  const middlewarePolicy = [...options.middlewareContext.headers ?? []].filter(([name, value]) => isCdnResponsePolicyHeader(name) && response.headers.get(name) === value);
77
- if (middlewarePolicy.some(([name]) => name === "cache-control")) return response;
89
+ const keepsMiddlewareCacheControl = middlewarePolicy.some(([name]) => name === "cache-control");
90
+ if (keepsMiddlewareCacheControl && dynamicStaleTimeSeconds === void 0) return response;
91
+ const stamped = cloneForStamping(response);
92
+ if (!keepsMiddlewareCacheControl) {
93
+ applyCdnResponseHeaders(stamped.headers, { cacheControl });
94
+ for (const [name, value] of middlewarePolicy) stamped.headers.set(name, value);
95
+ }
96
+ if (dynamicStaleTimeSeconds !== void 0) stamped.headers.set(VINEXT_DYNAMIC_STALE_TIME_HEADER, String(dynamicStaleTimeSeconds));
97
+ return stamped;
98
+ }
99
+ /** Some early responses have immutable headers, so stamp a copy. */
100
+ function cloneForStamping(response) {
78
101
  const stamped = preserveFullyBufferedBodyMetadata(response, new Response(response.body, response));
79
102
  copyLinkHeaderProvenance(response.headers, stamped.headers);
80
- applyCdnResponseHeaders(stamped.headers, { cacheControl });
81
- for (const [name, value] of middlewarePolicy) stamped.headers.set(name, value);
82
103
  return stamped;
83
104
  }
84
105
  /** The known-dynamic branches of the RSC and HTML response policies, in their order. */
@@ -368,32 +389,26 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
368
389
  }
369
390
  return dynamicUsageObserved;
370
391
  };
371
- const configuredProbePageBeforeRender = options.probePageBeforeRender ?? options.isRscRequest;
372
- const probePageBeforeRender = options.isRscRequest || configuredProbePageBeforeRender && !(options.peekDynamicUsage?.() ?? false);
373
- const preRenderResult = await probeAppPageBeforeRender({
374
- hasLoadingBoundary: options.hasLoadingBoundary,
375
- probePageBeforeRender,
392
+ const probeOutcome = await runWithDetachedDynamicUsage(() => probeAppPageBeforeRender({
376
393
  skipProbes: options.pprFallbackShellSignal !== void 0,
377
394
  layoutCount: options.layoutCount,
378
395
  probeLayoutAt(layoutIndex) {
379
396
  return options.probeLayoutAt(layoutIndex);
380
397
  },
381
- probePage() {
382
- return options.probePage();
383
- },
384
398
  renderLayoutSpecialError(specialError, layoutIndex) {
385
399
  return options.renderLayoutSpecialError(specialError, layoutIndex);
386
400
  },
387
- renderPageSpecialError(specialError) {
388
- return options.renderPageSpecialError(specialError);
389
- },
390
401
  resolveSpecialError: resolveAppPageSpecialError,
391
402
  runWithSuppressedHookWarning(probe) {
392
403
  return options.runWithSuppressedHookWarning(probe);
393
404
  },
394
405
  classification: options.classification
395
- });
396
- if (preRenderResult.response) return applyIneligibleRouteCachePolicy(preRenderResult.response, options);
406
+ }));
407
+ const preRenderResult = probeOutcome.result;
408
+ if (preRenderResult.response) {
409
+ if (probeOutcome.dynamicDetected) markDynamicUsage();
410
+ return applyIneligibleRouteCachePolicy(preRenderResult.response, options, probeOutcome.cacheLife);
411
+ }
397
412
  const layoutFlags = preRenderResult.layoutFlags;
398
413
  const artifactCompatibility = createAppPageArtifactCompatibility(options.element, options.routePattern);
399
414
  const rootBoundaryId = artifactCompatibility?.rootBoundaryId ?? null;
@@ -458,7 +473,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
458
473
  if (options.pprFallbackShellSignal) pprFallbackShellRsc = new Uint8Array(await readAppPageBinaryStream(rscStream));
459
474
  let revalidateSeconds = options.revalidateSeconds;
460
475
  let expireSeconds = options.expireSeconds;
461
- const shouldWaitForAllReady = options.isPrerender === true && options.isSpeculativePrerender !== true;
476
+ const shouldWaitForAllReady = options.isPrerender === true && options.isSpeculativePrerender !== true || options.renderWholeDocument === true;
462
477
  const shouldReadRequestCacheLifeForPrerender = options.isPrerender === true;
463
478
  const mayResolveCacheLifeAfterHeaders = options.isProgressiveActionRender !== true && (revalidateSeconds === null || revalidateSeconds > 0) && !options.isDraftMode && !options.isForceDynamic && !shouldBypassRscCache;
464
479
  const shouldCaptureRscForCacheMetadata = (options.isProduction || options.isPrerender === true) && mayResolveCacheLifeAfterHeaders && options.isStaticEligible;
@@ -474,6 +489,16 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
474
489
  const capturedRscDataRef = { value: null };
475
490
  if (rscCapture.sideStream && options.isRscRequest) capturedRscDataRef.value = readAppPageBinaryStream(rscCapture.sideStream);
476
491
  if (options.isRscRequest) {
492
+ const resolveRscRenderStatus = options.isProduction && shouldCaptureRscForCacheMetadata && capturedRscDataRef.value !== null && options.isPrerender !== true && options.isRoutePPREnabled !== true ? createAppPageRscRenderStatusResolver({
493
+ basePath: options.basePath,
494
+ capturedRscData: capturedRscDataRef.value,
495
+ getCapturedSpecialError: rscErrorTracker.getCapturedSpecialError,
496
+ getCapturedSpecialErrors: rscErrorTracker.getCapturedSpecialErrors,
497
+ isForceStatic: options.isForceStatic,
498
+ loadSsrHandler: options.loadSsrHandler,
499
+ navigationContext: options.getNavigationContext(),
500
+ rootParams: options.rootParams
501
+ }) : void 0;
477
502
  let requestCacheLifeForPrerender = null;
478
503
  if (shouldWaitForAllReady) await settleCapturedRscRenderForCacheMetadata(capturedRscDataRef.value);
479
504
  if (shouldReadRequestCacheLifeForPrerender) {
@@ -566,6 +591,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
566
591
  mountedSlotsHeader: options.mountedSlotsHeader,
567
592
  omitPendingDynamicCacheState: options.omitPendingDynamicCacheState,
568
593
  renderMode: options.renderMode,
594
+ resolveRscRenderStatus,
569
595
  preserveClientResponseHeaders: rscResponsePolicy.cacheState !== "MISS",
570
596
  expireSeconds,
571
597
  isStaticEligible: options.isStaticEligible,
@@ -591,6 +617,145 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
591
617
  let requestCacheLifeForPrerender = null;
592
618
  let dynamicUsedDuringHtmlRender = false;
593
619
  let renderEnd;
620
+ let shellSpecialError = null;
621
+ const resolveHtmlCacheWrite = (dynamicUsedDuringRender) => {
622
+ const htmlResponsePolicy = resolveAppPageHtmlResponsePolicy({
623
+ dynamicUsedDuringRender,
624
+ isProgressiveActionRender: options.isProgressiveActionRender === true,
625
+ hasScriptNonce: Boolean(options.scriptNonce),
626
+ isDraftMode: options.isDraftMode,
627
+ isDynamicError: options.isDynamicError,
628
+ isForceDynamic: options.isForceDynamic,
629
+ isForceStatic: options.isForceStatic,
630
+ isProduction: options.isProduction,
631
+ isStaticEligible: options.isStaticEligible,
632
+ expireSeconds,
633
+ revalidateSeconds
634
+ });
635
+ const shouldSpeculativelyWriteCache = options.isProduction && shouldCaptureRscForCacheMetadata && !options.isEdgeRuntime && revalidateSeconds === null && !options.isDynamicError && !options.isForceStatic && !options.scriptNonce && options.isProgressiveActionRender !== true && !dynamicUsedDuringRender;
636
+ return {
637
+ htmlResponsePolicy,
638
+ shouldWriteHtmlCache: htmlResponsePolicy.shouldWriteToCache || shouldSpeculativelyWriteCache
639
+ };
640
+ };
641
+ const finalizeHtmlCacheWrite = (response, write) => finalizeAppPageHtmlCacheResponse(response, {
642
+ bypassInterceptionContextCache: options.bypassInterceptionContextCache,
643
+ capturedDynamicUsageBeforeContextCleanup: write.capturedDynamicUsageBeforeContextCleanup,
644
+ capturedRscDataPromise: capturedRscDataRef.value,
645
+ cleanPathname: options.cleanPathname,
646
+ clientTraceMetadataMarker,
647
+ consumeDynamicUsage: consumeRenderDynamicUsage,
648
+ consumeRenderObservationState,
649
+ createHtmlRenderObservation(input) {
650
+ return createAppPageRenderObservation({
651
+ boundaryOutcome: { kind: "success" },
652
+ cacheability: "public",
653
+ cacheTags: input.cacheTags,
654
+ cleanPathname: options.cleanPathname,
655
+ completeness: "complete",
656
+ output: htmlOutputScope,
657
+ params: options.navigationParams,
658
+ state: input.state
659
+ });
660
+ },
661
+ createRscRenderObservation(input) {
662
+ return createAppPageRenderObservation({
663
+ boundaryOutcome: { kind: "success" },
664
+ cacheability: "public",
665
+ cacheTags: input.cacheTags,
666
+ cleanPathname: options.cleanPathname,
667
+ completeness: "complete",
668
+ output: rscOutputScope,
669
+ params: options.navigationParams,
670
+ state: input.state
671
+ });
672
+ },
673
+ getPageTags() {
674
+ return options.getPageTags();
675
+ },
676
+ getRequestCacheLife() {
677
+ return readRequestCacheLifeForCachePolicy(options);
678
+ },
679
+ headers: write.headers,
680
+ isrDebug: options.isrDebug,
681
+ isrHtmlKey: options.isrHtmlKey,
682
+ isrRscKey: options.isrRscKey,
683
+ isrSet: options.isrSet,
684
+ interceptionContext: options.interceptionContext,
685
+ interceptionId: options.interceptionId,
686
+ omitPendingDynamicCacheState: options.omitPendingDynamicCacheState,
687
+ preserveClientResponseHeaders: !write.htmlResponsePolicy.shouldWriteToCache,
688
+ expireSeconds,
689
+ isStaticEligible: options.isStaticEligible,
690
+ revalidateSeconds: resolveAppPageCacheWriteRevalidateSeconds({
691
+ isDynamicError: options.isDynamicError,
692
+ isForceStatic: options.isForceStatic,
693
+ isStaticEligible: options.isStaticEligible,
694
+ revalidateSeconds
695
+ }),
696
+ linkHeader: write.linkHeader,
697
+ status: write.status,
698
+ waitUntil(cachePromise) {
699
+ options.waitUntil?.(cachePromise);
700
+ }
701
+ });
702
+ const mayStoreShellSpecialError = () => options.isProduction && shouldCaptureRscForCacheMetadata && options.isPrerender !== true && capturedRscDataRef.value !== null && resolveEarlyResponseCacheControl(options) === null;
703
+ const mayPrerenderShellSpecialError = () => options.isPrerender === true && options.isSpeculativePrerender !== true && options.pprFallbackShellSignal === void 0 && shouldCaptureRscForCacheMetadata && capturedRscDataRef.value !== null && resolveEarlyResponseCacheControl(options) === null;
704
+ const readPrerenderCacheMetadata = () => ({
705
+ cacheTags: options.getPageTags(),
706
+ requestCacheLife: readRequestCacheLifeForPrerender(options)
707
+ });
708
+ let prerenderedSpecialError = null;
709
+ const finalizePrerenderedShellSpecialErrorResponse = async (response, specialError, prerendered) => {
710
+ if (response.status !== specialError.statusCode) return applyIneligibleRouteCachePolicy(response, options);
711
+ const body = await response.arrayBuffer();
712
+ const { cacheTags, requestCacheLife } = prerendered.redirectMetadata ?? readPrerenderCacheMetadata();
713
+ ({expireSeconds, revalidateSeconds} = applyRequestCacheLife({
714
+ expireSeconds,
715
+ requestCacheLife,
716
+ revalidateSeconds
717
+ }));
718
+ const { htmlResponsePolicy } = resolveHtmlCacheWrite(prerendered.dynamicUsed || consumeRenderDynamicUsage());
719
+ const headers = new Headers(response.headers);
720
+ if (htmlResponsePolicy.cacheControl && !headers.has("cache-control")) headers.set("Cache-Control", htmlResponsePolicy.cacheControl);
721
+ applyPrerenderCacheLifeHeader(headers, requestCacheLife);
722
+ applyPrerenderCacheTagsHeader(headers, cacheTags);
723
+ getRequestExecutionContext()?.recordPrerenderSpecialError?.({
724
+ headers: resolveAppPageSpecialErrorStoredHeaders(specialError, options.basePath) ?? {},
725
+ status: response.status
726
+ });
727
+ const prerenderResponse = new Response(body, {
728
+ headers,
729
+ status: response.status,
730
+ statusText: response.statusText
731
+ });
732
+ copyLinkHeaderProvenance(response.headers, prerenderResponse.headers);
733
+ return prerenderResponse;
734
+ };
735
+ const finalizeShellSpecialErrorResponse = (response, specialError) => {
736
+ if (response.status !== specialError.statusCode) return applyIneligibleRouteCachePolicy(response, options);
737
+ const dynamicUsedDuringRender = consumeRenderDynamicUsage();
738
+ const { htmlResponsePolicy, shouldWriteHtmlCache } = resolveHtmlCacheWrite(dynamicUsedDuringRender);
739
+ const headers = new Headers(response.headers);
740
+ if (htmlResponsePolicy.cacheControl && !headers.has("cache-control")) headers.set("Cache-Control", htmlResponsePolicy.cacheControl);
741
+ if (htmlResponsePolicy.cacheState) setCacheStateHeaders(headers, htmlResponsePolicy.cacheState);
742
+ const policyResponse = new Response(response.body, {
743
+ headers,
744
+ status: response.status,
745
+ statusText: response.statusText
746
+ });
747
+ copyLinkHeaderProvenance(response.headers, policyResponse.headers);
748
+ if (!shouldWriteHtmlCache) return policyResponse;
749
+ const clientResponse = finalizeHtmlCacheWrite(policyResponse, {
750
+ capturedDynamicUsageBeforeContextCleanup: () => dynamicUsedDuringRender,
751
+ headers: resolveAppPageSpecialErrorStoredHeaders(specialError, options.basePath),
752
+ htmlResponsePolicy,
753
+ linkHeader: getFrameworkLinkHeader(response.headers),
754
+ status: response.status
755
+ });
756
+ copyLinkHeaderProvenance(response.headers, clientResponse.headers);
757
+ return clientResponse;
758
+ };
594
759
  const htmlRender = await renderAppPageHtmlStreamWithRecovery({
595
760
  onShellRendered() {
596
761
  if (!options.isProduction) renderEnd = performance.now();
@@ -657,30 +822,34 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
657
822
  isStaticGeneration: options.isPrerender === true,
658
823
  isForceStatic: options.isForceStatic,
659
824
  isCacheCandidate: isCacheCandidateHtmlRender,
660
- onSsrError: createAppPageSsrErrorHandler(onSsrError, rscErrorTracker.isCapturedError)
825
+ onSsrError: createAppPageSsrErrorHandler(onSsrError, rscErrorTracker.isCapturedError),
826
+ mirrorNextFlight: () => rscErrorTracker.getCapturedSpecialError() !== null
661
827
  });
662
828
  },
663
- renderSpecialErrorResponse(specialError) {
664
- return options.renderPageSpecialError(specialError);
829
+ async renderSpecialErrorResponse(specialError) {
830
+ shellSpecialError = specialError;
831
+ const mayPrerender = mayPrerenderShellSpecialError();
832
+ if (mayStoreShellSpecialError() || mayPrerender) await settleCapturedRscRenderForCacheMetadata(capturedRscDataRef.value, peekRenderDynamicUsage);
833
+ if (mayPrerender) prerenderedSpecialError = {
834
+ dynamicUsed: consumeRenderDynamicUsage(),
835
+ redirectMetadata: specialError.kind === "redirect" ? readPrerenderCacheMetadata() : null
836
+ };
837
+ return options.renderPageSpecialError(specialError, { isCacheCandidate: isCacheCandidateHtmlRender });
665
838
  },
666
- resolveSpecialError: resolveAppPageSpecialError
839
+ resolveSpecialError(error) {
840
+ return resolveAppPageShellSpecialError(error, rscErrorTracker.getCapturedSpecialErrors(error));
841
+ }
667
842
  });
668
843
  options.onRenderComplete?.(htmlRender.renderComplete);
669
- if (htmlRender.response) return applyIneligibleRouteCachePolicy(htmlRender.response, options);
844
+ if (htmlRender.response) {
845
+ if (shellSpecialError && options.isProduction && options.isPrerender !== true && resolveEarlyResponseCacheControl(options) === null) return finalizeShellSpecialErrorResponse(htmlRender.response, shellSpecialError);
846
+ if (shellSpecialError && prerenderedSpecialError) return finalizePrerenderedShellSpecialErrorResponse(htmlRender.response, shellSpecialError, prerenderedSpecialError);
847
+ return applyIneligibleRouteCachePolicy(htmlRender.response, options);
848
+ }
670
849
  let htmlStream = htmlRender.htmlStream;
671
850
  if (!htmlStream) throw new Error("[vinext] Expected an HTML stream when no fallback response was returned");
672
851
  const linkHeader = buildAppPageLinkHeader(htmlRender.linkHeader, fontLinkHeader, options.reactMaxHeadersLength);
673
852
  if (options.isPrerender === true) await htmlRender.metadataReady;
674
- if (options.hasLoadingBoundary || !probePageBeforeRender) {
675
- const captured = rscErrorTracker.getCapturedSpecialError();
676
- if (captured) {
677
- const specialError = resolveAppPageSpecialError(captured);
678
- if (specialError) {
679
- htmlStream.cancel().catch(() => {});
680
- return applyIneligibleRouteCachePolicy(await options.renderPageSpecialError(specialError), options);
681
- }
682
- }
683
- }
684
853
  let dynamicUsedDuringRender = consumeRenderDynamicUsage();
685
854
  dynamicUsedDuringHtmlRender = dynamicUsedDuringRender;
686
855
  const stopSpeculativeMetadataWaitOnDynamicUsage = options.isSpeculativePrerender === true && shouldReadRequestCacheLifeForPrerender ? () => {
@@ -710,19 +879,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
710
879
  dynamicUsedDuringHtmlRender = dynamicUsedBeforeContextCleanup;
711
880
  options.clearRequestContext();
712
881
  });
713
- const htmlResponsePolicy = resolveAppPageHtmlResponsePolicy({
714
- dynamicUsedDuringRender,
715
- isProgressiveActionRender: options.isProgressiveActionRender === true,
716
- hasScriptNonce: Boolean(options.scriptNonce),
717
- isDraftMode: options.isDraftMode,
718
- isDynamicError: options.isDynamicError,
719
- isForceDynamic: options.isForceDynamic,
720
- isForceStatic: options.isForceStatic,
721
- isProduction: options.isProduction,
722
- isStaticEligible: options.isStaticEligible,
723
- expireSeconds,
724
- revalidateSeconds
725
- });
882
+ const { htmlResponsePolicy, shouldWriteHtmlCache } = resolveHtmlCacheWrite(dynamicUsedDuringRender);
726
883
  const htmlResponseTiming = buildResponseTiming({
727
884
  compileEnd,
728
885
  handlerStart: options.handlerStart,
@@ -747,8 +904,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
747
904
  applyCdnResponseHeaders(response.headers, { cacheControl: NEVER_CACHE_CONTROL });
748
905
  return response;
749
906
  }
750
- const shouldSpeculativelyWriteCache = options.isProduction && shouldCaptureRscForCacheMetadata && !options.isEdgeRuntime && revalidateSeconds === null && !options.isDynamicError && !options.isForceStatic && !options.scriptNonce && options.isProgressiveActionRender !== true && !dynamicUsedDuringRender;
751
- if (htmlResponsePolicy.shouldWriteToCache || shouldSpeculativelyWriteCache) {
907
+ if (shouldWriteHtmlCache) {
752
908
  const isrResponse = buildAppPageHtmlResponse(safeHtmlStream, {
753
909
  cacheTags: options.isPrerender === true ? options.getPageTags() : void 0,
754
910
  draftCookie,
@@ -760,66 +916,10 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
760
916
  timing: htmlResponseTiming
761
917
  });
762
918
  if (options.isPrerender === true) return isrResponse;
763
- return finalizeAppPageHtmlCacheResponse(isrResponse, {
764
- bypassInterceptionContextCache: options.bypassInterceptionContextCache,
765
- capturedDynamicUsageBeforeContextCleanup() {
766
- return dynamicUsedBeforeContextCleanup;
767
- },
768
- capturedRscDataPromise: capturedRscDataRef.value,
769
- cleanPathname: options.cleanPathname,
770
- clientTraceMetadataMarker,
771
- consumeDynamicUsage: consumeRenderDynamicUsage,
772
- consumeRenderObservationState,
773
- createHtmlRenderObservation(input) {
774
- return createAppPageRenderObservation({
775
- boundaryOutcome: { kind: "success" },
776
- cacheability: "public",
777
- cacheTags: input.cacheTags,
778
- cleanPathname: options.cleanPathname,
779
- completeness: "complete",
780
- output: htmlOutputScope,
781
- params: options.navigationParams,
782
- state: input.state
783
- });
784
- },
785
- createRscRenderObservation(input) {
786
- return createAppPageRenderObservation({
787
- boundaryOutcome: { kind: "success" },
788
- cacheability: "public",
789
- cacheTags: input.cacheTags,
790
- cleanPathname: options.cleanPathname,
791
- completeness: "complete",
792
- output: rscOutputScope,
793
- params: options.navigationParams,
794
- state: input.state
795
- });
796
- },
797
- getPageTags() {
798
- return options.getPageTags();
799
- },
800
- getRequestCacheLife() {
801
- return readRequestCacheLifeForCachePolicy(options);
802
- },
803
- isrDebug: options.isrDebug,
804
- isrHtmlKey: options.isrHtmlKey,
805
- isrRscKey: options.isrRscKey,
806
- isrSet: options.isrSet,
807
- interceptionContext: options.interceptionContext,
808
- interceptionId: options.interceptionId,
809
- omitPendingDynamicCacheState: options.omitPendingDynamicCacheState,
810
- preserveClientResponseHeaders: !htmlResponsePolicy.shouldWriteToCache,
811
- expireSeconds,
812
- isStaticEligible: options.isStaticEligible,
813
- revalidateSeconds: resolveAppPageCacheWriteRevalidateSeconds({
814
- isDynamicError: options.isDynamicError,
815
- isForceStatic: options.isForceStatic,
816
- isStaticEligible: options.isStaticEligible,
817
- revalidateSeconds
818
- }),
819
- linkHeader: linkHeader ?? null,
820
- waitUntil(cachePromise) {
821
- options.waitUntil?.(cachePromise);
822
- }
919
+ return finalizeHtmlCacheWrite(isrResponse, {
920
+ capturedDynamicUsageBeforeContextCleanup: () => dynamicUsedBeforeContextCleanup,
921
+ htmlResponsePolicy,
922
+ linkHeader: linkHeader ?? null
823
923
  });
824
924
  }
825
925
  const response = buildAppPageHtmlResponse(safeHtmlStream, {
@@ -49,7 +49,6 @@ type ResolveAppPageGenerateStaticParamsSourcesOptions = {
49
49
  };
50
50
  type BuildAppPageElementOptions<TElement> = {
51
51
  buildPageElement: () => Promise<TElement>;
52
- probePageSpecialError?: () => Promise<AppPageSpecialError | null>;
53
52
  renderErrorBoundaryPage: (error: unknown) => Promise<Response | null>;
54
53
  renderSpecialError: (specialError: AppPageSpecialError) => Promise<Response>;
55
54
  resolveSpecialError: (error: unknown) => AppPageSpecialError | null;
@@ -333,8 +333,7 @@ async function buildAppPageElement(options) {
333
333
  response: null
334
334
  };
335
335
  } catch (error) {
336
- const buildSpecialError = options.resolveSpecialError(error);
337
- const specialError = (buildSpecialError ? await options.probePageSpecialError?.() : null) ?? buildSpecialError;
336
+ const specialError = options.resolveSpecialError(error);
338
337
  if (specialError) return {
339
338
  element: null,
340
339
  response: await options.renderSpecialError(specialError)
@@ -71,9 +71,10 @@ type BuildAppPageHtmlResponseOptions = {
71
71
  timing?: AppPageResponseTiming;
72
72
  };
73
73
  /**
74
- * Only ever set from a *completed* render's cacheLife (cache replay or
75
- * prerender seed) — `use cache` scopes keep resolving after headers commit,
76
- * so a streaming response can never carry it.
74
+ * Only ever set from a *completed* render's cacheLife (cache replay,
75
+ * prerender seed, or a layout probe's early redirect) — `use cache` scopes
76
+ * keep resolving after headers commit, so a streaming response can never
77
+ * carry it.
77
78
  */
78
79
  export declare function applyClientStaleTimeHeader(headers: Headers, staleTimeSeconds: number | undefined): void;
79
80
  /**
@@ -18,9 +18,10 @@ function applyDynamicStaleTimeHeader(headers, dynamicStaleTimeSeconds) {
18
18
  if (dynamicStaleTimeSeconds !== void 0 && Number.isInteger(dynamicStaleTimeSeconds) && dynamicStaleTimeSeconds >= 0) headers.set(VINEXT_DYNAMIC_STALE_TIME_HEADER, String(dynamicStaleTimeSeconds));
19
19
  }
20
20
  /**
21
- * Only ever set from a *completed* render's cacheLife (cache replay or
22
- * prerender seed) — `use cache` scopes keep resolving after headers commit,
23
- * so a streaming response can never carry it.
21
+ * Only ever set from a *completed* render's cacheLife (cache replay,
22
+ * prerender seed, or a layout probe's early redirect) — `use cache` scopes
23
+ * keep resolving after headers commit, so a streaming response can never
24
+ * carry it.
24
25
  */
25
26
  function applyClientStaleTimeHeader(headers, staleTimeSeconds) {
26
27
  if (staleTimeSeconds === void 0) return;
@@ -140,14 +140,15 @@ type BuildAppPageRouteElementOptions<TModule extends AppPageModule = AppPageModu
140
140
  makeThenableParams: MakeThenableParams;
141
141
  matchedParams: AppPageParams;
142
142
  metadataPlacement?: "body" | "head";
143
- resolvedMetadata: Metadata | null;
143
+ /**
144
+ * Starts resolving the page's head. The head, its streamed body tags and its
145
+ * error outlet each call this while Flight renders them, so callers memoise
146
+ * it per render with React's `cache()`. Without it the head has no metadata.
147
+ */
148
+ resolveHead?: (() => AppPageRouteHead) | null;
144
149
  resolvedMetadataPathname?: string;
145
- resolvedViewport: Viewport;
146
150
  scriptNonce?: string;
147
- streamingMetadata?: Promise<Metadata | null> | null;
148
- streamingMetadataOutlet?: Promise<unknown> | null;
149
151
  streamingMetadataOutletSuspended?: boolean;
150
- streamingMetadataTags?: Promise<Metadata | null> | null;
151
152
  trailingSlash?: boolean;
152
153
  rootForbiddenModule?: TModule | null;
153
154
  rootNotFoundModule?: TModule | null;
@@ -158,6 +159,17 @@ type BuildAppPageRouteElementOptions<TModule extends AppPageModule = AppPageModu
158
159
  searchParams?: unknown;
159
160
  slotOverrides?: Readonly<Record<string, AppPageSlotOverride<TModule>>> | null;
160
161
  };
162
+ /**
163
+ * A page's head as Flight renders it. `metadata` and `viewport` hold the tags
164
+ * to render, including the HTTP access fallback's when resolution failed, so
165
+ * they never reject. `outlet` rejects with the resolution error so the route's
166
+ * boundaries can handle it.
167
+ */
168
+ export type AppPageRouteHead = {
169
+ metadata: Promise<Metadata | null>;
170
+ outlet: Promise<null>;
171
+ viewport: Promise<Viewport>;
172
+ };
161
173
  type MakeThenableParams = (params: AppPageParams, observer?: ThenableParamsObserver) => unknown;
162
174
  type BuildAppPageElementsOptions<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = BuildAppPageRouteElementOptions<TModule, TErrorModule> & {
163
175
  interception?: AppElementsInterception | null;