vinext 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +20 -20
  2. package/dist/build/prerender.js +3 -2
  3. package/dist/check.js +3 -6
  4. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  5. package/dist/client/dev-error-overlay.d.ts +2 -0
  6. package/dist/client/dev-error-overlay.js +19 -3
  7. package/dist/config/config-matchers.js +2 -0
  8. package/dist/config/destination-params.d.ts +2 -1
  9. package/dist/config/destination-params.js +52 -2
  10. package/dist/config/next-config.d.ts +6 -0
  11. package/dist/config/next-config.js +18 -3
  12. package/dist/entries/app-rsc-entry.js +0 -1
  13. package/dist/entries/pages-server-entry.js +3 -1
  14. package/dist/index.js +29 -20
  15. package/dist/plugins/strip-server-exports.js +2 -1
  16. package/dist/server/api-handler.js +2 -1
  17. package/dist/server/app-browser-entry.js +7 -3
  18. package/dist/server/app-browser-rsc-redirect.js +3 -3
  19. package/dist/server/app-browser-server-action-client.js +2 -2
  20. package/dist/server/app-browser-visible-commit.js +2 -2
  21. package/dist/server/app-optimistic-routing.js +2 -3
  22. package/dist/server/app-page-boundary-render.js +5 -10
  23. package/dist/server/app-page-dispatch.d.ts +0 -1
  24. package/dist/server/app-page-dispatch.js +0 -1
  25. package/dist/server/app-page-element-builder.js +3 -3
  26. package/dist/server/app-page-execution.js +9 -6
  27. package/dist/server/app-page-render.d.ts +0 -1
  28. package/dist/server/app-page-render.js +14 -14
  29. package/dist/server/app-page-response.js +1 -1
  30. package/dist/server/app-page-route-wiring.js +22 -5
  31. package/dist/server/app-page-stream.d.ts +10 -8
  32. package/dist/server/app-page-stream.js +2 -1
  33. package/dist/server/app-request-stage-independent-entry.js +1 -1
  34. package/dist/server/app-route-handler-execution.js +3 -2
  35. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  36. package/dist/server/app-route-handler-runtime.js +11 -7
  37. package/dist/server/app-router-entry.js +1 -1
  38. package/dist/server/app-rsc-cache-busting.d.ts +10 -10
  39. package/dist/server/app-rsc-cache-busting.js +27 -68
  40. package/dist/server/app-rsc-error-handler.js +1 -1
  41. package/dist/server/app-rsc-errors.js +4 -1
  42. package/dist/server/app-rsc-handler.js +53 -62
  43. package/dist/server/app-rsc-request-normalization.d.ts +10 -12
  44. package/dist/server/app-rsc-request-normalization.js +11 -14
  45. package/dist/server/app-rsc-response-stage.js +4 -12
  46. package/dist/server/app-server-action-execution.js +8 -4
  47. package/dist/server/app-ssr-entry.d.ts +6 -0
  48. package/dist/server/app-ssr-entry.js +13 -8
  49. package/dist/server/app-worker-stages.d.ts +12 -3
  50. package/dist/server/app-worker-stages.js +37 -4
  51. package/dist/server/bfcache-identity.d.ts +7 -0
  52. package/dist/server/bfcache-identity.js +10 -1
  53. package/dist/server/cacheability-manifest.js +1 -2
  54. package/dist/server/dev-origin-check.d.ts +2 -1
  55. package/dist/server/dev-origin-check.js +2 -1
  56. package/dist/server/dev-server.js +2 -1
  57. package/dist/server/image-optimization.d.ts +5 -0
  58. package/dist/server/image-optimization.js +6 -1
  59. package/dist/server/isr-cache.js +4 -1
  60. package/dist/server/metadata-route-response.js +1 -1
  61. package/dist/server/middleware-matcher.js +19 -6
  62. package/dist/server/middleware-runtime.js +6 -2
  63. package/dist/server/multi-stage.d.ts +7 -3
  64. package/dist/server/pages-node-compat.d.ts +1 -1
  65. package/dist/server/pages-node-compat.js +2 -0
  66. package/dist/server/pages-page-data.js +2 -1
  67. package/dist/server/pages-page-handler.js +17 -6
  68. package/dist/server/pages-page-response.d.ts +9 -0
  69. package/dist/server/pages-page-response.js +18 -4
  70. package/dist/server/pages-request-stage-entry.js +24 -4
  71. package/dist/server/pages-router-entry.js +3 -2
  72. package/dist/server/prod-server.d.ts +2 -2
  73. package/dist/server/prod-server.js +42 -8
  74. package/dist/server/request-error-log.d.ts +9 -0
  75. package/dist/server/request-error-log.js +13 -0
  76. package/dist/server/request-pipeline.d.ts +9 -4
  77. package/dist/server/request-pipeline.js +23 -15
  78. package/dist/server/rsc-completion-metadata.js +3 -0
  79. package/dist/server/static-file-cache.d.ts +9 -0
  80. package/dist/server/static-file-cache.js +13 -2
  81. package/dist/shims/cache-request-state.d.ts +6 -6
  82. package/dist/shims/cache-request-state.js +6 -36
  83. package/dist/shims/cache-runtime.d.ts +3 -1
  84. package/dist/shims/cache-runtime.js +77 -47
  85. package/dist/shims/cache.d.ts +10 -5
  86. package/dist/shims/cache.js +35 -22
  87. package/dist/shims/client-hook-error.d.ts +2 -0
  88. package/dist/shims/client-hook-error.js +16 -1
  89. package/dist/shims/fetch-cache.js +6 -2
  90. package/dist/shims/headers.d.ts +2 -1
  91. package/dist/shims/headers.js +5 -3
  92. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  93. package/dist/shims/link.js +5 -10
  94. package/dist/shims/navigation.js +2 -3
  95. package/dist/shims/request-state-types.d.ts +2 -1
  96. package/dist/shims/slot.d.ts +6 -2
  97. package/dist/shims/slot.js +10 -3
  98. package/dist/shims/unified-request-context.d.ts +6 -0
  99. package/dist/shims/unified-request-context.js +1 -0
  100. package/dist/utils/cache-life-profiles.d.ts +33 -0
  101. package/dist/utils/cache-life-profiles.js +111 -0
  102. package/dist/utils/middleware-request-headers.d.ts +14 -3
  103. package/dist/utils/middleware-request-headers.js +54 -7
  104. package/package.json +1 -1
