vinext 1.4.0 → 1.4.2

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 (156) hide show
  1. package/README.md +21 -21
  2. package/dist/build/action-owner-manifest.js +1 -0
  3. package/dist/build/prerender.js +3 -2
  4. package/dist/check.js +4 -7
  5. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  6. package/dist/client/dev-error-overlay.d.ts +2 -0
  7. package/dist/client/dev-error-overlay.js +19 -3
  8. package/dist/config/config-matchers.js +4 -2
  9. package/dist/config/destination-params.d.ts +2 -1
  10. package/dist/config/destination-params.js +55 -2
  11. package/dist/config/next-config.d.ts +22 -1
  12. package/dist/config/next-config.js +49 -4
  13. package/dist/entries/app-rsc-entry.js +41 -19
  14. package/dist/entries/app-rsc-manifest.js +4 -0
  15. package/dist/entries/pages-server-entry.js +5 -1
  16. package/dist/index.js +44 -32
  17. package/dist/init-cloudflare.js +18 -2
  18. package/dist/plugins/css-target.d.ts +18 -0
  19. package/dist/plugins/css-target.js +93 -0
  20. package/dist/plugins/strip-server-exports.js +2 -1
  21. package/dist/routing/app-route-graph.d.ts +10 -0
  22. package/dist/routing/app-route-graph.js +15 -1
  23. package/dist/server/api-handler.d.ts +2 -0
  24. package/dist/server/api-handler.js +5 -6
  25. package/dist/server/app-action-forwarding.js +3 -1
  26. package/dist/server/app-browser-entry.js +28 -7
  27. package/dist/server/app-browser-rsc-redirect.js +3 -3
  28. package/dist/server/app-browser-server-action-client.js +2 -2
  29. package/dist/server/app-browser-visible-commit.js +2 -2
  30. package/dist/server/app-elements.d.ts +3 -0
  31. package/dist/server/app-elements.js +14 -1
  32. package/dist/server/app-fallback-renderer.js +2 -2
  33. package/dist/server/app-optimistic-routing.d.ts +12 -1
  34. package/dist/server/app-optimistic-routing.js +40 -19
  35. package/dist/server/app-page-boundary-render.js +7 -12
  36. package/dist/server/app-page-cache-render.js +1 -1
  37. package/dist/server/app-page-dispatch.d.ts +10 -2
  38. package/dist/server/app-page-dispatch.js +73 -31
  39. package/dist/server/app-page-element-builder.d.ts +9 -20
  40. package/dist/server/app-page-element-builder.js +40 -52
  41. package/dist/server/app-page-execution.js +9 -6
  42. package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
  43. package/dist/server/app-page-intercept-segment-config.js +90 -0
  44. package/dist/server/app-page-navigation-params.d.ts +23 -0
  45. package/dist/server/app-page-navigation-params.js +49 -0
  46. package/dist/server/app-page-render.d.ts +5 -3
  47. package/dist/server/app-page-render.js +31 -17
  48. package/dist/server/app-page-request.d.ts +24 -2
  49. package/dist/server/app-page-request.js +10 -10
  50. package/dist/server/app-page-response.js +1 -1
  51. package/dist/server/app-page-route-wiring.d.ts +10 -0
  52. package/dist/server/app-page-route-wiring.js +34 -13
  53. package/dist/server/app-page-segment-plan.js +9 -1
  54. package/dist/server/app-page-segment-state.d.ts +9 -0
  55. package/dist/server/app-page-segment-state.js +54 -1
  56. package/dist/server/app-page-stream.d.ts +10 -8
  57. package/dist/server/app-page-stream.js +2 -1
  58. package/dist/server/app-request-stage-independent-entry.js +1 -1
  59. package/dist/server/app-response-stage-entry.js +1 -0
  60. package/dist/server/app-route-handler-execution.js +3 -2
  61. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  62. package/dist/server/app-route-handler-runtime.js +11 -7
  63. package/dist/server/app-route-module-loader.d.ts +2 -0
  64. package/dist/server/app-route-module-loader.js +1 -0
  65. package/dist/server/app-router-entry.js +1 -1
  66. package/dist/server/app-rsc-cache-busting.d.ts +10 -10
  67. package/dist/server/app-rsc-cache-busting.js +27 -68
  68. package/dist/server/app-rsc-error-handler.js +1 -1
  69. package/dist/server/app-rsc-errors.js +4 -1
  70. package/dist/server/app-rsc-handler.d.ts +10 -1
  71. package/dist/server/app-rsc-handler.js +126 -103
  72. package/dist/server/app-rsc-request-normalization.d.ts +10 -12
  73. package/dist/server/app-rsc-request-normalization.js +11 -14
  74. package/dist/server/app-rsc-response-finalizer.d.ts +3 -0
  75. package/dist/server/app-rsc-response-finalizer.js +11 -1
  76. package/dist/server/app-rsc-response-stage.js +4 -12
  77. package/dist/server/app-rsc-route-matching.d.ts +14 -0
  78. package/dist/server/app-rsc-route-matching.js +42 -18
  79. package/dist/server/app-server-action-execution.js +24 -14
  80. package/dist/server/app-ssr-entry.d.ts +6 -0
  81. package/dist/server/app-ssr-entry.js +13 -8
  82. package/dist/server/app-worker-stages.d.ts +12 -3
  83. package/dist/server/app-worker-stages.js +37 -4
  84. package/dist/server/bfcache-identity.d.ts +7 -0
  85. package/dist/server/bfcache-identity.js +10 -1
  86. package/dist/server/cache-control.d.ts +2 -0
  87. package/dist/server/cache-control.js +3 -1
  88. package/dist/server/cacheability-manifest.js +1 -2
  89. package/dist/server/dev-origin-check.d.ts +2 -1
  90. package/dist/server/dev-origin-check.js +2 -1
  91. package/dist/server/dev-server.js +2 -1
  92. package/dist/server/image-local-patterns.d.ts +21 -0
  93. package/dist/server/image-local-patterns.js +59 -0
  94. package/dist/server/image-optimization.d.ts +28 -22
  95. package/dist/server/image-optimization.js +17 -2
  96. package/dist/server/isr-cache.js +4 -1
  97. package/dist/server/metadata-route-response.js +25 -7
  98. package/dist/server/middleware-matcher.js +19 -6
  99. package/dist/server/middleware-runtime.js +6 -2
  100. package/dist/server/multi-stage.d.ts +7 -3
  101. package/dist/server/nitro-public-files-plugin.js +20 -8
  102. package/dist/server/pages-data-route.d.ts +5 -1
  103. package/dist/server/pages-data-route.js +14 -6
  104. package/dist/server/pages-node-compat.d.ts +1 -1
  105. package/dist/server/pages-node-compat.js +2 -0
  106. package/dist/server/pages-page-data.js +2 -1
  107. package/dist/server/pages-page-handler.js +17 -6
  108. package/dist/server/pages-page-response.d.ts +9 -0
  109. package/dist/server/pages-page-response.js +18 -4
  110. package/dist/server/pages-request-pipeline.js +6 -5
  111. package/dist/server/pages-request-stage-entry.js +25 -4
  112. package/dist/server/pages-router-entry.js +3 -2
  113. package/dist/server/prod-server.d.ts +2 -2
  114. package/dist/server/prod-server.js +59 -17
  115. package/dist/server/request-error-log.d.ts +9 -0
  116. package/dist/server/request-error-log.js +13 -0
  117. package/dist/server/request-pipeline.d.ts +14 -4
  118. package/dist/server/request-pipeline.js +33 -15
  119. package/dist/server/response-stage-cacheability.d.ts +7 -0
  120. package/dist/server/response-stage-cacheability.js +6 -5
  121. package/dist/server/rsc-completion-metadata.js +3 -0
  122. package/dist/server/static-file-cache.d.ts +9 -0
  123. package/dist/server/static-file-cache.js +13 -2
  124. package/dist/server/worker-image-optimization.d.ts +2 -2
  125. package/dist/server/worker-image-optimization.js +18 -3
  126. package/dist/server/worker-utils.d.ts +8 -0
  127. package/dist/server/worker-utils.js +19 -2
  128. package/dist/shims/cache-request-state.d.ts +6 -6
  129. package/dist/shims/cache-request-state.js +6 -36
  130. package/dist/shims/cache-runtime.d.ts +3 -1
  131. package/dist/shims/cache-runtime.js +77 -47
  132. package/dist/shims/cache.d.ts +10 -5
  133. package/dist/shims/cache.js +35 -22
  134. package/dist/shims/client-hook-error.d.ts +2 -0
  135. package/dist/shims/client-hook-error.js +16 -1
  136. package/dist/shims/fetch-cache.js +6 -2
  137. package/dist/shims/headers.d.ts +2 -1
  138. package/dist/shims/headers.js +5 -3
  139. package/dist/shims/image-props.d.ts +6 -0
  140. package/dist/shims/image-props.js +39 -3
  141. package/dist/shims/image.js +3 -3
  142. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  143. package/dist/shims/link.js +5 -10
  144. package/dist/shims/navigation.js +2 -3
  145. package/dist/shims/request-state-types.d.ts +2 -1
  146. package/dist/shims/slot.d.ts +9 -2
  147. package/dist/shims/slot.js +76 -11
  148. package/dist/shims/unified-request-context.d.ts +6 -0
  149. package/dist/shims/unified-request-context.js +1 -0
  150. package/dist/utils/base-path.d.ts +7 -0
  151. package/dist/utils/base-path.js +11 -1
  152. package/dist/utils/cache-life-profiles.d.ts +33 -0
  153. package/dist/utils/cache-life-profiles.js +111 -0
  154. package/dist/utils/middleware-request-headers.d.ts +14 -3
  155. package/dist/utils/middleware-request-headers.js +54 -7
  156. package/package.json +1 -1