@@ -3,7 +3,7 @@ import { stripBasePath } from "../utils/base-path.js";
3
3
  import { decodeMatchedParams, splitPathnameForRouteMatch } from "../routing/utils.js";
4
4
  import { extractRawRoutePatternParams, matchRoutePattern } from "../routing/route-pattern.js";
5
5
  import { buildOptimisticRouteTrie, matchOptimisticRouteTrie } from "../routing/optimistic-route-trie.js";
6
- import { stripRscCacheBustingSearchParam, stripRscSuffix } from "./app-rsc-cache-busting.js";
6
+ import { stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
7
7
  import { AppElementsWire } from "./app-elements-wire.js";
8
8
  import "./app-elements.js";
9
9
  import { createNestedBfcacheSlotSegmentId, deriveBfcacheSegmentIdentity, isNestedBfcacheSlotSegmentId } from "./bfcache-identity.js";
@@ -36,8 +36,7 @@ function hrefToRouteParts(href, basePath) {
36
36
  return null;
37
37
  }
38
38
  stripRscCacheBustingSearchParam(url);
39
- const withoutRscSuffix = stripRscSuffix(url.pathname);
40
- const appPathname = stripBasePath(withoutRscSuffix, basePath);
39
+ const appPathname = stripBasePath(url.pathname, basePath);
41
40
  const pathname = appPathname === "" ? "/" : appPathname;
42
41
  return {
43
42
  normalized: splitPathnameForRouteMatch(pathname),
@@ -3,13 +3,12 @@ import { isNavigationSignalError } from "../utils/navigation-signal.js";
3
3
  import { NEVER_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
4
4
  import { isRenderDynamicLatched, isStaticGenerationAccessError } from "../shims/internal/headers-state.js";
5
5
  import { copyLinkHeaderProvenance } from "./app-response-header-provenance.js";
6
- import { stripRscSuffix } from "./app-rsc-cache-busting.js";
7
6
  import { createArtifactCompatibilityEnvelope } from "./artifact-compatibility.js";
8
7
  import { AppElementsWire } from "./app-elements-wire.js";
9
8
  import "./app-elements.js";
10
9
  import DefaultGlobalError from "../shims/default-global-error.js";
11
10
  import { ErrorBoundary, GlobalErrorBoundary, SerializedErrorBoundary } from "../shims/error-boundary.js";
12
- import { buildClientHookErrorMessage } from "../shims/client-hook-error.js";
11
+ import { rewriteClientHookError } from "../shims/client-hook-error.js";
13
12
  import { LayoutSegmentProvider } from "../shims/layout-segment-context.js";
14
13
  import { withUseCacheLayoutMarker } from "../shims/internal/app-page-props-cache-key.js";
15
14
  import { MetadataHead, ViewportHead } from "../shims/metadata.js";
@@ -258,7 +257,7 @@ async function renderAppPageHttpAccessFallback(options) {
258
257
  if (!boundaryComponent) return null;
259
258
  const layoutModules = options.layoutModules ?? options.route?.layouts ?? options.rootLayouts;
260
259
  const pathname = new URL(options.requestUrl).pathname;
261
- const routePathname = options.routePathname ?? stripRscSuffix(stripBasePath(pathname, options.basePath ?? ""));
260
+ const routePathname = options.routePathname ?? stripBasePath(pathname, options.basePath ?? "");
262
261
  const headBoundaryModule = options.headBoundaryModule ?? boundaryModule;
263
262
  const headLayoutModules = options.headLayoutModules ?? layoutModules;
264
263
  const routeSegments = resolveHttpAccessFallbackHeadRouteSegments(options.route, headLayoutModules);
@@ -612,6 +611,7 @@ async function renderAppPageErrorBoundary(options) {
612
611
  skipLayoutWrapping: errorBoundary.isGlobalError
613
612
  });
614
613
  };
614
+ const isErrorStatus = errorBoundary.isGlobalError || !options.isRscRequest;
615
615
  const renderWith = async (BoundaryComponent) => {
616
616
  const response = await renderAppPageBoundaryElementResponse({
617
617
  ...options,
@@ -621,9 +621,9 @@ async function renderAppPageErrorBoundary(options) {
621
621
  navigationParams: matchedParams,
622
622
  route: options.route,
623
623
  routePattern: options.route?.pattern,
624
- status: errorBoundary.isGlobalError ? 500 : 200
624
+ status: isErrorStatus ? 500 : 200
625
625
  });
626
- if (errorBoundary.isGlobalError) applyCdnResponseHeaders(response.headers, { cacheControl: NEVER_CACHE_CONTROL });
626
+ if (isErrorStatus) applyCdnResponseHeaders(response.headers, { cacheControl: NEVER_CACHE_CONTROL });
627
627
  return response;
628
628
  };
629
629
  try {
@@ -636,10 +636,5 @@ async function renderAppPageErrorBoundary(options) {
636
636
  throw renderError;
637
637
  }
638
638
  }
639
- const _clientHookPattern = /\b(useState|useEffect|useReducer|useRef|useContext|useLayoutEffect|useInsertionEffect|useSyncExternalStore|useTransition|useImperativeHandle|useDeferredValue|useActionState|useOptimistic|useEffectEvent)\b.*is not a function/;
640
- function rewriteClientHookError(error) {
641
- const match = error.message.match(_clientHookPattern);
642
- if (match) error.message = buildClientHookErrorMessage(`${match[1]}()`);
643
- }
644
639
  //#endregion
645
640
  export { renderAppPageErrorBoundary, renderAppPageHttpAccessFallback };
@@ -200,7 +200,6 @@ export type DispatchAppPageOptions<TRoute extends AppPageDispatchRoute> = {
200
200
  hasGenerateStaticParams: boolean;
201
201
  /** Whether any segment of the route exports `generateStaticParams`. */
202
202
  hasAnyGenerateStaticParams: boolean;
203
- hasCustomGlobalError?: boolean;
204
203
  hasPageDefaultExport: boolean;
205
204
  hasPageModule: boolean;
206
205
  handlerStart: number;
@@ -692,7 +692,6 @@ async function dispatchAppPageInner(options) {
692
692
  return renderPageSpecialError(options, specialError, serveStreamingMetadata, interceptResult.interceptOpts, renderOptions?.isCacheCandidate === true, renderOptions?.caughtDocument, renderedRouteHead);
693
693
  },
694
694
  renderToReadableStream: options.renderToReadableStream,
695
- hasCustomGlobalError: options.hasCustomGlobalError,
696
695
  prerenderToReadableStream: options.prerenderToReadableStream,
697
696
  routePattern: route.pattern,
698
697
  scriptNonce: options.scriptNonce,
@@ -451,9 +451,9 @@ async function buildPageElements(options) {
451
451
  * slot-scoped params, which `app-page-route-wiring` then hands to the
452
452
  * slot page instead of the route's matched params.
453
453
  *
454
- * `routePath` is the already-normalized request pathname (basePath stripped,
455
- * RSC suffix removed). Re-parsing `request.url` here would re-introduce the
456
- * basePath and silently break the match for any app that configures one.
454
+ * `routePath` is the already-normalized request pathname (basePath stripped).
455
+ * Re-parsing `request.url` here would re-introduce the basePath and silently
456
+ * break the match for any app that configures one.
457
457
  */
458
458
  function buildSlotOverrides(route, routeParams, routePath, opts, interceptMetadataOutlet) {
459
459
  const overrides = {};
@@ -1,7 +1,7 @@
1
1
  import { VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER } from "./headers.js";
2
2
  import { copyLinkHeaderProvenance, getFrameworkLinkHeader, markFrameworkLinkHeaders } from "./app-response-header-provenance.js";
3
3
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
4
- import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader, createRscRedirectLocation } from "./app-rsc-cache-busting.js";
4
+ import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader, createRscRedirectLocation, toPathAbsolutePathname } from "./app-rsc-cache-busting.js";
5
5
  import { isHTTPAccessFallbackError, isThrownNavigationError } from "../shims/navigation-errors.js";
6
6
  import { applyEdgeRuntimeHeader } from "./app-page-response.js";
7
7
  import { parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
@@ -298,9 +298,11 @@ function toHeaderSafeUrlReference(location) {
298
298
  * header-safe, so a protocol-relative or scheme-relative target still resolves
299
299
  * against the page each client fetched. A relative target resolves against the
300
300
  * request's path to a path-only form, which the client resolves against its
301
- * own origin. Resolving against two placeholder origins tells these apart the
302
- * way the URL parser does, never depends on the request's origin, and cannot
303
- * mistake an absolute target naming one placeholder for a relative one.
301
+ * own origin; a resolved pathname that begins with "//" keeps a "/." prefix
302
+ * so that form stays a path rather than naming another host. Resolving
303
+ * against two placeholder origins tells these apart the way the URL parser
304
+ * does, never depends on the request's origin, and cannot mistake an absolute
305
+ * target naming one placeholder for a relative one.
304
306
  */
305
307
  function resolveRedirectTargets(location, requestUrl) {
306
308
  try {
@@ -310,9 +312,10 @@ function resolveRedirectTargets(location, requestUrl) {
310
312
  headerUrl: toHeaderSafeUrlReference(location)
311
313
  };
312
314
  const { pathname, search, hash } = first.resolved;
315
+ const target = `${toPathAbsolutePathname(pathname)}${search}${hash}`;
313
316
  return {
314
- digestUrl: `${pathname}${search}${hash}`,
315
- headerUrl: `${pathname}${search}${hash}`
317
+ digestUrl: target,
318
+ headerUrl: target
316
319
  };
317
320
  } catch {
318
321
  return {
@@ -72,7 +72,6 @@ type RenderAppPageLifecycleOptionsBase = {
72
72
  peekRequestCacheLife?: () => AppPageRequestCacheLife | null;
73
73
  getDraftModeCookieHeader: () => string | null | undefined;
74
74
  handlerStart: number;
75
- hasCustomGlobalError?: boolean;
76
75
  dynamicStaleTimeSeconds?: number;
77
76
  isDynamicError: boolean;
78
77
  isDraftMode: boolean;
@@ -13,6 +13,7 @@ import { isRenderDynamicLatched } from "../shims/internal/headers-state.js";
13
13
  import { peekDynamicUsage } from "../shims/headers.js";
14
14
  import { runWithFetchDedupe } from "../shims/fetch-cache.js";
15
15
  import { setCacheStateHeaders } from "./cache-headers.js";
16
+ import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
16
17
  import { copyLinkHeaderProvenance, getFrameworkLinkHeader } from "./app-response-header-provenance.js";
17
18
  import { traceResponseStartWithCompletion } from "./response-start-tracing.js";
18
19
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
@@ -21,7 +22,6 @@ import { AppElementsWire, isAppElementsRecord } from "./app-elements-wire.js";
21
22
  import "./app-elements.js";
22
23
  import { appendRscCompletionMetadata } from "./rsc-completion-metadata.js";
23
24
  import { isBailoutToCSRError } from "../shims/navigation-errors.js";
24
- import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
25
25
  import { applyClientStaleTimeHeader, applyEdgeRuntimeHeader, buildAppPageHtmlResponse, buildAppPageRscResponse, resolveAppPageHtmlResponsePolicy, resolveAppPageRscResponsePolicy, resolveUncacheableCacheControl } from "./app-page-response.js";
26
26
  import { buildAppPageFontLinkHeader, readAppPageBinaryStream, resolveAppPageCaughtNotFoundBoundaries, resolveAppPageShellSpecialError, resolveAppPageSpecialError, resolveAppPageSpecialErrorStoredHeaders, teeAppPageRscStreamForCapture } from "./app-page-execution.js";
27
27
  import { getAppPageLayoutGate } from "./app-page-layout-gate.js";
@@ -230,7 +230,6 @@ async function prepareAppPageElement(options) {
230
230
  }
231
231
  async function renderAppPageLifecycleImpl(options, renderSpan) {
232
232
  let dynamicUsageObserved = false;
233
- let dynamicUsageFinalized = false;
234
233
  const isCacheCandidateHtmlRender = options.isCacheCandidate === true && options.isPrerender !== true && !options.isRscRequest;
235
234
  const readsRenderDynamicLatch = isCacheCandidateHtmlRender || !options.isRscRequest && options.pprFallbackShellSignal === void 0 && (options.isPrerender === true || isRouteCacheabilityEvaluation());
236
235
  const readDynamicUsage = bindRequestContext(() => options.consumeDynamicUsage() || readsRenderDynamicLatch && isRenderDynamicLatched());
@@ -240,13 +239,6 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
240
239
  if (!dynamicUsageObserved) dynamicUsageObserved = readDynamicUsage();
241
240
  return dynamicUsageObserved;
242
241
  };
243
- const finalizeRenderDynamicUsage = () => {
244
- if (!dynamicUsageFinalized) {
245
- consumeRenderDynamicUsage();
246
- dynamicUsageFinalized = true;
247
- }
248
- return dynamicUsageObserved;
249
- };
250
242
  const artifactCompatibility = createAppPageArtifactCompatibility(options.element, options.routePattern);
251
243
  const rootBoundaryId = artifactCompatibility?.rootBoundaryId ?? null;
252
244
  const renderEpoch = artifactCompatibility?.renderEpoch ?? null;
@@ -429,7 +421,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
429
421
  const completionHeaders = new Headers(rscResponse.headers);
430
422
  completionHeaders.set(VINEXT_RSC_COMPLETION_METADATA_HEADER, "1");
431
423
  const completionResponse = dynamicStaleTimeSeconds !== void 0 && options.isPrerender !== true && !options.isForceStatic && rscResponse.body ? new Response(appendRscCompletionMetadata(rscResponse.body, () => {
432
- if (!finalizeRenderDynamicUsage()) return void 0;
424
+ if (!consumeRenderDynamicUsage()) return void 0;
433
425
  const completedServerStaleTimeSeconds = resolveClientStaleTimeSeconds(options.peekRequestCacheLife?.());
434
426
  if (shouldEmitDynamicStaleTime && completedServerStaleTimeSeconds === void 0) return;
435
427
  return {
@@ -447,7 +439,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
447
439
  bypassInterceptionContextCache: options.bypassInterceptionContextCache,
448
440
  captureAdmittedDocument,
449
441
  cleanPathname: options.cleanPathname,
450
- consumeDynamicUsage: finalizeRenderDynamicUsage,
442
+ consumeDynamicUsage: consumeRenderDynamicUsage,
451
443
  consumeRenderObservationState,
452
444
  createHtmlRenderObservation(input) {
453
445
  return createAppPageRenderObservation({
@@ -630,6 +622,12 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
630
622
  copyLinkHeaderProvenance(response.headers, prerenderResponse.headers);
631
623
  return prerenderResponse;
632
624
  };
625
+ const markPrerenderedDynamicShellSpecialError = (response, specialError) => {
626
+ if (options.isPrerender === true && response.status === specialError.statusCode && resolveEarlyResponseCacheControl(options) !== null && response.headers.get("cache-control")?.includes("no-store")) getRequestExecutionContext()?.recordPrerenderSpecialError?.({
627
+ headers: resolveAppPageSpecialErrorStoredHeaders(specialError, options.basePath) ?? {},
628
+ status: response.status
629
+ });
630
+ };
633
631
  const finalizeShellSpecialErrorResponse = (response, specialError) => {
634
632
  if (response.status !== specialError.statusCode) return applyIneligibleRouteCachePolicy(response, options);
635
633
  const dynamicUsedDuringRender = consumeRenderDynamicUsage();
@@ -687,7 +685,6 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
687
685
  capturedRscDataRef,
688
686
  getInitialNavigationCacheMetadata: () => resolveInitialNavigationCacheMetadata(dynamicUsedDuringHtmlRender || peekDynamicUsage(), options.isPrerender === true ? requestCacheLifeForPrerender ?? options.peekRequestCacheLife?.() : options.peekRequestCacheLife?.()),
689
687
  fontData,
690
- hasCustomGlobalError: options.hasCustomGlobalError,
691
688
  navigationContext: options.getNavigationContext(),
692
689
  basePath: options.basePath,
693
690
  clientTraceMetadata: options.clientTraceMetadata,
@@ -701,7 +698,8 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
701
698
  scriptNonce: options.scriptNonce,
702
699
  sideStream: rscCapture.sideStream,
703
700
  ssrHandler,
704
- fallbackToErrorDocumentOnShellError: options.isPrerender === true && options.isSpeculativePrerender === true ? false : void 0,
701
+ fallbackToErrorDocumentOnShellError: !(options.isPrerender === true && options.isSpeculativePrerender === true),
702
+ isServerComponentError: rscErrorTracker.isCapturedError,
705
703
  waitForAllReady: shouldWaitForAllReady,
706
704
  isStaticGeneration: options.isPrerender === true,
707
705
  isForceStatic: options.isForceStatic,
@@ -740,7 +738,9 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
740
738
  if (htmlRender.response) {
741
739
  if (shellSpecialError && options.isProduction && options.isPrerender !== true && resolveEarlyResponseCacheControl(options) === null) return finalizeShellSpecialErrorResponse(htmlRender.response, shellSpecialError);
742
740
  if (shellSpecialError && prerenderedSpecialError) return finalizePrerenderedShellSpecialErrorResponse(htmlRender.response, shellSpecialError, prerenderedSpecialError);
743
- return applyIneligibleRouteCachePolicy(htmlRender.response, options);
741
+ const response = applyIneligibleRouteCachePolicy(htmlRender.response, options);
742
+ if (shellSpecialError) markPrerenderedDynamicShellSpecialError(response, shellSpecialError);
743
+ return response;
744
744
  }
745
745
  let htmlStream = htmlRender.htmlStream;
746
746
  if (!htmlStream) throw new Error("[vinext] Expected an HTML stream when no fallback response was returned");
@@ -2,10 +2,10 @@ import { VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_RSC_VARY_HEADER } from "./app-rsc-v
2
2
  import { NEXT_CACHE_TAGS_HEADER, NEXT_ROUTER_STALE_TIME_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_STALE_TIME_PENDING_HEADER, VINEXT_TIMING_HEADER } from "./headers.js";
3
3
  import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, buildRevalidateCacheControl } from "./cache-control.js";
4
4
  import { setCacheStateHeaders } from "./cache-headers.js";
5
+ import { applyPrerenderCacheLifeHeader } from "./prerender-cache-life-header.js";
5
6
  import { markFrameworkLinkHeaders } from "./app-response-header-provenance.js";
6
7
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
7
8
  import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
8
- import { applyPrerenderCacheLifeHeader } from "./prerender-cache-life-header.js";
9
9
  //#region src/server/app-page-response.ts
10
10
  function applyTimingHeader(headers, timing) {
11
11
  if (!timing) return;
@@ -3,6 +3,7 @@ import { fnv1a64 } from "../utils/hash.js";
3
3
  import { createInlineScriptTag, escapeHtmlAttr } from "./html.js";
4
4
  import { APP_LAYOUT_IDS_KEY, APP_ROOT_LAYOUT_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_STATIC_SIBLINGS_KEY, AppElementsWire, normalizeAppElementsSlotBindings } from "./app-elements-wire.js";
5
5
  import { APP_PREFETCH_LOADING_SHELL_MARKER_KEY, APP_STREAMING_METADATA_BODY_ID_PREFIX } from "./app-elements.js";
6
+ import { createBfcacheChildSegmentStateKey } from "./bfcache-identity.js";
6
7
  import { AppRouterScrollTarget } from "../shims/app-router-scroll.js";
7
8
  import { hashMountedLayoutIdentity } from "./app-mounted-layouts.js";
8
9
  import { BfcacheChildrenBoundary, BfcacheSegmentBoundary, Children as Children$1, ParallelSlot, Slot } from "../shims/slot.js";
@@ -876,8 +877,20 @@ function buildAppPageElements(options) {
876
877
  fallback: pageErrorComponent,
877
878
  children: routeChildren
878
879
  });
879
- const renderedTreePositions = isPrefetchLoadingShell ? orderedTreePositions.filter(includesPrefetchTreePosition) : orderedTreePositions;
880
- let childSegmentElementId = pageElementId;
880
+ const firstLayoutWithElementTreePosition = layoutEntries.find((entry) => getDefaultExport(entry.layoutModule) !== null)?.treePosition;
881
+ const keepsSegmentLevelLists = String(process.env.__NEXT_CACHE_COMPONENTS) === "true" && firstLayoutWithElementTreePosition !== void 0 && !options.route.childrenSlot && options.semanticPageIdentity === void 0;
882
+ const spineTreePositions = keepsSegmentLevelLists ? Array.from(/* @__PURE__ */ new Set([...orderedTreePositions, ...Array.from({ length: Math.max(0, routeSegments.length - firstLayoutWithElementTreePosition) }, (_, offset) => firstLayoutWithElementTreePosition + 1 + offset)])).sort((left, right) => left - right) : orderedTreePositions;
883
+ const renderedTreePositions = isPrefetchLoadingShell ? spineTreePositions.filter(includesPrefetchTreePosition) : spineTreePositions;
884
+ const firstLayoutBelowShell = keepsSegmentLevelLists && isPrefetchLoadingShell ? layoutEntries.find((entry) => !includesPrefetchTreePosition(entry.treePosition) && getDefaultExport(entry.layoutModule) !== null) : void 0;
885
+ let childSegmentElementId = firstLayoutBelowShell?.id ?? pageElementId;
886
+ let childSegmentTreePosition = firstLayoutBelowShell?.treePosition ?? routeSegments.length + 1;
887
+ const retainChildSegment = (treePosition, children) => treePosition === childSegmentTreePosition - 1 ? /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
888
+ id: childSegmentElementId,
889
+ children
890
+ }) : /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
891
+ stateKey: createBfcacheChildSegmentStateKey(resolveAppPageTemplateStateKey(routeSegments, treePosition, options.matchedParams)),
892
+ children
893
+ });
881
894
  for (let index = renderedTreePositions.length - 1; index >= 0; index--) {
882
895
  const treePosition = renderedTreePositions[index];
883
896
  const segmentResetKey = resolveRouteSegmentResetKey(treePosition);
@@ -927,11 +940,12 @@ function buildAppPageElements(options) {
927
940
  children: segmentChildren
928
941
  }, String(process.env.__NEXT_CACHE_COMPONENTS) === "true" ? void 0 : templateStateKey);
929
942
  }
943
+ const layoutHasElement = layoutEntry !== void 0 && getDefaultExport(layoutEntry.layoutModule) !== null;
944
+ if (keepsSegmentLevelLists && !layoutHasElement && treePosition > firstLayoutWithElementTreePosition && treePosition < childSegmentTreePosition) segmentChildren = retainChildSegment(treePosition, segmentChildren);
930
945
  if (!layoutEntry) {
931
946
  routeChildren = segmentChildren;
932
947
  continue;
933
948
  }
934
- const layoutHasElement = getDefaultExport(layoutEntry.layoutModule) !== null;
935
949
  const layoutIndex = layoutIndicesByTreePosition.get(treePosition) ?? -1;
936
950
  const segmentMap = { children: resolveAppPageLayoutSegmentProviderSegments(options.route.childrenRouteSegments ?? routeSegments, layoutEntry.treePosition, options.matchedParams) };
937
951
  for (const [slotKey, slot] of Object.entries(options.route.slots ?? {})) {
@@ -949,13 +963,16 @@ function buildAppPageElements(options) {
949
963
  children: layoutHasElement ? /* @__PURE__ */ jsx(Slot, {
950
964
  id: layoutEntry.id,
951
965
  parallelSlots: createAppPageParallelSlotEntries(layoutIndex, layoutEntries, options.route, getEffectiveSlotParams, resolveSlotOverride),
952
- children: /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
966
+ children: keepsSegmentLevelLists ? retainChildSegment(treePosition, segmentChildren) : /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
953
967
  id: childSegmentElementId,
954
968
  children: segmentChildren
955
969
  })
956
970
  }) : segmentChildren
957
971
  });
958
- if (layoutHasElement) childSegmentElementId = layoutEntry.id;
972
+ if (layoutHasElement) {
973
+ childSegmentElementId = layoutEntry.id;
974
+ childSegmentTreePosition = treePosition;
975
+ }
959
976
  }
960
977
  const globalErrorComponent = getErrorBoundaryExport(options.globalErrorModule);
961
978
  routeChildren = /* @__PURE__ */ jsx(GlobalErrorBoundary, {
@@ -96,10 +96,13 @@ export type AppPageSsrHandler = {
96
96
  /** Mirror inline Flight chunks into Next.js's `self.__next_f` transport, or start
97
97
  * mirroring once a function returns true. */
98
98
  mirrorNextFlight?: boolean | (() => boolean);
99
- /** When true, an SSR-phase-only shell render error resolves to the
100
- * default `__next_error__` error-document shell (with the original
101
- * flight payload and bootstrap) instead of rejecting. See handleSsr. */
99
+ /** When true, a shell render error other than a redirect, HTTP access
100
+ * fallback or bail-out resolves to the default `__next_error__`
101
+ * error-document shell (with the original flight payload and
102
+ * bootstrap) instead of rejecting. See handleSsr. */
102
103
  fallbackToErrorDocumentOnShellError?: boolean;
104
+ /** Whether the RSC render threw the given shell error. See handleSsr. */
105
+ isServerComponentError?: (error: unknown) => boolean;
103
106
  /** The parallel slot not-found boundaries that catch a shell error. See handleSsr. */
104
107
  resolveCaughtNotFoundBoundaries?: (error: unknown) => readonly string[] | null;
105
108
  dynamicStaleTimeSeconds?: number;
@@ -153,8 +156,11 @@ type RenderAppPageHtmlStreamOptions = {
153
156
  isForceStatic?: boolean;
154
157
  /** Production render that may be stored under a query-free key. */
155
158
  isCacheCandidate?: boolean;
156
- /** Override the default shell-error recovery decision passed to handleSsr. */
159
+ /** Whether handleSsr answers a shell error with the error document. Off by
160
+ * default: a boundary render that fails keeps rejecting. */
157
161
  fallbackToErrorDocumentOnShellError?: boolean;
162
+ /** Whether the RSC render threw the given shell error. See handleSsr. */
163
+ isServerComponentError?: (error: unknown) => boolean;
158
164
  /** The parallel slot not-found boundaries that catch a shell error. See handleSsr. */
159
165
  resolveCaughtNotFoundBoundaries?: (error: unknown) => readonly string[] | null;
160
166
  /** Dev-only: original server error to surface in the browser overlay. */
@@ -164,10 +170,6 @@ type RenderAppPageHtmlStreamOptions = {
164
170
  /** Mirror inline Flight chunks into Next.js's `self.__next_f` transport, or start
165
171
  * mirroring once a function returns true. */
166
172
  mirrorNextFlight?: boolean | (() => boolean);
167
- /** True when the app supplies a custom global-error.tsx. Disables the
168
- * default error-document shell fallback so SSR shell errors keep driving
169
- * the server-rendered global-error boundary re-render. */
170
- hasCustomGlobalError?: boolean;
171
173
  };
172
174
  type RenderAppPageHtmlResponseOptions = {
173
175
  clearRequestContext: () => void;
@@ -86,7 +86,8 @@ async function renderAppPageHtmlStream(options) {
86
86
  initialDevServerError: options.initialDevServerError,
87
87
  onSsrError: options.onSsrError,
88
88
  mirrorNextFlight: options.mirrorNextFlight,
89
- fallbackToErrorDocumentOnShellError: options.fallbackToErrorDocumentOnShellError ?? (options.waitForAllReady !== true && options.hasCustomGlobalError === false),
89
+ fallbackToErrorDocumentOnShellError: options.fallbackToErrorDocumentOnShellError,
90
+ isServerComponentError: options.isServerComponentError,
90
91
  resolveCaughtNotFoundBoundaries: options.resolveCaughtNotFoundBoundaries,
91
92
  dynamicStaleTimeSeconds: options.dynamicStaleTimeSeconds,
92
93
  getInitialNavigationCacheMetadata: options.getInitialNavigationCacheMetadata
@@ -32,7 +32,7 @@ function handleRequestStage(request, env, ctx, dispatchResponseStage) {
32
32
  callback: () => handleRequest(request, env, ctx, dispatchResponseStage, ctx?.assets).then((response) => applyCdnResponseIdentityHeaders(response, originalRequest)),
33
33
  getStatus: (response) => response?.status,
34
34
  headers: request.headers,
35
- isRsc: url.pathname.endsWith(".rsc") || request.headers.get("RSC") === "1",
35
+ isRsc: request.headers.get("RSC") === "1",
36
36
  method: request.method,
37
37
  target: url.pathname + url.search
38
38
  });
@@ -1,6 +1,7 @@
1
1
  import { isFullyBufferedBody } from "./fully-buffered-response.js";
2
2
  import { patternToNextFormat } from "../routing/route-validation.js";
3
3
  import { frameworkTracer } from "./tracer.js";
4
+ import { logRequestError } from "./request-error-log.js";
4
5
  import { getRouteCacheabilityCaptureOptions, getRouteCacheabilityDynamicReason, isRouteCacheabilityEvaluation, markRouteCacheabilityExplicitResponsePolicy, markRouteCacheabilityResponseBodyComplete, recordRouteCacheability } from "../shims/cacheability-classification.js";
5
6
  import { NEVER_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy } from "./cache-control.js";
6
7
  import { buildAppRouteMissIsrCacheControl } from "./isr-decision.js";
@@ -117,7 +118,7 @@ async function runAppRouteHandler(options) {
117
118
  onDynamicAccess() {
118
119
  options.markDynamicUsage();
119
120
  },
120
- requestMode: options.dynamicConfig === "force-static" || options.dynamicConfig === "error" ? options.dynamicConfig : "auto",
121
+ requestMode: options.dynamicConfig === "force-dynamic" || options.dynamicConfig === "force-static" || options.dynamicConfig === "error" ? options.dynamicConfig : "auto",
121
122
  signal: options.requestSignal,
122
123
  staticGenerationErrorMessage(expression) {
123
124
  return getAppRouteStaticGenerationErrorMessage(options.routePattern, expression);
@@ -339,7 +340,7 @@ async function executeAppRouteHandlerImpl(options) {
339
340
  }), options.middlewareContext, middlewareMergeOptions), shouldApplyDraftPolicy);
340
341
  return applyDraftModeCachePolicy(applyRouteHandlerMiddlewareContext(new Response(null, { status: specialError.statusCode }), options.middlewareContext, middlewareMergeOptions), shouldApplyDraftPolicy);
341
342
  }
342
- console.error("[vinext] Route handler error:", error);
343
+ logRequestError("[vinext] Route handler error:", error);
343
344
  await options.reportRequestError(error, {
344
345
  path: options.cleanPathname,
345
346
  method: options.request.method,
@@ -21,7 +21,7 @@ type NextUrlDynamicAccess = "nextUrl.search" | "nextUrl.searchParams" | "nextUrl
21
21
  type HostDerivedNextUrlDynamicAccess = "nextUrl.protocol" | "nextUrl.username" | "nextUrl.password" | "nextUrl.host" | "nextUrl.hostname" | "nextUrl.port" | DomainLocaleNextUrlDynamicAccess;
22
22
  type DomainLocaleNextUrlDynamicAccess = "nextUrl.locale" | "nextUrl.defaultLocale" | "nextUrl.domainLocale";
23
23
  type AppRouteDynamicRequestAccess = RequestDynamicAccess | NextUrlDynamicAccess;
24
- type AppRouteRequestMode = "auto" | "force-static" | "error";
24
+ type AppRouteRequestMode = "auto" | "force-dynamic" | "force-static" | "error";
25
25
  type TrackedAppRouteRequestOptions = {
26
26
  basePath?: string;
27
27
  i18n?: NextI18nConfig | null;
@@ -2,6 +2,7 @@ import { buildRequestHeadersFromMiddlewareResponse } from "../utils/middleware-r
2
2
  import { addBasePathToPathname } from "../utils/base-path.js";
3
3
  import { cloneRequestWithHeaders, cloneRequestWithUrl } from "./request-pipeline.js";
4
4
  import { NextRequest, RequestCookies, sealRequestCookies, sealRequestHeaders } from "../shims/server.js";
5
+ import { throwIfInsideCacheScope } from "../shims/internal/headers-state.js";
5
6
  import { BUILT_IN_REQUEST_PROPERTIES } from "./app-route-request-built-ins.js";
6
7
  //#region src/server/app-route-handler-runtime.ts
7
8
  const BUILT_IN_REQUEST_PROPERTY_MAP = new Map(BUILT_IN_REQUEST_PROPERTIES.map((property) => [property.key, property]));
@@ -150,7 +151,8 @@ function createTrackedAppRouteRequest(request, options = {}) {
150
151
  let didAccessDynamicRequest = false;
151
152
  const requestMode = options.requestMode ?? "auto";
152
153
  const nextConfig = buildNextConfig(options);
153
- const markDynamicAccess = (access) => {
154
+ const markDynamicAccess = (access, trackedByNext) => {
155
+ if (trackedByNext && requestMode !== "force-dynamic") throwIfInsideCacheScope(access);
154
156
  didAccessDynamicRequest = true;
155
157
  options.onDynamicAccess?.(access);
156
158
  };
@@ -184,7 +186,7 @@ function createTrackedAppRouteRequest(request, options = {}) {
184
186
  const wrapNextUrl = (nextUrl) => {
185
187
  return new Proxy(nextUrl, { get(target, prop) {
186
188
  if (isNextDynamicNextUrlProperty(prop) || isHostDerivedNextUrlProperty(prop)) {
187
- markDynamicAccess(`nextUrl.${String(prop)}`);
189
+ markDynamicAccess(`nextUrl.${String(prop)}`, isNextDynamicNextUrlProperty(prop));
188
190
  return bindMethodIfNeeded(Reflect.get(target, prop, target), target);
189
191
  }
190
192
  if (prop === "clone") return () => wrapNextUrl(target.clone());
@@ -252,7 +254,7 @@ function createTrackedAppRouteRequest(request, options = {}) {
252
254
  const accessCf = (read, forceStaticValue) => {
253
255
  if (requestMode === "force-static") return forceStaticValue;
254
256
  if (requestMode === "error") return throwStaticGenerationError("request.cf");
255
- markDynamicAccess("request.cf");
257
+ markDynamicAccess("request.cf", false);
256
258
  return read();
257
259
  };
258
260
  const controlledCfGetter = () => accessCf(sourceCfAccessor ? () => Reflect.apply(sourceCfAccessor, requestWithOverrides, []) : () => rawCfMetadata, void 0);
@@ -354,17 +356,19 @@ function createTrackedAppRouteRequest(request, options = {}) {
354
356
  return proxiedNextUrl;
355
357
  case "headers":
356
358
  case "cookies":
357
- case "ip":
358
- case "geo":
359
359
  case "url":
360
360
  case "body":
361
361
  case "blob":
362
- case "bytes":
363
362
  case "json":
364
363
  case "text":
365
364
  case "arrayBuffer":
366
365
  case "formData":
367
- markDynamicAccess(`request.${String(prop)}`);
366
+ markDynamicAccess(`request.${String(prop)}`, true);
367
+ return bindMethodIfNeeded(Reflect.get(target, prop, target), target);
368
+ case "ip":
369
+ case "geo":
370
+ case "bytes":
371
+ markDynamicAccess(`request.${String(prop)}`, false);
368
372
  return bindMethodIfNeeded(Reflect.get(target, prop, target), target);
369
373
  case "clone": return cloneTrackedRequest;
370
374
  default: return getTrackedRequestProperty(target, prop, receiver);
@@ -37,7 +37,7 @@ var app_router_entry_default = {
37
37
  callback: async () => applyCdnResponseIdentityHeaders(await handleRequest(request, env, ctx), request),
38
38
  getStatus: (response) => response?.status,
39
39
  headers: request.headers,
40
- isRsc: url.pathname.endsWith(".rsc") || request.headers.get("RSC") === "1",
40
+ isRsc: request.headers.get("RSC") === "1",
41
41
  method: request.method,
42
42
  target: url.pathname + url.search
43
43
  });
@@ -68,11 +68,6 @@ export declare function stripRscCacheBustingSearchParam(url: URL): void;
68
68
  * are dropped. Other requests keep no `_rsc`, because it selects nothing there.
69
69
  */
70
70
  export declare function keepOnlyValidatedRscCacheBustingSearchParam(url: URL, isRscRequest: boolean): void;
71
- /**
72
- * Remove a trailing `.rsc` suffix from a pathname. Returns the pathname
73
- * unchanged when the suffix is absent.
74
- */
75
- export declare function stripRscSuffix(pathname: string): string;
76
71
  export declare function createCanonicalRscRequestHeaders(deploymentId?: string | null | undefined): Headers;
77
72
  /** Headers for the deterministic loading-boundary prefetch representation. */
78
73
  export declare function createCanonicalLoadingShellRscRequestHeaders(deploymentId?: string | null | undefined): Headers;
@@ -96,13 +91,18 @@ export declare function canonicalizeLoadingShellRscRequestHeaders(headers: Heade
96
91
  * appended to the base instead of resolved against it.
97
92
  */
98
93
  export declare function parseRscRequestPath(href: string): URL;
94
+ /**
95
+ * A pathname as the start of a path-absolute reference. One that begins with
96
+ * "//" would serialize as a protocol-relative URL naming another host, so it
97
+ * keeps a "/." prefix, as the WHATWG URL serializer does. URL resolution drops
98
+ * the dot segment, so the reference still targets the same pathname on the
99
+ * origin it is resolved against.
100
+ */
101
+ export declare function toPathAbsolutePathname(pathname: string): string;
99
102
  /**
100
103
  * Serialize the path-absolute reference callers fetch against the app origin.
101
- * A pathname that begins with "//" would serialize as a protocol-relative URL
102
- * and send the request to another host, so it keeps a "/." prefix, as the
103
- * WHATWG URL serializer does. URL resolution drops the dot segment, so the
104
- * request still targets the same pathname on the app origin, as Next.js's
105
- * full-URL fetch does.
104
+ * A "//" pathname keeps its "/." prefix so the request still targets the same
105
+ * pathname on the app origin, as Next.js's full-URL fetch does.
106
106
  */
107
107
  export declare function formatRscRequestPath(url: URL): string;
108
108
  export declare function createRscRequestUrl(href: string, headers: Headers): Promise<string>;