@@ -6,11 +6,9 @@ export type NormalizedRscRequest = {
6
6
  url: URL;
7
7
  /** Normalized pathname with basePath stripped. Used for all internal routing. */
8
8
  pathname: string;
9
- /** Pathname with `.rsc` suffix removed. Used for route matching and navigation context. */
10
- cleanPathname: string;
11
- /** Original encoded request pathname with basePath and `.rsc` removed. */
12
- requestCleanPathname: string;
13
- /** True when the request targets a canonical `.rsc` payload URL. */
9
+ /** Original encoded request pathname with basePath stripped. */
10
+ requestPathname: string;
11
+ /** True when the request selects the RSC payload with `RSC: 1`. */
14
12
  isRscRequest: boolean;
15
13
  /** Sanitized X-Vinext-Interception-Context header (null bytes stripped). null when absent. */
16
14
  interceptionContextHeader: string | null;
@@ -42,13 +40,13 @@ export type NormalizedRscRequest = {
42
40
  * 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)
43
41
  * 5. basePath check + strip — 404 when pathname lacks the basePath prefix.
44
42
  * `/__vinext/` bypasses this for internal prerender endpoints.
45
- * 6. RSC detection: `.rsc` suffix or Next-style `RSC: 1`. The internal
46
- * `_rsc` cache-busting query is validated separately so full-route Flight
47
- * responses do not share the canonical HTML URL in caches that ignore Vary.
48
- * 7. cleanPathname — pathname with `.rsc` suffix stripped
49
- * 8. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)
50
- * 9. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys
51
- * 10. Read semantic render mode for refresh/action payload rendering
43
+ * 6. RSC detection: `RSC: 1`, as in Next.js. A `.rsc` suffix is an ordinary
44
+ * part of the pathname. The internal `_rsc` cache-busting query is
45
+ * validated separately so full-route Flight responses do not share the
46
+ * canonical HTML URL in caches that ignore Vary.
47
+ * 7. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)
48
+ * 8. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys
49
+ * 9. Read semantic render mode for refresh/action payload rendering
52
50
  *
53
51
  * @returns A 400 or 404 Response for invalid or out-of-scope inputs,
54
52
  * or a NormalizedRscRequest for valid requests.
@@ -8,7 +8,7 @@ import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
8
8
  import { normalizePath } from "./normalize-path.js";
9
9
  import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
10
10
  import { APP_RSC_RENDER_MODE_NAVIGATION, APP_RSC_RENDER_MODE_PREFETCH_EMPTY, APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL, parseAppRscRenderMode } from "./app-rsc-render-mode.js";
11
- import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, stripRscSuffix } from "./app-rsc-cache-busting.js";
11
+ import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM } from "./app-rsc-cache-busting.js";
12
12
  import { normalizeInterceptionContextHeader } from "./app-interception-context-header.js";
13
13
  //#region src/server/app-rsc-request-normalization.ts
14
14
  function extractFlightRouterStatePath(value, depth = 0) {
@@ -78,13 +78,13 @@ function tryNormalizeComparablePathAndSearch(value, basePath, baseUrl) {
78
78
  * 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)
79
79
  * 5. basePath check + strip — 404 when pathname lacks the basePath prefix.
80
80
  * `/__vinext/` bypasses this for internal prerender endpoints.
81
- * 6. RSC detection: `.rsc` suffix or Next-style `RSC: 1`. The internal
82
- * `_rsc` cache-busting query is validated separately so full-route Flight
83
- * responses do not share the canonical HTML URL in caches that ignore Vary.
84
- * 7. cleanPathname — pathname with `.rsc` suffix stripped
85
- * 8. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)
86
- * 9. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys
87
- * 10. Read semantic render mode for refresh/action payload rendering
81
+ * 6. RSC detection: `RSC: 1`, as in Next.js. A `.rsc` suffix is an ordinary
82
+ * part of the pathname. The internal `_rsc` cache-busting query is
83
+ * validated separately so full-route Flight responses do not share the
84
+ * canonical HTML URL in caches that ignore Vary.
85
+ * 7. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)
86
+ * 8. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys
87
+ * 9. Read semantic render mode for refresh/action payload rendering
88
88
  *
89
89
  * @returns A 400 or 404 Response for invalid or out-of-scope inputs,
90
90
  * or a NormalizedRscRequest for valid requests.
@@ -110,9 +110,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, as
110
110
  requestPathname = stripBasePath(requestPathname, basePath);
111
111
  }
112
112
  }
113
- const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("RSC") === "1";
114
- const cleanPathname = stripRscSuffix(pathname);
115
- const requestCleanPathname = stripRscSuffix(requestPathname);
113
+ const isRscRequest = request.headers.get("RSC") === "1";
116
114
  const interceptionContextHeader = normalizeInterceptionContextHeader(request.headers.get(VINEXT_INTERCEPTION_CONTEXT_HEADER));
117
115
  const rawInterceptionId = request.headers.get(VINEXT_INTERCEPTION_ID_HEADER);
118
116
  if (rawInterceptionId !== null && (rawInterceptionId.length > 4096 || !rawInterceptionId.startsWith("interception:"))) return badRequestResponse();
@@ -124,7 +122,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, as
124
122
  const routerState = parsePrefetchRouterState(request.headers.get(NEXT_ROUTER_STATE_TREE_HEADER));
125
123
  if (nextUrl && routerState) {
126
124
  const targetUrl = new URL(url);
127
- targetUrl.pathname = cleanPathname;
125
+ targetUrl.pathname = pathname;
128
126
  targetUrl.searchParams.delete(VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM);
129
127
  const routerPathAndSearch = tryNormalizeComparablePathAndSearch(routerState.pathAndSearch, basePath, url);
130
128
  const nextPathAndSearch = tryNormalizeComparablePathAndSearch(nextUrl, basePath, url);
@@ -136,8 +134,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, as
136
134
  hadBasePath,
137
135
  url,
138
136
  pathname,
139
- cleanPathname,
140
- requestCleanPathname,
137
+ requestPathname,
141
138
  isRscRequest,
142
139
  interceptionContextHeader,
143
140
  interceptionIdHeader,
@@ -32,6 +32,9 @@ type FinalizeAppRscResponseOptions = {
32
32
  };
33
33
  /** Mark a response whose final target pipeline has already applied config headers. */
34
34
  export declare function markAppRscResponseConfigHeadersApplied(response: Response): Response;
35
+ /** Mark a Server Action redirect that streams its target's render, and so keeps its policy. */
36
+ export declare function markStreamedActionRedirectResponse(response: Response): Response;
37
+ export declare function isStreamedActionRedirectResponse(response: Response): boolean;
35
38
  /** Apply only the matching next.config headers for an App Router request. */
36
39
  export declare function applyAppRscConfigHeaders(headers: Headers, request: Request, options: FinalizeAppRscResponseOptions): Promise<void>;
37
40
  /**
@@ -25,6 +25,15 @@ function markAppRscResponseConfigHeadersApplied(response) {
25
25
  configHeadersAlreadyApplied.add(response);
26
26
  return response;
27
27
  }
28
+ const streamedActionRedirects = /* @__PURE__ */ new WeakSet();
29
+ /** Mark a Server Action redirect that streams its target's render, and so keeps its policy. */
30
+ function markStreamedActionRedirectResponse(response) {
31
+ streamedActionRedirects.add(response);
32
+ return response;
33
+ }
34
+ function isStreamedActionRedirectResponse(response) {
35
+ return streamedActionRedirects.has(response);
36
+ }
28
37
  /** Apply only the matching next.config headers for an App Router request. */
29
38
  async function applyAppRscConfigHeaders(headers, request, options) {
30
39
  if (!HAS_CONFIG_HEADERS || !options.configHeaders.length) return;
@@ -73,6 +82,7 @@ async function finalizeAppRscResponse(response, request, options) {
73
82
  if (!response.headers.has("Cache-Control")) {
74
83
  applyCdnResponseHeaders(response.headers, { cacheControl: "" });
75
84
  captureRouteCacheabilityResponsePolicy(captureCdnResponsePolicyHeaders(response.headers));
85
+ if (isStaticFileSignal(response)) response.headers.delete("Cache-Control");
76
86
  }
77
87
  if (configHeadersAlreadyApplied.has(response)) {
78
88
  normalizeExplicitNonCacheablePolicy(response.headers);
@@ -84,4 +94,4 @@ async function finalizeAppRscResponse(response, request, options) {
84
94
  return response;
85
95
  }
86
96
  //#endregion
87
- export { applyAppRscConfigHeaders, finalizeAppRscResponse, markAppRscResponseConfigHeadersApplied };
97
+ export { applyAppRscConfigHeaders, finalizeAppRscResponse, isStreamedActionRedirectResponse, markAppRscResponseConfigHeadersApplied, markStreamedActionRedirectResponse };
@@ -10,7 +10,7 @@ import { applyEffectiveRequestCookieHeader, getDraftModeCookieHeader, headersCon
10
10
  import { ensureFetchPatch, setCurrentFetchSoftTags } from "../shims/fetch-cache.js";
11
11
  import { traceFindPageComponents } from "./pages-execution-tracing.js";
12
12
  import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
13
- import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, hasRscCacheBustingSearchParam, stripRscCacheBustingSearchParam, stripRscSuffix } from "./app-rsc-cache-busting.js";
13
+ import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, hasRscCacheBustingSearchParam, stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
14
14
  import { traceAppPageRender } from "./app-page-tracing.js";
15
15
  import { buildPageCacheTags } from "./implicit-tags.js";
16
16
  import { pickRootParams, setRootParams } from "../shims/root-params.js";
@@ -48,13 +48,6 @@ function requestWithoutRscCacheBustingSearchParam(request) {
48
48
  stripRscCacheBustingSearchParam(url);
49
49
  return cloneRequestWithUrl(request, url.toString());
50
50
  }
51
- function requestWithoutRscSuffix(request) {
52
- const url = new URL(request.url);
53
- const pathname = stripRscSuffix(url.pathname);
54
- if (pathname === url.pathname) return request;
55
- url.pathname = pathname;
56
- return cloneRequestWithUrl(request, url.toString());
57
- }
58
51
  function pathnameForResolvedUrl(resolvedUrl) {
59
52
  return resolvedUrl.split("#", 1)[0].split("?", 1)[0];
60
53
  }
@@ -192,9 +185,8 @@ async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, sta
192
185
  setRootParams(rootParams);
193
186
  if (props.kind === "app-route-handler") {
194
187
  setCurrentFetchSoftTags(buildPageCacheTags(props.cleanPathname, [], [...route.routeSegments], "route"));
195
- const normalizedUserlandRequest = requestWithoutRscSuffix(request);
196
- const userlandRequest = requestWithoutRscCacheBustingSearchParam(normalizedUserlandRequest);
197
- const routeHandlerRequest = isEdgeRouteHandler(route.routeHandler) ? userlandRequest : normalizedUserlandRequest;
188
+ const userlandRequest = requestWithoutRscCacheBustingSearchParam(request);
189
+ const routeHandlerRequest = isEdgeRouteHandler(route.routeHandler) ? userlandRequest : request;
198
190
  const routeHandlerUrl = new URL(routeHandlerRequest.url);
199
191
  const internalRscValues = isEdgeRouteHandler(route.routeHandler) ? [] : routeHandlerUrl.searchParams.getAll(VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM);
200
192
  routeHandlerUrl.search = searchParams.toString();
@@ -225,7 +217,7 @@ async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, sta
225
217
  handlerStart: process.env.NODE_ENV !== "production" ? performance.now() : 0,
226
218
  interceptionContext: normalized.interceptionContextHeader,
227
219
  interceptionId: normalized.interceptionIdHeader,
228
- interceptionPathname: props.matchKind === "resolved" ? props.cleanPathname : normalized.requestCleanPathname,
220
+ interceptionPathname: props.matchKind === "resolved" ? props.cleanPathname : normalized.requestPathname,
229
221
  isProgressiveActionRender: false,
230
222
  isRscRequest: normalized.isRscRequest,
231
223
  middlewareContext,
@@ -101,6 +101,7 @@ type AppRscRouteForMatching = {
101
101
  pattern: string;
102
102
  patternParts: string[];
103
103
  routeHandler?: unknown;
104
+ routeSegments?: readonly string[] | null;
104
105
  slots?: Record<string, AppRscSlotForMatching>;
105
106
  siblingIntercepts?: AppRscSiblingInterceptForMatching[];
106
107
  };
@@ -108,6 +109,14 @@ type AppRscInterceptMatch = AppRscInterceptLookupEntry & {
108
109
  matchedParams: AppRscRouteParams;
109
110
  sourceRouteIsConcrete: boolean;
110
111
  sourceMatchedParams: AppRscRouteParams;
112
+ /**
113
+ * The source route and params the request's interception context names,
114
+ * which the request handler authorizes. They differ from the rendered
115
+ * `sourceRouteIndex` only for a sibling intercept opened from a deeper
116
+ * source, which renders the intercepting route itself.
117
+ */
118
+ authorizationSourceRouteIndex: number;
119
+ authorizationSourceParams: AppRscRouteParams;
111
120
  };
112
121
  type AppRscInterceptLoadState = {
113
122
  page: unknown;
@@ -120,6 +129,11 @@ type AppRscInterceptLookupEntry = {
120
129
  interceptionId: string | null;
121
130
  interceptionGraphId: string | null;
122
131
  sourceRouteIndex: number;
132
+ /**
133
+ * A sibling intercept whose owner route's page sits in the directory of the
134
+ * interception marker, so the owner is the intercepting route itself.
135
+ */
136
+ ownerIsInterceptingRoute: boolean;
123
137
  slotKey: string;
124
138
  targetPattern: string;
125
139
  targetPatternParts: string[];
@@ -1,7 +1,8 @@
1
- import { decodeMatchedParams, decodeRouteSegment, splitPathSegments, splitPathnameForRouteMatch } from "../routing/utils.js";
1
+ import { decodeMatchedParams, decodeRouteSegment, splitPathSegments } from "../routing/utils.js";
2
2
  import { createAppRouteGraphInterceptionId } from "../routing/app-route-ids.js";
3
3
  import { buildRouteTrie, trieMatchRaw } from "../routing/route-trie.js";
4
4
  import { extractRawRoutePatternParams, matchRoutePatternPrefix, matchRoutePatternRaw } from "../routing/route-pattern.js";
5
+ import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
5
6
  import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
6
7
  //#region src/server/app-rsc-route-matching.ts
7
8
  /**
@@ -13,10 +14,19 @@ const SIBLING_PAGE_INTERCEPT_SLOT_KEY = "__vinext_page_intercept";
13
14
  function createRouteParams() {
14
15
  return Object.create(null);
15
16
  }
16
- function appRscPathnameParts(pathname, isNormalized = false) {
17
+ /**
18
+ * Whether each encoded raw segment is exactly the spelling the URL parser, as
19
+ * a browser runs it, gives its decoded form: `caf%C3%A9` for `café`, but `q&a`
20
+ * for `q&a`. `%61dmin`, `caf%c3%a9`, `%63af%C3%A9` and `q%26a` are other
21
+ * spellings of a page, which middleware never compared as its pathname.
22
+ */
23
+ function isCanonicalPathEncoding(rawParts, decodedParts) {
24
+ return rawParts.every((rawPart, index) => !rawPart.includes("%") || rawPart === new URL(`/${decodedParts[index]}`, "http://n").pathname.slice(1));
25
+ }
26
+ function appRscPathnameParts(pathname) {
17
27
  const pathOnly = pathname.split("?")[0];
18
28
  const normalizedPathname = pathOnly === "/" ? "/" : pathOnly.replace(/\/$/, "");
19
- return isNormalized ? splitPathSegments(normalizedPathname) : splitPathnameForRouteMatch(normalizedPathname);
29
+ return splitPathSegments(normalizedPathname);
20
30
  }
21
31
  function isAppRouteHandlerRoute(route) {
22
32
  return route.routeHandler != null || typeof route.__loadRouteHandler === "function";
@@ -49,8 +59,10 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
49
59
  const result = matchRawRequestParts(rawParts);
50
60
  if (!result) return null;
51
61
  if (decodedPathnameRoutes.size > 0 && result.route.patternParts.some((part) => part.startsWith(":")) && !isAppRouteHandlerRoute(result.route) && rawParts.some((part) => part.includes("%"))) {
52
- const route = decodedPathnameRoutes.get(rawParts.map(decodeRouteSegment).join("/"));
53
- if (route && route !== result.route) return {
62
+ const decodedParts = rawParts.map(decodeRouteSegment);
63
+ const decodedPathname = decodedParts.join("/");
64
+ const route = decodedPathnameRoutes.get(decodedPathname);
65
+ if (route && route !== result.route && (isCanonicalPathEncoding(rawParts, decodedParts) || getRenderedConcreteUrlPathsForRoute(route.pattern)?.has(`/${decodedPathname}`) === true)) return {
54
66
  route,
55
67
  params: createRouteParams()
56
68
  };
@@ -62,23 +74,18 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
62
74
  return interceptionIds.has(interceptionId);
63
75
  },
64
76
  matchRoute(url) {
65
- const rawParts = appRscPathnameParts(url, true);
66
- const result = trieMatchRaw(routeTrie, appRscPathnameParts(url, false));
67
- if (!result) return null;
68
- result.params = extractRawRoutePatternParams(result.route.patternParts, rawParts);
69
- normalizeMatchedParamsForRoute(result);
70
- return result;
77
+ return matchRequestParts(appRscPathnameParts(url));
71
78
  },
72
79
  matchRequestRoute(url) {
73
- return matchRequestParts(appRscPathnameParts(url, true));
80
+ return matchRequestParts(appRscPathnameParts(url));
74
81
  },
75
82
  matchRawRequestRoute(url) {
76
- return matchRawRequestParts(appRscPathnameParts(url, true));
83
+ return matchRawRequestParts(appRscPathnameParts(url));
77
84
  },
78
85
  findIntercept(pathname, sourcePathname = null, interceptionId = null) {
79
86
  if (sourcePathname === null) return null;
80
- const urlParts = appRscPathnameParts(pathname, true);
81
- const sourceParts = appRscPathnameParts(sourcePathname, true);
87
+ const urlParts = appRscPathnameParts(pathname);
88
+ const sourceParts = appRscPathnameParts(sourcePathname);
82
89
  const matchedSourceRoute = matchRawRequestParts(sourceParts);
83
90
  for (const entry of interceptLookup) {
84
91
  if (interceptionId !== null && entry.interceptionId !== interceptionId) continue;
@@ -92,15 +99,21 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
92
99
  if (sourceRoute && isAppRouteHandlerRoute(sourceRoute)) continue;
93
100
  const matchedSourceParams = concreteSourceRoute ? concreteSourceRoute.params : sourceRoute ? matchSlotOwnerSourceParams(sourceParts, sourceRoute.patternParts, entry.sourceMatchPatternParts !== null) : null;
94
101
  if (!concreteSourceRoute && matchedSourceParams) canonicalizeAppPageParams(matchedSourceParams);
102
+ const ownerRoute = routes[entry.sourceRouteIndex];
103
+ const rendersOwnerRoute = entry.ownerIsInterceptingRoute && concreteSourceRouteIndex !== entry.sourceRouteIndex && ownerRoute !== void 0;
104
+ const renderedSourceParams = rendersOwnerRoute ? matchSlotOwnerSourceParams(sourceParts, ownerRoute.patternParts, true) : matchedSourceParams;
105
+ if (rendersOwnerRoute && renderedSourceParams) canonicalizeAppPageParams(renderedSourceParams);
95
106
  if (matchedSourceParams === null && entry.sourceMatchPatternParts === null) continue;
96
- const sourceParams = matchedSourceParams && entry.sourceMatchPatternParts !== null ? pickPatternParams(matchedSourceParams, entry.sourceMatchPatternParts) : matchedSourceParams ?? createRouteParams();
107
+ const sourceParams = renderedSourceParams && entry.sourceMatchPatternParts !== null ? pickPatternParams(renderedSourceParams, entry.sourceMatchPatternParts) : renderedSourceParams ?? createRouteParams();
97
108
  return {
98
109
  ...entry,
99
110
  page: entry.__loadState.page,
100
111
  sourceRouteIsConcrete: concreteSourceRoute !== null,
101
- sourceRouteIndex: concreteSourceRouteIndex,
112
+ sourceRouteIndex: rendersOwnerRoute ? entry.sourceRouteIndex : concreteSourceRouteIndex,
102
113
  matchedParams: mergeMatchedParams(sourceParams, params),
103
- sourceMatchedParams: matchedSourceParams ?? createRouteParams()
114
+ sourceMatchedParams: renderedSourceParams ?? createRouteParams(),
115
+ authorizationSourceRouteIndex: concreteSourceRouteIndex,
116
+ authorizationSourceParams: matchedSourceParams ?? createRouteParams()
104
117
  };
105
118
  }
106
119
  return null;
@@ -108,6 +121,15 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
108
121
  };
109
122
  }
110
123
  /**
124
+ * Whether a sibling intercept's owner route is the intercepting route: its
125
+ * page lives in the directory that holds the interception marker. An owner
126
+ * found elsewhere stands in for a marker directory without a page.
127
+ */
128
+ function isInterceptingRoute(routeSegments, sourcePageSegments) {
129
+ if (!routeSegments || !sourcePageSegments) return false;
130
+ return sourcePageSegments.findIndex((segment) => /^\(\.+\)/.test(segment)) === routeSegments.length && routeSegments.every((segment, index) => segment === sourcePageSegments[index]);
131
+ }
132
+ /**
111
133
  * Params for the slot owner when interception falls back to it instead of a
112
134
  * concrete descendant source route. The owner is what renders, and
113
135
  * `matchInterceptRoute` reads the promoted route's params solely from these,
@@ -195,6 +217,7 @@ function createInterceptLookup(routes) {
195
217
  interceptionId: typeof slotModule.id === "string" && sourceMatchPattern !== null ? createAppRouteGraphInterceptionId(slotModule.id, sourceMatchPattern, intercept.targetPattern) : null,
196
218
  interceptionGraphId: null,
197
219
  sourceRouteIndex: ownerRouteIndex,
220
+ ownerIsInterceptingRoute: false,
198
221
  slotKey,
199
222
  slotId: typeof slotModule.id === "string" ? slotModule.id : null,
200
223
  targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
@@ -246,6 +269,7 @@ function createInterceptLookup(routes) {
246
269
  interceptionId: typeof intercept.id === "string" ? intercept.id : null,
247
270
  interceptionGraphId: typeof intercept.id === "string" ? intercept.id : null,
248
271
  sourceRouteIndex: routeIndex,
272
+ ownerIsInterceptingRoute: !isAppRouteHandlerRoute(route) && isInterceptingRoute(route.routeSegments, intercept.sourcePageSegments),
249
273
  slotKey: SIBLING_PAGE_INTERCEPT_SLOT_KEY,
250
274
  slotId: typeof intercept.slotId === "string" ? intercept.slotId : null,
251
275
  targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
@@ -11,6 +11,8 @@ import { internalServerErrorResponse, payloadTooLargeResponse } from "./http-err
11
11
  import { attachRequestCfMetadata, validateCsrfOrigin, validateServerActionPayload } from "./request-pipeline.js";
12
12
  import { splitPathSegments } from "../routing/utils.js";
13
13
  import { getRequestExecutionContext } from "../shims/request-context.js";
14
+ import { logRequestError } from "./request-error-log.js";
15
+ import { SERVER_ACTION_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
14
16
  import { shareIsolatePromise } from "./isolate-shared-promise.js";
15
17
  import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
16
18
  import { isDraftModeRequest, setHeadersContext } from "../shims/headers.js";
@@ -20,17 +22,18 @@ import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
20
22
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
21
23
  import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, createRscRequestUrl, stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
22
24
  import { getSetCookieName } from "./cookie-utils.js";
25
+ import { markAppRscResponseConfigHeadersApplied, markStreamedActionRedirectResponse } from "./app-rsc-response-finalizer.js";
23
26
  import { createServerActionNotFoundResponse, getServerActionNotFoundMessage, isServerActionNotFoundError } from "./server-action-not-found.js";
24
27
  import { applyEdgeRuntimeHeader } from "./app-page-response.js";
25
28
  import { getNextErrorDigest, parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
26
- import { hasDigest } from "./app-rsc-errors.js";
27
- import { resolveAppPageNavigationParams } from "./app-page-element-builder.js";
29
+ import { resolveAppPageNavigationParams } from "./app-page-navigation-params.js";
28
30
  import "./app-page-stream.js";
31
+ import { hasDigest } from "./app-rsc-errors.js";
29
32
  import { buildAppPageTags } from "./implicit-tags.js";
33
+ import { resolveAppPageInterceptSegmentConfig } from "./app-page-intercept-segment-config.js";
30
34
  import { resolveAppPageActionRerenderTarget } from "./app-page-request.js";
31
35
  import { createRootParamsUsageController, runWithRootParamsUsage } from "../shims/root-params.js";
32
36
  import { createStaticGenerationHeadersContext } from "./app-static-generation.js";
33
- import { markAppRscResponseConfigHeadersApplied } from "./app-rsc-response-finalizer.js";
34
37
  //#region src/server/app-server-action-execution.ts
35
38
  /**
36
39
  * Run a server-reference load or decode outside every request scope.
@@ -509,7 +512,10 @@ function createRejectedActionResult(error) {
509
512
  * Next.js, a thrown string is not logged in development.
510
513
  */
511
514
  function logRejectedActionResult(error) {
512
- if (process.env.NODE_ENV === "production" || hasDigest(error)) console.error("[vinext] Server action error:", error);
515
+ if (process.env.NODE_ENV === "production" || hasDigest(error)) {
516
+ if (typeof error === "string") console.error("[vinext] Server action error:", error);
517
+ else logRequestError("[vinext] Server action error:", error);
518
+ }
513
519
  }
514
520
  function getActionHttpFallbackStatus(error) {
515
521
  const digest = getNextErrorDigest(error);
@@ -520,7 +526,7 @@ function getActionHttpFallbackStatus(error) {
520
526
  }
521
527
  function createServerActionErrorResponse(error, options) {
522
528
  options.getAndClearPendingCookies();
523
- console.error("[vinext] Server action error:", error);
529
+ logRequestError("[vinext] Server action error:", error);
524
530
  options.reportRequestError(error, {
525
531
  path: options.cleanPathname,
526
532
  method: options.request.method,
@@ -627,7 +633,7 @@ async function handleProgressiveServerActionRequest(options) {
627
633
  actionFailed = true;
628
634
  actionFailedWithError = getActionHttpFallbackStatus(error) === null && !isServerActionNotFoundError(error, null);
629
635
  if (actionFailedWithError) {
630
- console.error("[vinext] Server action error:", error);
636
+ logRequestError("[vinext] Server action error:", error);
631
637
  options.reportRequestError(error, {
632
638
  path: options.cleanPathname,
633
639
  method: options.request.method,
@@ -655,7 +661,7 @@ async function handleProgressiveServerActionRequest(options) {
655
661
  options.clearRequestContext();
656
662
  const headers = new Headers();
657
663
  mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders);
658
- headers.set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
664
+ headers.set("Cache-Control", SERVER_ACTION_CACHE_CONTROL);
659
665
  for (const cookie of actionPendingCookies) headers.append("Set-Cookie", cookie);
660
666
  if (actionDraftCookie) headers.append("Set-Cookie", actionDraftCookie);
661
667
  return internalServerErrorResponse(process.env.NODE_ENV === "production" ? void 0 : "Server action failed: " + getServerActionFailureMessage(actionError), { headers });
@@ -699,7 +705,7 @@ async function handleProgressiveServerActionRequest(options) {
699
705
  });
700
706
  getAndClearActionRevalidationKind();
701
707
  options.getAndClearPendingCookies();
702
- console.error("[vinext] Server action payload parsing error:", error);
708
+ logRequestError("[vinext] Server action payload parsing error:", error);
703
709
  options.reportRequestError(error, {
704
710
  path: options.cleanPathname,
705
711
  method: options.request.method,
@@ -875,6 +881,7 @@ async function handleServerActionRscRequest(options) {
875
881
  applyEdgeRuntimeHeader(redirectHeaders, options.isEdgeRuntime);
876
882
  mergeActionRedirectSourceHeaders(redirectHeaders, options.sourceConfigHeaders ?? null);
877
883
  mergeActionRedirectSourceHeaders(redirectHeaders, options.middlewareHeaders);
884
+ applyCdnResponseHeaders(redirectHeaders, { cacheControl: SERVER_ACTION_CACHE_CONTROL });
878
885
  applyRscCompatibilityIdHeader(redirectHeaders);
879
886
  const actionRedirectUrl = applyActionRedirectBasePath(actionRedirect.url, options.basePath ?? "");
880
887
  redirectHeaders.set(ACTION_REDIRECT_HEADER, actionRedirectUrl);
@@ -934,10 +941,10 @@ async function handleServerActionRscRequest(options) {
934
941
  targetPathname,
935
942
  targetRoute: targetMatch?.route ?? null
936
943
  }) ? 200 : 303;
937
- return markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
944
+ return markStreamedActionRedirectResponse(markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
938
945
  status: redirectResponseStatus,
939
946
  headers: redirectHeaders
940
- }, options.clearRequestContext));
947
+ }, options.clearRequestContext)));
941
948
  }
942
949
  const actionPendingCookies = dedupePendingCookies(options.getAndClearPendingCookies());
943
950
  const actionDraftCookie = options.getDraftModeCookieHeader();
@@ -972,6 +979,7 @@ async function handleServerActionRscRequest(options) {
972
979
  let element;
973
980
  let errorPattern = match ? match.route.pattern : options.cleanPathname;
974
981
  let renderedPathAndSearch = null;
982
+ let actionRerenderIsEdgeRuntime = options.isEdgeRuntime;
975
983
  const actionRerenderIsRscRequest = true;
976
984
  if (match) {
977
985
  const { route: actionRoute, params: actionParams } = match;
@@ -987,7 +995,9 @@ async function handleServerActionRscRequest(options) {
987
995
  });
988
996
  const resolvedActionNavigationParams = resolveAppPageNavigationParams(actionRerenderTarget.route, actionRerenderTarget.navigationParams, options.cleanPathname, actionRerenderTarget.interceptOpts);
989
997
  await options.ensureRouteLoaded?.(actionRerenderTarget.route);
990
- const actionRerenderDynamicConfig = options.resolveRouteDynamicConfig?.(actionRerenderTarget.route);
998
+ const actionInterceptConfig = actionRerenderTarget.interceptOpts ? resolveAppPageInterceptSegmentConfig(actionRerenderTarget.route, actionRerenderTarget.interceptOpts) : null;
999
+ if (actionInterceptConfig) actionRerenderIsEdgeRuntime = actionInterceptConfig.isEdgeRuntime;
1000
+ const actionRerenderDynamicConfig = actionInterceptConfig ? actionInterceptConfig.dynamicConfig : options.resolveRouteDynamicConfig?.(actionRerenderTarget.route);
991
1001
  const actionRerenderSearchParams = prepareActionPageRerenderContext({
992
1002
  draftModeCookie: actionDraftCookie,
993
1003
  draftModeSecret: options.draftModeSecret,
@@ -1003,8 +1013,8 @@ async function handleServerActionRscRequest(options) {
1003
1013
  });
1004
1014
  const renderedSearch = options.searchParams.toString();
1005
1015
  renderedPathAndSearch = renderedSearch ? `${options.cleanPathname}?${renderedSearch}` : options.cleanPathname;
1006
- setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
1007
- setCurrentFetchRevalidate(options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
1016
+ setCurrentFetchCacheMode(actionInterceptConfig ? actionInterceptConfig.fetchCacheMode : options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
1017
+ setCurrentFetchRevalidate(actionInterceptConfig ? actionInterceptConfig.revalidateSeconds : options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
1008
1018
  setCurrentForceDynamicFetchDefault(actionRerenderDynamicConfig === "force-dynamic");
1009
1019
  setCurrentFetchSoftTags(buildServerActionPageTags(actionRerenderTarget.route, options.cleanPathname));
1010
1020
  const buildActionRerenderElement = () => options.buildPageElement({
@@ -1043,7 +1053,7 @@ async function handleServerActionRscRequest(options) {
1043
1053
  "Content-Type": VINEXT_RSC_CONTENT_TYPE,
1044
1054
  Vary: VINEXT_RSC_VARY_HEADER
1045
1055
  });
1046
- applyEdgeRuntimeHeader(actionHeaders, options.isEdgeRuntime);
1056
+ applyEdgeRuntimeHeader(actionHeaders, actionRerenderIsEdgeRuntime);
1047
1057
  mergeMiddlewareResponseHeaders(actionHeaders, options.middlewareHeaders);
1048
1058
  applyRscCompatibilityIdHeader(actionHeaders);
1049
1059
  setActionRevalidatedHeader(actionHeaders, actionRevalidationKind);
@@ -72,6 +72,12 @@ export declare function handleSsr(rscStream: ReadableStream<Uint8Array>, navCont
72
72
  */
73
73
  isCacheCandidate?: boolean;
74
74
  fallbackToErrorDocumentOnShellError?: boolean;
75
+ /**
76
+ * Whether the RSC render threw the given shell error. As in Next.js, in
77
+ * dev the error document carries only an error that the HTML render alone
78
+ * threw, for the overlay: the browser reports the others itself.
79
+ */
80
+ isServerComponentError?: (error: unknown) => boolean;
75
81
  /**
76
82
  * The parallel slot not-found boundaries that catch the error that
77
83
  * rejected the shell, or null when none catches it. The document then
@@ -2,6 +2,7 @@ import { deferUntilStreamConsumed } from "./defer-until-stream-consumed.js";
2
2
  import { notFoundResponse } from "./http-error-responses.js";
3
3
  import { isOpenRedirectShaped, repeatedSlashRedirectResponse } from "./open-redirect.js";
4
4
  import "./server-globals.js";
5
+ import { isNavigationSignalError } from "../utils/navigation-signal.js";
5
6
  import { isPprFallbackShellAbortError } from "../shims/ppr-fallback-shell.js";
6
7
  import { onRenderDynamicLatched } from "../shims/internal/headers-state.js";
7
8
  import { AppRouterContext } from "../shims/internal/app-router-context.js";
@@ -15,6 +16,7 @@ import { createNavigationRuntimeRenderedSearchScript, createNavigationRuntimeRsc
15
16
  import { AppElementsWire } from "./app-elements-wire.js";
16
17
  import "./app-elements.js";
17
18
  import { createInitialBfcacheMaps } from "./app-bfcache-identity.js";
19
+ import { isBailoutToCSRError, isDynamicServerError } from "../shims/navigation-errors.js";
18
20
  import { ServerInsertedHTMLContext, clearServerInsertedHTML, getBfcacheIdMapContext, registerServerInsertedHTMLCallback, renderServerInsertedHTML, setNavigationContext } from "../shims/navigation-context-state.js";
19
21
  import "../shims/navigation-server.js";
20
22
  import { BfcacheIdentityMapContext } from "../shims/slot.js";
@@ -71,13 +73,14 @@ function createUtf8Stream(html) {
71
73
  controller.close();
72
74
  } });
73
75
  }
74
- function buildBootstrapModuleScript(bootstrapModuleUrl, nonce) {
76
+ function buildBootstrapModuleScript(bootstrapModuleUrl, nonce, crossOrigin) {
75
77
  if (!bootstrapModuleUrl) return "";
76
- return `<script type="module"${createNonceAttribute(nonce)} src="` + escapeHtmlAttr(bootstrapModuleUrl) + "\" id=\"_R_\" async=\"\" fetchPriority=\"low\"><\/script>";
78
+ const crossOriginAttribute = crossOrigin === void 0 ? "" : ` crossorigin="${crossOrigin === "use-credentials" ? "use-credentials" : ""}"`;
79
+ return `<script type="module"${createNonceAttribute(nonce)} src="` + escapeHtmlAttr(bootstrapModuleUrl) + `"${crossOriginAttribute} id="_R_" async="" fetchPriority="low"><\/script>`;
77
80
  }
78
- function renderSsrErrorDocumentShell(bootstrapModuleUrl, nonce) {
79
- const html = renderToStaticMarkup(createElement(DefaultGlobalError, { error: null })).replace("<style>", "<style data-vinext-error-shell-style=\"\">");
80
- const bootstrapScript = buildBootstrapModuleScript(bootstrapModuleUrl, nonce);
81
+ function renderSsrErrorDocumentShell(bootstrapModuleUrl, nonce, crossOrigin) {
82
+ const html = renderToStaticMarkup(createElement(DefaultGlobalError, { error: null })).replace("<head>", "<head><meta name=\"robots\" content=\"noindex\"/>").replace("<style>", "<style data-vinext-error-shell-style=\"\">");
83
+ const bootstrapScript = buildBootstrapModuleScript(bootstrapModuleUrl, nonce, crossOrigin);
81
84
  if (!bootstrapScript) return createUtf8Stream(`<!DOCTYPE html>${html}`);
82
85
  const documentClose = "</body></html>";
83
86
  if (!html.endsWith(documentClose)) return createUtf8Stream(`<!DOCTYPE html>${html}${bootstrapScript}`);
@@ -292,6 +295,7 @@ async function handleSsr(rscStream, navContext, fontData, options) {
292
295
  let htmlStream;
293
296
  let renderComplete = Promise.resolve();
294
297
  let shellErrorRecovered = false;
298
+ let ssrOnlyShellError;
295
299
  let caughtShellError;
296
300
  let caughtBoundaries = null;
297
301
  let shouldDelayInitialHtmlPull = false;
@@ -355,10 +359,11 @@ async function handleSsr(rscStream, navContext, fontData, options) {
355
359
  shouldDelayInitialHtmlPull = options?.waitForAllReady !== true;
356
360
  caughtShellError = error;
357
361
  htmlStream = caughtHtmlStream;
358
- } else if (options?.fallbackToErrorDocumentOnShellError !== true || options?.waitForAllReady === true || typeof error?.digest === "string") throw error;
362
+ } else if (options?.fallbackToErrorDocumentOnShellError !== true || isNavigationSignalError(error) || isBailoutToCSRError(error) || isDynamicServerError(error)) throw error;
359
363
  else {
360
364
  shellErrorRecovered = true;
361
- htmlStream = renderSsrErrorDocumentShell(bootstrapModuleUrl, options?.scriptNonce);
365
+ if (options?.isServerComponentError?.(error) !== true) ssrOnlyShellError = error;
366
+ htmlStream = renderSsrErrorDocumentShell(bootstrapModuleUrl, options?.scriptNonce, assetCrossOrigin);
362
367
  }
363
368
  }
364
369
  }
@@ -385,7 +390,7 @@ async function handleSsr(rscStream, navContext, fontData, options) {
385
390
  const getInsertedHTML = () => {
386
391
  const insertedHTML = renderInsertedHtml(renderServerInsertedHTML());
387
392
  const errorMetaHTML = errorMetaRenderer.flush();
388
- const initialDevServerErrorHTML = createInitialDevServerErrorScript(options?.initialDevServerError, options?.scriptNonce);
393
+ const initialDevServerErrorHTML = createInitialDevServerErrorScript(options?.initialDevServerError ?? ssrOnlyShellError, options?.scriptNonce);
389
394
  if (didInjectHeadHTML) return insertedHTML + errorMetaHTML + getRenderedSearchHTML();
390
395
  didInjectHeadHTML = true;
391
396
  const hidesQuery = searchParamsGate !== null && searchParamsGate.gate.decision !== "real";
@@ -107,11 +107,20 @@ export type SharedAppCacheIdentityProps = Exclude<AppWorkerResponseStageProps, {
107
107
  export declare function createSharedAppCacheIdentity(request: Request, props: SharedAppCacheIdentityProps): SharedAppCacheIdentity;
108
108
  /**
109
109
  * Build the query-free cache identity of a shared App page dispatch. The user
110
- * query leaves both the URL and `resolvedUrl`; the `.rsc` suffix, an RSC
111
- * request's validated `_rsc`, and the render mode stay because they select the
112
- * representation. HTML identities also drop the RSC selector headers.
110
+ * query leaves both the URL and `resolvedUrl`; an RSC request's validated
111
+ * `_rsc` and the render mode stay because they select the representation. HTML identities also drop the RSC selector headers.
113
112
  */
114
113
  export declare function createSharedAppPageCacheIdentity(request: Request, props: AppMatchedWorkerResponseStageProps): SharedAppCacheIdentity;
114
+ /**
115
+ * Build the query-free cache identity of a shared Route Handler or metadata
116
+ * route dispatch. Next.js keys a route handler by its pathname: the query and
117
+ * the RSC selectors pick no representation of a handler's response, and a
118
+ * handler that reads the request's URL or headers is dynamic, so admission
119
+ * refuses its response. Keeping them in the identity would only let a client
120
+ * store one entry per query or selector value. The method, path and route
121
+ * props stay; the selector headers and the props derived from them leave.
122
+ */
123
+ export declare function createSharedAppRouteCacheIdentity(request: Request, props: AppMatchedWorkerResponseStageProps | AppMetadataWorkerResponseStageProps): SharedAppCacheIdentity;
115
124
  /**
116
125
  * Build the cache identity of a shared hybrid Pages page dispatch: the
117
126
  * dispatched request and props under `origin`, so every host that names it