vinext 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +72 -1
  2. package/dist/build/app-page-static-eligibility.d.ts +15 -0
  3. package/dist/build/app-page-static-eligibility.js +82 -0
  4. package/dist/build/prerender-paths.js +3 -78
  5. package/dist/build/prerender.js +33 -8
  6. package/dist/build/server-manifest.d.ts +7 -0
  7. package/dist/build/server-manifest.js +11 -1
  8. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  9. package/dist/cache/cache-adapters-virtual.js +3 -3
  10. package/dist/client/navigation-runtime.d.ts +11 -0
  11. package/dist/client/navigation-runtime.js +1 -1
  12. package/dist/config/config-matchers.js +3 -1
  13. package/dist/config/next-config.d.ts +7 -0
  14. package/dist/config/next-config.js +18 -3
  15. package/dist/entries/app-rsc-entry.d.ts +2 -0
  16. package/dist/entries/app-rsc-entry.js +64 -43
  17. package/dist/index.d.ts +4 -1
  18. package/dist/index.js +59 -30
  19. package/dist/plugins/cache-flight-codec.d.ts +19 -0
  20. package/dist/plugins/cache-flight-codec.js +68 -0
  21. package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
  22. package/dist/plugins/commonjs-esm-facade.js +80 -0
  23. package/dist/plugins/fonts.d.ts +8 -11
  24. package/dist/plugins/fonts.js +71 -34
  25. package/dist/routing/file-matcher.d.ts +1 -0
  26. package/dist/routing/file-matcher.js +1 -1
  27. package/dist/server/api-handler.js +17 -7
  28. package/dist/server/app-browser-entry.js +11 -7
  29. package/dist/server/app-browser-history-controller.d.ts +2 -0
  30. package/dist/server/app-browser-history-controller.js +8 -0
  31. package/dist/server/app-browser-interception-context.js +26 -2
  32. package/dist/server/app-browser-navigation-controller.d.ts +7 -1
  33. package/dist/server/app-browser-navigation-controller.js +93 -10
  34. package/dist/server/app-fallback-renderer.d.ts +2 -0
  35. package/dist/server/app-fallback-renderer.js +1 -0
  36. package/dist/server/app-history-state.d.ts +3 -0
  37. package/dist/server/app-history-state.js +7 -0
  38. package/dist/server/app-middleware.js +2 -1
  39. package/dist/server/app-optimistic-routing.js +1 -1
  40. package/dist/server/app-page-boundary-render.d.ts +5 -0
  41. package/dist/server/app-page-boundary-render.js +6 -4
  42. package/dist/server/app-page-cache-finalizer.d.ts +11 -0
  43. package/dist/server/app-page-cache-finalizer.js +41 -7
  44. package/dist/server/app-page-cache-render.d.ts +18 -0
  45. package/dist/server/app-page-cache-render.js +63 -43
  46. package/dist/server/app-page-cache.d.ts +5 -0
  47. package/dist/server/app-page-cache.js +27 -7
  48. package/dist/server/app-page-dispatch.d.ts +9 -1
  49. package/dist/server/app-page-dispatch.js +47 -30
  50. package/dist/server/app-page-element-builder.d.ts +7 -0
  51. package/dist/server/app-page-element-builder.js +27 -42
  52. package/dist/server/app-page-execution.d.ts +15 -12
  53. package/dist/server/app-page-execution.js +30 -40
  54. package/dist/server/app-page-head.d.ts +5 -0
  55. package/dist/server/app-page-head.js +9 -3
  56. package/dist/server/app-page-probe.d.ts +0 -109
  57. package/dist/server/app-page-probe.js +3 -136
  58. package/dist/server/app-page-render-identity.d.ts +0 -1
  59. package/dist/server/app-page-render-identity.js +1 -1
  60. package/dist/server/app-page-render-observation.js +2 -2
  61. package/dist/server/app-page-render.d.ts +13 -5
  62. package/dist/server/app-page-render.js +219 -119
  63. package/dist/server/app-page-request.d.ts +0 -1
  64. package/dist/server/app-page-request.js +1 -2
  65. package/dist/server/app-page-response.d.ts +4 -3
  66. package/dist/server/app-page-response.js +4 -3
  67. package/dist/server/app-page-route-wiring.d.ts +17 -5
  68. package/dist/server/app-page-route-wiring.js +55 -33
  69. package/dist/server/app-page-rsc-render-status.d.ts +48 -0
  70. package/dist/server/app-page-rsc-render-status.js +54 -0
  71. package/dist/server/app-page-stream.d.ts +15 -8
  72. package/dist/server/app-page-stream.js +6 -0
  73. package/dist/server/app-request-stage-dispatch.js +1 -1
  74. package/dist/server/app-request-stage-independent-entry.js +4 -4
  75. package/dist/server/app-response-header-provenance.d.ts +2 -0
  76. package/dist/server/app-response-header-provenance.js +10 -4
  77. package/dist/server/app-route-handler-dispatch.js +1 -1
  78. package/dist/server/app-router-entry.js +3 -3
  79. package/dist/server/app-rsc-handler.d.ts +4 -0
  80. package/dist/server/app-rsc-handler.js +212 -96
  81. package/dist/server/app-rsc-request-normalization.d.ts +3 -1
  82. package/dist/server/app-rsc-request-normalization.js +4 -2
  83. package/dist/server/app-rsc-response-stage.d.ts +1 -1
  84. package/dist/server/app-rsc-response-stage.js +6 -4
  85. package/dist/server/app-rsc-route-matching.d.ts +9 -1
  86. package/dist/server/app-rsc-route-matching.js +36 -19
  87. package/dist/server/app-server-action-execution.js +1 -1
  88. package/dist/server/app-ssr-entry.d.ts +3 -2
  89. package/dist/server/app-ssr-entry.js +6 -4
  90. package/dist/server/app-ssr-stream.d.ts +6 -1
  91. package/dist/server/app-ssr-stream.js +34 -15
  92. package/dist/server/app-worker-stages.d.ts +2 -2
  93. package/dist/server/app-worker-stages.js +3 -3
  94. package/dist/server/cacheability-request.js +9 -5
  95. package/dist/server/config-headers.js +2 -0
  96. package/dist/server/dev-server.js +12 -2
  97. package/dist/server/headers.d.ts +14 -0
  98. package/dist/server/headers.js +15 -1
  99. package/dist/server/image-optimization.d.ts +16 -0
  100. package/dist/server/image-optimization.js +26 -6
  101. package/dist/server/isr-cache.js +2 -2
  102. package/dist/server/middleware-matcher-pattern.js +8 -4
  103. package/dist/server/middleware-response-headers.js +2 -0
  104. package/dist/server/multi-stage.d.ts +8 -0
  105. package/dist/server/navigation-planner.js +1 -1
  106. package/dist/server/node-response-signal.d.ts +16 -0
  107. package/dist/server/node-response-signal.js +26 -0
  108. package/dist/server/open-redirect.d.ts +47 -0
  109. package/dist/server/open-redirect.js +74 -1
  110. package/dist/server/pages-page-handler.js +21 -4
  111. package/dist/server/pages-request-stage-entry.js +8 -4
  112. package/dist/server/prod-server.d.ts +8 -7
  113. package/dist/server/prod-server.js +153 -69
  114. package/dist/server/request-pipeline.d.ts +20 -8
  115. package/dist/server/request-pipeline.js +21 -7
  116. package/dist/server/response-compression.d.ts +43 -0
  117. package/dist/server/response-compression.js +111 -0
  118. package/dist/server/seed-cache.js +10 -7
  119. package/dist/server/socket-error-backstop.d.ts +14 -0
  120. package/dist/server/socket-error-backstop.js +27 -4
  121. package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
  122. package/dist/shims/cache-adapter-instantiate.js +118 -0
  123. package/dist/shims/cache-callable-runtime.d.ts +2 -40
  124. package/dist/shims/cache-callable-runtime.js +50 -168
  125. package/dist/shims/cache-flight-arguments.d.ts +22 -0
  126. package/dist/shims/cache-flight-arguments.js +56 -0
  127. package/dist/shims/cache-request-state.d.ts +15 -0
  128. package/dist/shims/cache-request-state.js +31 -8
  129. package/dist/shims/cache-runtime.d.ts +6 -19
  130. package/dist/shims/cache-runtime.js +165 -225
  131. package/dist/shims/cache.d.ts +2 -2
  132. package/dist/shims/cache.js +5 -4
  133. package/dist/shims/cacheability-classification.d.ts +7 -0
  134. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  135. package/dist/shims/error-boundary-navigation.js +19 -1
  136. package/dist/shims/error-boundary.d.ts +10 -10
  137. package/dist/shims/error-boundary.js +26 -7
  138. package/dist/shims/fetch-cache.d.ts +6 -0
  139. package/dist/shims/fetch-cache.js +19 -1
  140. package/dist/shims/form.js +1 -1
  141. package/dist/shims/headers.d.ts +13 -0
  142. package/dist/shims/headers.js +47 -4
  143. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
  144. package/dist/shims/internal/app-page-props-cache-key.js +24 -15
  145. package/dist/shims/internal/headers-state.d.ts +0 -1
  146. package/dist/shims/metadata.js +3 -3
  147. package/dist/shims/navigation-errors.d.ts +6 -0
  148. package/dist/shims/navigation-errors.js +11 -1
  149. package/dist/shims/navigation.d.ts +7 -0
  150. package/dist/shims/navigation.js +14 -4
  151. package/dist/shims/request-context.d.ts +9 -0
  152. package/dist/shims/thenable-params.d.ts +4 -0
  153. package/dist/shims/thenable-params.js +25 -3
  154. package/dist/shims/unified-request-context.js +1 -0
  155. package/dist/utils/mdx-scan.d.ts +5 -0
  156. package/dist/utils/mdx-scan.js +8 -1
  157. package/dist/utils/regex-safety.d.ts +10 -0
  158. package/dist/utils/regex-safety.js +70 -18
  159. package/package.json +3 -3
@@ -10,10 +10,10 @@ import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base
10
10
  import { patternToNextFormat } from "../routing/route-validation.js";
11
11
  import { getRequestExecutionContext } from "../shims/request-context.js";
12
12
  import "./app-rsc-vary.js";
13
- import { ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_REWRITTEN_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER } from "./headers.js";
13
+ import { ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_REWRITTEN_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_SPECIAL_ERROR_STATUS_HEADER } from "./headers.js";
14
14
  import { isInterceptionMatchedUrlPath, normalizePath } from "./normalize-path.js";
15
15
  import { badRequestResponse, notFoundResponse, notFoundStaticAssetResponse } from "./http-error-responses.js";
16
- import { cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, normalizeTrailingSlash, resolvePublicFileRoute } from "./request-pipeline.js";
16
+ import { cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, normalizeTrailingSlash, normalizeTrailingSlashPathname, resolvePublicFileRoute } from "./request-pipeline.js";
17
17
  import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
18
18
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
19
19
  import { markRouteCacheabilityDynamic, preserveRouteCacheabilityResponsePolicy } from "../shims/cacheability-classification.js";
@@ -37,6 +37,8 @@ import { setFrameworkRequestRoute, traceFrameworkRequest } from "./request-traci
37
37
  import { matchPrerenderRouteParamsPayload, readTrustedPrerenderRouteParams, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
38
38
  import { buildPageCacheTags } from "./implicit-tags.js";
39
39
  import { createServerActionNotFoundResponse, getServerActionNotFoundMessage } from "./server-action-not-found.js";
40
+ import { isPossibleAppRouteActionRequest } from "./app-action-request.js";
41
+ import { normalizeInterceptionContextHeader } from "./app-interception-context-header.js";
40
42
  import { buildPostMwRequestContext } from "./app-post-middleware-context.js";
41
43
  import { pickRootParams, setRootParams } from "../shims/root-params.js";
42
44
  import { createRouteTreePrefetchResponse, isRouteTreePrefetchRequest } from "./app-route-tree-prefetch.js";
@@ -51,6 +53,33 @@ import { consumePagesResponseStagePolicyOwner, withoutResponseStageVary } from "
51
53
  const HAS_CONFIG_HEADERS = process.env.__VINEXT_HAS_CONFIG_HEADERS !== "false";
52
54
  const HAS_CONFIG_REDIRECTS = process.env.__VINEXT_HAS_CONFIG_REDIRECTS !== "false";
53
55
  const HAS_CONFIG_REWRITES = process.env.__VINEXT_HAS_CONFIG_REWRITES !== "false";
56
+ /**
57
+ * Decode an interception context to the canonical source pathname it names.
58
+ * Throws for any value the client could not send back as its context header.
59
+ */
60
+ function decodeInterceptionSourcePathname(context) {
61
+ if (!isInterceptionMatchedUrlPath(context)) throw new Error("Invalid interception source pathname");
62
+ if (new URL(context, "http://n").pathname !== context) throw new Error("Interception source is not a parsed URL pathname");
63
+ const decodedSourcePathname = normalizePathnameForRouteMatchStrict(context);
64
+ if (/[\t\n\r]/.test(decodedSourcePathname)) throw new Error("Interception source contains a stripped URL character");
65
+ if (hasUrlParserDotSegment(decodedSourcePathname)) throw new Error("Interception source contains a URL dot segment");
66
+ const sourcePathname = normalizePath(decodedSourcePathname);
67
+ if (sourcePathname !== decodedSourcePathname) throw new Error("Non-canonical interception source pathname");
68
+ return sourcePathname;
69
+ }
70
+ /**
71
+ * Whether a server-derived context satisfies the whole inbound header
72
+ * contract, so the client can send it back unchanged on its next request.
73
+ */
74
+ function isValidInterceptionSourceContext(context) {
75
+ if (normalizeInterceptionContextHeader(context) !== context) return false;
76
+ try {
77
+ decodeInterceptionSourcePathname(context);
78
+ return true;
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
54
83
  function ruleUsesUnkeyedRequestCondition(rule) {
55
84
  return [...rule.has ?? [], ...rule.missing ?? []].some((condition) => condition.type === "header" || condition.type === "cookie" || condition.type === "host");
56
85
  }
@@ -123,6 +152,26 @@ function applyMiddlewareContextToResponse(response, middlewareContext, appendToP
123
152
  headers
124
153
  }));
125
154
  }
155
+ /**
156
+ * Next.js answers a Link's segment prefetch of a page whose notFound(),
157
+ * forbidden() or unauthorized() it stored with a 200, so its router renders
158
+ * the page's fallback without loading the document. A shared response stage
159
+ * gives that prefetch the navigation's response, so the request's own headers
160
+ * decide the status here.
161
+ */
162
+ function withStoredSpecialErrorStatus(response, isSegmentPrefetch) {
163
+ const marker = response.headers.get(VINEXT_SPECIAL_ERROR_STATUS_HEADER);
164
+ if (marker === null) return response;
165
+ const headers = new Headers(response.headers);
166
+ headers.delete(VINEXT_SPECIAL_ERROR_STATUS_HEADER);
167
+ const storedStatus = Number(marker);
168
+ const status = !(storedStatus === 401 || storedStatus === 403 || storedStatus === 404) ? response.status : isSegmentPrefetch ? 200 : storedStatus;
169
+ return preserveFullyBufferedBodyMetadata(response, new Response(response.body, {
170
+ headers,
171
+ status,
172
+ statusText: status === response.status ? response.statusText : ""
173
+ }));
174
+ }
126
175
  function hasProperty(value, key) {
127
176
  return key in value;
128
177
  }
@@ -206,7 +255,8 @@ function requestWithoutRscSuffix(request) {
206
255
  url.pathname = pathname;
207
256
  return cloneRequestWithUrl(request, url.toString());
208
257
  }
209
- function markUnverifiedInterceptionResponseUncacheable(response) {
258
+ /** Send a response with the never-cache policy, rebuilding immutable headers. */
259
+ function withNeverCacheControl(response) {
210
260
  const applyNoStore = (headers) => {
211
261
  applyCdnResponseHeaders(headers, { cacheControl: NEVER_CACHE_CONTROL });
212
262
  };
@@ -224,6 +274,13 @@ function markUnverifiedInterceptionResponseUncacheable(response) {
224
274
  }
225
275
  return markedResponse;
226
276
  }
277
+ const unmatchedRouteResponses = /* @__PURE__ */ new WeakSet();
278
+ /** Send an unmatched route's 404 with the never-cache policy, as Next.js does. */
279
+ function withUnmatchedRouteCacheControl(response) {
280
+ const markedResponse = withNeverCacheControl(response);
281
+ unmatchedRouteResponses.add(markedResponse);
282
+ return markedResponse;
283
+ }
227
284
  async function handleAppRscRequest(options, request, preMiddlewareRequestContext, middlewareContext, isDataRequest, isMiddlewareDataRequest, pagesDataRequest, dispatchInternalRequest, allowInternalRscDocumentFallback, dispatchResponseStage, responseStageProbeMode = null, setInterceptionResponseUncacheable = () => {}) {
228
285
  const handlerStart = process.env.NODE_ENV !== "production" ? performance.now() : 0;
229
286
  if (process.env.NODE_ENV !== "production") {
@@ -248,12 +305,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
248
305
  if (hasRawInterceptionContext) setInterceptionResponseUncacheable(true);
249
306
  let interceptionSourcePathname = null;
250
307
  if (isRscRequest && interceptionContextHeader !== null) try {
251
- if (!isInterceptionMatchedUrlPath(interceptionContextHeader)) throw new Error("Invalid interception source pathname");
252
- const decodedInterceptionSourcePathname = normalizePathnameForRouteMatchStrict(interceptionContextHeader);
253
- if (/[\t\n\r]/.test(decodedInterceptionSourcePathname)) throw new Error("Interception source contains a stripped URL character");
254
- if (hasUrlParserDotSegment(decodedInterceptionSourcePathname)) throw new Error("Interception source contains a URL dot segment");
255
- interceptionSourcePathname = normalizePath(decodedInterceptionSourcePathname);
256
- if (interceptionSourcePathname !== decodedInterceptionSourcePathname) throw new Error("Non-canonical interception source pathname");
308
+ interceptionSourcePathname = decodeInterceptionSourcePathname(interceptionContextHeader);
257
309
  } catch {
258
310
  options.clearRequestContext();
259
311
  setInterceptionResponseUncacheable(true);
@@ -282,10 +334,10 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
282
334
  });
283
335
  if (selectorCacheBustingRedirect) return selectorCacheBustingRedirect;
284
336
  }
285
- const provesConcreteInterceptionSource = (sourceMatch) => {
337
+ const provesConcreteInterceptionSource = (sourceMatch, sourceContext = interceptionContextHeader) => {
286
338
  if (sourceMatch === null) return false;
287
339
  if (sourceMatch.interceptionSourceIsConcrete !== void 0) return sourceMatch.interceptionSourceIsConcrete;
288
- return interceptionSourcePathname !== null && options.matchRoute(interceptionSourcePathname)?.route === sourceMatch.route;
340
+ return sourceContext !== null && (options.matchRequestRoute ?? options.matchRoute)(sourceContext)?.route === sourceMatch.route;
289
341
  };
290
342
  if (provesConcreteInterceptionSource(hadBasePath && hasRawInterceptionContext && interceptionContextHeader !== null ? options.matchInterceptRoute?.(requestCleanPathname, interceptionContextHeader, interceptionIdHeader) ?? null : null)) setInterceptionResponseUncacheable(false);
291
343
  if (pathname === "/__vinext/prerender/static-params" || pathname === "/__vinext/prerender/pages-static-paths" || pathname === "/__vinext/prerender/metadata-routes") {
@@ -461,7 +513,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
461
513
  isRscRequest,
462
514
  middlewareCookieOverlay,
463
515
  mountedSlotsHeader,
464
- protocolVersion: 10,
516
+ protocolVersion: 11,
465
517
  requestOrigin: url.origin,
466
518
  renderMode,
467
519
  resolvedUrl,
@@ -618,8 +670,9 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
618
670
  });
619
671
  const directPreActionMatch = filesystemRouteEligible ? matchCleanPathname() : null;
620
672
  const preActionRoutePathname = cleanPathnameIsRequestPathname ? requestCleanPathname : cleanPathname;
621
- const interceptionSourceMatch = filesystemRouteEligible && interceptionSourcePathname !== null && interceptionContextHeader !== null ? options.matchInterceptRoute?.(preActionRoutePathname, interceptionContextHeader, interceptionIdHeader) ?? null : null;
622
- const hasVerifiedInterceptionSource = provesConcreteInterceptionSource(interceptionSourceMatch);
673
+ let interceptionSourceContext = interceptionContextHeader;
674
+ let interceptionSourceMatch = filesystemRouteEligible && interceptionSourcePathname !== null && interceptionContextHeader !== null ? options.matchInterceptRoute?.(preActionRoutePathname, interceptionContextHeader, interceptionIdHeader) ?? null : null;
675
+ let hasVerifiedInterceptionSource = provesConcreteInterceptionSource(interceptionSourceMatch);
623
676
  if (interceptionIdHeader !== null && !hasVerifiedInterceptionSource) {
624
677
  options.clearRequestContext();
625
678
  setInterceptionResponseUncacheable(true);
@@ -634,81 +687,142 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
634
687
  bypassInterceptionContextCache = true;
635
688
  setInterceptionResponseUncacheable(true);
636
689
  };
637
- if (interceptionSourceMatch !== null && interceptionSourcePathname !== null && runMiddleware && interceptionSourceMatch.route !== directPreActionMatch?.route) {
638
- const sourceUrl = new URL(userlandRequest.url);
639
- sourceUrl.search = new URL(resolvedUrl, url).search;
640
- sourceUrl.pathname = hadBasePath ? addBasePathToPathname(interceptionSourcePathname, options.basePath) : interceptionSourcePathname;
641
- const sourceRequest = userlandRequest.body ? userlandRequest.clone() : userlandRequest;
642
- const sourceMiddlewareRequest = cloneRequestWithUrl(sourceRequest, sourceUrl.href);
643
- sourceMiddlewareRequest.headers.delete(VINEXT_MW_CTX_HEADER);
644
- for (const header of FLIGHT_HEADERS) sourceMiddlewareRequest.headers.delete(header);
645
- const targetHeadersContext = getHeadersContext();
646
- const targetRequestHeaders = targetHeadersContext ? new Headers(targetHeadersContext.headers) : null;
647
- targetRequestHeaders?.delete(VINEXT_MW_CTX_HEADER);
648
- for (const header of FLIGHT_HEADERS) targetRequestHeaders?.delete(header);
649
- if (targetRequestHeaders) {
650
- const sourceHeaderNames = Array.from(sourceMiddlewareRequest.headers.keys());
651
- for (const header of sourceHeaderNames) sourceMiddlewareRequest.headers.delete(header);
652
- for (const [name, value] of targetRequestHeaders) sourceMiddlewareRequest.headers.append(name, value);
653
- }
654
- const sourceMiddlewareContext = {
655
- headers: null,
656
- requestHeaders: null,
657
- status: null
658
- };
659
- const sourceHeadersContext = headersContextFromRequest(sourceMiddlewareRequest, { draftModeSecret: options.draftModeSecret });
660
- let sourceMiddlewareResult;
661
- try {
662
- sourceMiddlewareResult = await runWithHeadersContext(sourceHeadersContext, () => runMiddleware({
663
- cleanPathname: interceptionSourcePathname,
664
- context: sourceMiddlewareContext,
665
- externalRewriteRequest: normalizedUserlandRequest,
666
- hadBasePath,
667
- isDataRequest: isMiddlewareDataRequest,
668
- request: sourceMiddlewareRequest,
669
- validateExternalRewriteRequest: () => validateClaimedOutsideBasePathRsc(true)
670
- }));
671
- } finally {
672
- if (sourceMiddlewareRequest.body && !sourceMiddlewareRequest.bodyUsed && !sourceMiddlewareRequest.body.locked) sourceMiddlewareRequest.body.cancel().catch(() => {});
673
- if (sourceRequest !== userlandRequest && sourceRequest.body && !sourceRequest.bodyUsed && !sourceRequest.body.locked) sourceRequest.body.cancel().catch(() => {});
674
- }
675
- if (!dispatchResponseStage && sourceMiddlewareResult.pathnameEligible) markRouteCacheabilityDynamic(sourceMiddlewareResult.matched ? "middleware matched this request" : "middleware is eligible for this pathname");
676
- if (sourceMiddlewareResult.kind === "response") {
677
- options.clearRequestContext();
678
- return sourceMiddlewareResult.response;
679
- }
680
- let sourceHeadersCompatible = true;
681
- if (targetRequestHeaders) {
682
- for (const [name, value] of targetRequestHeaders) if (sourceHeadersContext.headers.get(name) !== value) {
683
- sourceHeadersCompatible = false;
684
- break;
690
+ if (interceptionSourceMatch !== null && interceptionContextHeader !== null && interceptionSourcePathname !== null && !(interceptionSourceMatch.route === directPreActionMatch?.route && haveSamePageParams(interceptionSourceMatch.params, directPreActionMatch.params))) {
691
+ const sourceRequestPathname = normalizeTrailingSlashPathname(interceptionContextHeader, options.trailingSlash) ?? interceptionContextHeader;
692
+ const decodedSourceRequestPathname = normalizeTrailingSlashPathname(interceptionSourcePathname, options.trailingSlash) ?? interceptionSourcePathname;
693
+ const adoptRewrittenInterceptionSource = (rewrittenPathname) => {
694
+ const rewrittenInterceptionSourceMatch = isValidInterceptionSourceContext(rewrittenPathname) ? options.matchInterceptRoute?.(preActionRoutePathname, rewrittenPathname, interceptionIdHeader) ?? null : null;
695
+ if (rewrittenInterceptionSourceMatch !== null) {
696
+ interceptionSourceMatch = rewrittenInterceptionSourceMatch;
697
+ interceptionSourceContext = rewrittenPathname;
685
698
  }
686
- }
687
- if (!targetHeadersContext || !targetRequestHeaders || !sourceHeadersCompatible || !haveSameRequestCookies(targetHeadersContext.cookies, sourceHeadersContext.cookies)) {
699
+ const rewrittenSourceMatch = options.matchRoute(rewrittenPathname);
700
+ return interceptionSourceMatch !== null && rewrittenSourceMatch?.route === interceptionSourceMatch.route && haveSamePageParams(rewrittenSourceMatch.params, interceptionSourceMatch.params);
701
+ };
702
+ if (configMatchers?.matchRedirect(matchPathname(sourceRequestPathname), options.configRedirects, preMiddlewareRequestContext, basePathState, dispatchResponseStage ? void 0 : markConditionalRedirectCacheability)) {
688
703
  options.clearRequestContext();
689
704
  return notFoundResponse();
690
705
  }
691
- let addedSourceHeader = false;
692
- for (const [name, value] of sourceHeadersContext.headers) if (!targetRequestHeaders.has(name)) {
693
- targetHeadersContext.headers.set(name, value);
694
- addedSourceHeader = true;
695
- }
696
- if (addedSourceHeader) {
697
- targetHeadersContext.readonlyHeaders = void 0;
698
- canUseSharedWorkerResponseStage = false;
706
+ let sourceRulesPathname = sourceRequestPathname;
707
+ if (runMiddleware) {
708
+ const sourceUrl = new URL(userlandRequest.url);
709
+ sourceUrl.search = new URL(resolvedUrl, url).search;
710
+ const sourceUrlPathname = hadBasePath && options.basePath ? sourceRequestPathname === "/" ? options.basePath : options.basePath + sourceRequestPathname : sourceRequestPathname;
711
+ sourceUrl.pathname = sourceUrlPathname;
712
+ if (sourceUrl.pathname !== sourceUrlPathname) {
713
+ options.clearRequestContext();
714
+ setInterceptionResponseUncacheable(true);
715
+ return badRequestResponse();
716
+ }
717
+ const sourceRequest = userlandRequest.body ? userlandRequest.clone() : userlandRequest;
718
+ const sourceMiddlewareRequest = cloneRequestWithUrl(sourceRequest, sourceUrl.href);
719
+ sourceMiddlewareRequest.headers.delete(VINEXT_MW_CTX_HEADER);
720
+ for (const header of FLIGHT_HEADERS) sourceMiddlewareRequest.headers.delete(header);
721
+ const targetHeadersContext = getHeadersContext();
722
+ const targetRequestHeaders = targetHeadersContext ? new Headers(targetHeadersContext.headers) : null;
723
+ targetRequestHeaders?.delete(VINEXT_MW_CTX_HEADER);
724
+ for (const header of FLIGHT_HEADERS) targetRequestHeaders?.delete(header);
725
+ if (targetRequestHeaders) {
726
+ const sourceHeaderNames = Array.from(sourceMiddlewareRequest.headers.keys());
727
+ for (const header of sourceHeaderNames) sourceMiddlewareRequest.headers.delete(header);
728
+ for (const [name, value] of targetRequestHeaders) sourceMiddlewareRequest.headers.append(name, value);
729
+ }
730
+ const sourceMiddlewareContext = {
731
+ headers: null,
732
+ requestHeaders: null,
733
+ status: null
734
+ };
735
+ const sourceHeadersContext = headersContextFromRequest(sourceMiddlewareRequest, { draftModeSecret: options.draftModeSecret });
736
+ let sourceMiddlewareResult;
737
+ try {
738
+ sourceMiddlewareResult = await runWithHeadersContext(sourceHeadersContext, () => runMiddleware({
739
+ cleanPathname: decodedSourceRequestPathname,
740
+ context: sourceMiddlewareContext,
741
+ externalRewriteRequest: normalizedUserlandRequest,
742
+ hadBasePath,
743
+ isDataRequest: isMiddlewareDataRequest,
744
+ request: sourceMiddlewareRequest,
745
+ validateExternalRewriteRequest: () => validateClaimedOutsideBasePathRsc(true)
746
+ }));
747
+ } finally {
748
+ if (sourceMiddlewareRequest.body && !sourceMiddlewareRequest.bodyUsed && !sourceMiddlewareRequest.body.locked) sourceMiddlewareRequest.body.cancel().catch(() => {});
749
+ if (sourceRequest !== userlandRequest && sourceRequest.body && !sourceRequest.bodyUsed && !sourceRequest.body.locked) sourceRequest.body.cancel().catch(() => {});
750
+ }
751
+ if (!dispatchResponseStage && sourceMiddlewareResult.pathnameEligible) markRouteCacheabilityDynamic(sourceMiddlewareResult.matched ? "middleware matched this request" : "middleware is eligible for this pathname");
752
+ if (sourceMiddlewareResult.kind === "response") {
753
+ options.clearRequestContext();
754
+ return sourceMiddlewareResult.response;
755
+ }
756
+ let sourceHeadersCompatible = true;
757
+ if (targetRequestHeaders) {
758
+ for (const [name, value] of targetRequestHeaders) if (sourceHeadersContext.headers.get(name) !== value) {
759
+ sourceHeadersCompatible = false;
760
+ break;
761
+ }
762
+ }
763
+ if (!targetHeadersContext || !targetRequestHeaders || !sourceHeadersCompatible || !haveSameRequestCookies(targetHeadersContext.cookies, sourceHeadersContext.cookies)) {
764
+ options.clearRequestContext();
765
+ return notFoundResponse();
766
+ }
767
+ let addedSourceHeader = false;
768
+ for (const [name, value] of sourceHeadersContext.headers) if (!targetRequestHeaders.has(name)) {
769
+ targetHeadersContext.headers.set(name, value);
770
+ addedSourceHeader = true;
771
+ }
772
+ if (addedSourceHeader) {
773
+ targetHeadersContext.readonlyHeaders = void 0;
774
+ canUseSharedWorkerResponseStage = false;
775
+ }
776
+ if (sourceMiddlewareResult.rewritten) {
777
+ if (sourceMiddlewareResult.search !== sourceUrl.search || !adoptRewrittenInterceptionSource(sourceMiddlewareResult.cleanPathname)) {
778
+ options.clearRequestContext();
779
+ return notFoundResponse();
780
+ }
781
+ sourceRulesPathname = sourceMiddlewareResult.cleanPathname;
782
+ }
699
783
  }
700
- if (sourceMiddlewareResult.rewritten) {
701
- const rewrittenSourceMatch = options.matchRoute(sourceMiddlewareResult.cleanPathname);
702
- if (sourceMiddlewareResult.search !== sourceUrl.search || rewrittenSourceMatch?.route !== interceptionSourceMatch.route || !haveSamePageParams(rewrittenSourceMatch.params, interceptionSourceMatch.params)) {
784
+ if (HAS_CONFIG_REWRITES && options.configRewrites.beforeFiles.length) {
785
+ const { matchRewrite } = await import("../config/config-matchers.js");
786
+ const sourceRewriteContext = requestContextForResolvedUrl(buildPostMwRequestContext(userlandRequest), resolvedUrl, url);
787
+ let didRewriteSource = false;
788
+ for (const rewrite of options.configRewrites.beforeFiles) {
789
+ const destination = matchRewrite(matchPathname(sourceRulesPathname), [rewrite], sourceRewriteContext, basePathState, matchPathname(sourceRulesPathname), dispatchResponseStage === void 0 ? markConditionalRewriteCacheability : void 0);
790
+ if (!destination) continue;
791
+ if (isExternalUrl(destination) || destination.includes("?")) {
792
+ options.clearRequestContext();
793
+ return notFoundResponse();
794
+ }
795
+ sourceRulesPathname = pathnameForResolvedUrl(destination);
796
+ didRewriteSource = true;
797
+ }
798
+ if (didRewriteSource && !adoptRewrittenInterceptionSource(sourceRulesPathname)) {
703
799
  options.clearRequestContext();
704
800
  return notFoundResponse();
705
801
  }
706
802
  }
707
803
  }
804
+ if (interceptionSourceMatch !== null) {
805
+ hasVerifiedInterceptionSource = provesConcreteInterceptionSource(interceptionSourceMatch, interceptionSourceContext);
806
+ if (!hasVerifiedInterceptionSource) {
807
+ if (interceptionIdHeader !== null) {
808
+ options.clearRequestContext();
809
+ setInterceptionResponseUncacheable(true);
810
+ return badRequestResponse();
811
+ }
812
+ interceptionSourceMatch = null;
813
+ interceptionSourceContext = null;
814
+ }
815
+ bypassInterceptionContextCache = hasRawInterceptionContext && !hasVerifiedInterceptionSource;
816
+ setInterceptionResponseUncacheable(bypassInterceptionContextCache);
817
+ }
708
818
  const interceptionPreActionMatch = filesystemRouteEligible && directPreActionMatch === null && isRscRequest && interceptionSourcePathname !== null ? interceptionSourceMatch : null;
709
819
  const preActionMatch = directPreActionMatch ?? interceptionPreActionMatch;
710
820
  const isInterceptionMatch = interceptionPreActionMatch !== null;
711
- if (preActionMatch) setRootParams(pickRootParams(preActionMatch.params, preActionMatch.route.rootParamNames));
821
+ const rendersRawRequestMatch = options.matchRawRequestRoute !== void 0 && (isPossibleAppRouteActionRequest(request) || isDraftModeRequest(request, options.draftModeSecret) || isDraftModeEnabled());
822
+ const usesRawRequestMatch = () => rendersRawRequestMatch && cleanPathnameIsRequestPathname && !isInterceptionMatch;
823
+ const renderedRouteMatch = (routeMatch) => routeMatch !== null && usesRawRequestMatch() ? options.matchRawRequestRoute(requestCleanPathname) ?? routeMatch : routeMatch;
824
+ const renderedPreActionMatch = renderedRouteMatch(preActionMatch);
825
+ if (renderedPreActionMatch) setRootParams(pickRootParams(renderedPreActionMatch.params, renderedPreActionMatch.route.rootParamNames));
712
826
  if (pagesDataRequest && didMiddlewareRewritePathname && preActionMatch && !preActionMatch.route.isDynamic) {
713
827
  const headers = new Headers();
714
828
  mergeMiddlewareResponseHeaders(headers, middlewareContext.headers);
@@ -729,7 +843,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
729
843
  contentType,
730
844
  middlewareContext,
731
845
  request,
732
- routeMatch: preActionMatch
846
+ routeMatch: renderedPreActionMatch
733
847
  });
734
848
  else if (preActionMatch?.route.__loadPage && !preActionMatch.route.__loadRouteHandler) return createMissingServerActionResponse(options, null);
735
849
  }
@@ -761,13 +875,13 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
761
875
  actionId,
762
876
  cleanPathname,
763
877
  contentType,
764
- interceptionContext: interceptionContextHeader,
878
+ interceptionContext: interceptionSourceContext,
765
879
  isRscRequest,
766
880
  middlewareContext,
767
881
  mountedSlotsHeader,
768
882
  request,
769
883
  scriptNonce,
770
- routeMatch: preActionMatch,
884
+ routeMatch: renderedPreActionMatch,
771
885
  routePathname: preActionRoutePathname,
772
886
  dispatchRedirectTargetRequest: dispatchInternalRequest,
773
887
  sourceConfigHeaders,
@@ -803,7 +917,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
803
917
  matchKind,
804
918
  middlewareCookieOverlay,
805
919
  preHandlerHeaders: cache === "shared" && resourceKind === "page" && dataKind === "static" ? null : [...await loadPreHandlerResponseHeaders()],
806
- protocolVersion: 10,
920
+ protocolVersion: 11,
807
921
  requestOrigin: url.origin,
808
922
  resourceKind,
809
923
  requestUrl: request.url,
@@ -935,15 +1049,16 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
935
1049
  }
936
1050
  if (match) break;
937
1051
  }
938
- if (interceptionCacheProofInvalidated && interceptionContextHeader !== null) {
1052
+ if (interceptionCacheProofInvalidated && interceptionSourceContext !== null) {
939
1053
  const finalInterceptionTargetPathname = cleanPathnameIsRequestPathname ? requestCleanPathname : cleanPathname;
940
- const finalInterceptionSourceMatch = filesystemRouteEligible && match !== null ? options.matchInterceptRoute?.(finalInterceptionTargetPathname, interceptionContextHeader, interceptionIdHeader) ?? null : null;
1054
+ const finalInterceptionSourceMatch = filesystemRouteEligible && match !== null ? options.matchInterceptRoute?.(finalInterceptionTargetPathname, interceptionSourceContext, interceptionIdHeader) ?? null : null;
941
1055
  const hasVerifiedFinalInterceptionSource = hasVerifiedInterceptionSource && interceptionSourceMatch !== null && provesConcreteInterceptionSource(finalInterceptionSourceMatch) && finalInterceptionSourceMatch?.route === interceptionSourceMatch.route && haveSamePageParams(finalInterceptionSourceMatch.params, interceptionSourceMatch.params);
942
1056
  if (interceptionIdHeader !== null && !hasVerifiedFinalInterceptionSource) {
943
1057
  options.clearRequestContext();
944
1058
  setInterceptionResponseUncacheable(true);
945
1059
  return badRequestResponse();
946
1060
  }
1061
+ if (!hasVerifiedFinalInterceptionSource) interceptionSourceContext = null;
947
1062
  bypassInterceptionContextCache = !hasVerifiedFinalInterceptionSource;
948
1063
  setInterceptionResponseUncacheable(bypassInterceptionContextCache);
949
1064
  }
@@ -958,7 +1073,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
958
1073
  options.clearRequestContext();
959
1074
  const headers = new Headers();
960
1075
  mergeMiddlewareResponseHeaders(headers, middlewareContext.headers);
961
- return notFoundResponse({ headers });
1076
+ return withUnmatchedRouteCacheControl(notFoundResponse({ headers }));
962
1077
  }
963
1078
  if (pagesDataRequest) {
964
1079
  options.clearRequestContext();
@@ -982,7 +1097,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
982
1097
  }
983
1098
  setFrameworkRequestRoute("/404", isRscRequest);
984
1099
  const notFoundResponseStage = transportedResponseStage ?? options.renderResponseStageLocally;
985
- if (notFoundResponseStage) return composeResponseStageResponse(await notFoundResponseStage(responseStageRequest(), {
1100
+ if (notFoundResponseStage) return withUnmatchedRouteCacheControl(await composeResponseStageResponse(await notFoundResponseStage(responseStageRequest(), {
986
1101
  kind: "app-not-found",
987
1102
  buildId: options.buildId,
988
1103
  cacheability: responseStageCacheability(resolvedUrl),
@@ -992,12 +1107,12 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
992
1107
  isRscRequest,
993
1108
  middlewareCookieOverlay,
994
1109
  mountedSlotsHeader,
995
- protocolVersion: 10,
1110
+ protocolVersion: 11,
996
1111
  requestOrigin: url.origin,
997
1112
  renderMode,
998
1113
  resolvedUrl,
999
1114
  scriptNonce: scriptNonce ?? null
1000
- }));
1115
+ })));
1001
1116
  const renderedNotFoundResponse = await traceAppPageRender("/404", "render", () => options.renderNotFound({
1002
1117
  isRscRequest,
1003
1118
  middlewareContext,
@@ -1005,12 +1120,13 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
1005
1120
  route: null,
1006
1121
  scriptNonce
1007
1122
  }));
1008
- if (renderedNotFoundResponse) return renderedNotFoundResponse;
1123
+ if (renderedNotFoundResponse) return withUnmatchedRouteCacheControl(renderedNotFoundResponse);
1009
1124
  options.clearRequestContext();
1010
1125
  const headers = new Headers();
1011
1126
  mergeMiddlewareResponseHeaders(headers, middlewareContext.headers);
1012
- return notFoundResponse({ headers });
1127
+ return withUnmatchedRouteCacheControl(notFoundResponse({ headers }));
1013
1128
  }
1129
+ match = renderedRouteMatch(match);
1014
1130
  const { route, params } = match;
1015
1131
  const requestPathnameDiffersFromCachePath = requestCleanPathname !== cleanPathname;
1016
1132
  const rawRequestPathnameIsObservable = isInterceptionMatch || route.routeHandler != null || typeof route.__loadRouteHandler === "function";
@@ -1038,7 +1154,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
1038
1154
  const prerenderRouteParams = prerenderRouteParamsMatch?.params ?? null;
1039
1155
  const isPrerenderFallbackShell = prerenderRouteParamsMatch?.kind === "fallback-shell";
1040
1156
  const renderParams = prerenderRouteParams ?? params;
1041
- const responseStageMatchKind = isInterceptionMatch ? "interception" : cleanPathnameIsRequestPathname && options.matchRequestRoute ? "request" : "resolved";
1157
+ const responseStageMatchKind = isInterceptionMatch ? "interception" : cleanPathnameIsRequestPathname && options.matchRequestRoute ? usesRawRequestMatch() ? "raw-request" : "request" : "resolved";
1042
1158
  const responseStageRoutePathname = isInterceptionMatch ? preActionRoutePathname : cleanPathnameIsRequestPathname ? requestCleanPathname || "/" : cleanPathname || "/";
1043
1159
  const matchedResponseStage = transportedResponseStage ?? options.renderResponseStageLocally;
1044
1160
  let runtimeFallbackShells = [];
@@ -1078,7 +1194,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
1078
1194
  middlewareCookieOverlay,
1079
1195
  mountedSlotsHeader,
1080
1196
  params,
1081
- protocolVersion: 10,
1197
+ protocolVersion: 11,
1082
1198
  requestOrigin: url.origin,
1083
1199
  renderMode,
1084
1200
  resolvedUrl,
@@ -1099,7 +1215,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
1099
1215
  searchParams: resolvedSearchParams
1100
1216
  });
1101
1217
  }
1102
- const pageResponse = matchedResponseStage ? await matchedResponseStage(responseStageRequest(), {
1218
+ const pageResponse = withStoredSpecialErrorStatus(matchedResponseStage ? await matchedResponseStage(responseStageRequest(), {
1103
1219
  kind: "app-page",
1104
1220
  buildId: options.buildId,
1105
1221
  cacheability: responseStageCacheability(resolvedUrl),
@@ -1110,14 +1226,14 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
1110
1226
  cleanPathname,
1111
1227
  draftModeCookie,
1112
1228
  forceDynamic: route.forceDynamic === true,
1113
- interceptionContext: isRscRequest ? interceptionContextHeader : null,
1229
+ interceptionContext: isRscRequest ? interceptionSourceContext : null,
1114
1230
  interceptionId: interceptionIdHeader,
1115
1231
  isRscRequest,
1116
1232
  matchKind: responseStageMatchKind,
1117
1233
  middlewareCookieOverlay,
1118
1234
  mountedSlotsHeader,
1119
1235
  params,
1120
- protocolVersion: 10,
1236
+ protocolVersion: 11,
1121
1237
  requestOrigin: url.origin,
1122
1238
  renderMode,
1123
1239
  resolvedUrl,
@@ -1134,7 +1250,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
1134
1250
  actionError: normalizedProgressiveActionError,
1135
1251
  actionFailed,
1136
1252
  handlerStart,
1137
- interceptionContext: isRscRequest ? interceptionContextHeader : null,
1253
+ interceptionContext: isRscRequest ? interceptionSourceContext : null,
1138
1254
  interceptionId: interceptionIdHeader,
1139
1255
  interceptionPathname: cleanPathnameIsRequestPathname ? requestCleanPathname : cleanPathname,
1140
1256
  isProgressiveActionRender,
@@ -1157,7 +1273,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
1157
1273
  scriptNonce,
1158
1274
  searchParams: resolvedSearchParams,
1159
1275
  renderMode
1160
- });
1276
+ }), isRscRequest && request.headers.get("Next-Router-Prefetch") === "1" && request.headers.has("Next-Router-Segment-Prefetch"));
1161
1277
  if (isProgressiveActionRender) return applyProgressiveActionSideEffects(pageResponse, progressiveActionFormState);
1162
1278
  return pageResponse;
1163
1279
  }
@@ -1261,7 +1377,7 @@ function createAppRscRequestHandler(options) {
1261
1377
  recordCacheability: dispatchResponseStage === void 0,
1262
1378
  requestContext: preMiddlewareRequestContext
1263
1379
  });
1264
- return interceptionResponseUncacheable ? markUnverifiedInterceptionResponseUncacheable(response) : response;
1380
+ return interceptionResponseUncacheable || unmatchedRouteResponses.has(response) ? withNeverCacheControl(response) : response;
1265
1381
  }, {
1266
1382
  cacheComponents: options.createPprFallbackShells !== void 0,
1267
1383
  route: () => new URL(request.url).pathname
@@ -35,7 +35,9 @@ export type NormalizedRscRequest = {
35
35
  *
36
36
  * 1. Parse URL
37
37
  * 2. Protocol-relative URL guard — on the raw pathname, BEFORE normalizePath collapses
38
- * `//` to `/`. If the guard ran after normalization, `//evil.com` → `/evil.com`
38
+ * `//` to `/`. Literal repeated slashes / backslashes get Next.js's 308 to the
39
+ * collapsed path; encoded leading `%2F` / `%5C` get a 404. If the guard ran after
40
+ * normalization, `//evil.com` → `/evil.com`
39
41
  * would bypass the check and reach the trailing-slash redirector, which echoes the
40
42
  * path into a `Location` header that browsers interpret as protocol-relative.
41
43
  * 3. Strict percent-decode each segment — throws on malformed sequences (→ 400). Must
@@ -69,7 +69,9 @@ function tryNormalizeComparablePathAndSearch(value, basePath, baseUrl) {
69
69
  *
70
70
  * 1. Parse URL
71
71
  * 2. Protocol-relative URL guard — on the raw pathname, BEFORE normalizePath collapses
72
- * `//` to `/`. If the guard ran after normalization, `//evil.com` → `/evil.com`
72
+ * `//` to `/`. Literal repeated slashes / backslashes get Next.js's 308 to the
73
+ * collapsed path; encoded leading `%2F` / `%5C` get a 404. If the guard ran after
74
+ * normalization, `//evil.com` → `/evil.com`
73
75
  * would bypass the check and reach the trailing-slash redirector, which echoes the
74
76
  * path into a `Location` header that browsers interpret as protocol-relative.
75
77
  * 3. Strict percent-decode each segment — throws on malformed sequences (→ 400). Must
@@ -91,7 +93,7 @@ function tryNormalizeComparablePathAndSearch(value, basePath, baseUrl) {
91
93
  */
92
94
  function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, assetPrefix = "") {
93
95
  const url = new URL(request.url);
94
- const protoGuard = guardProtocolRelativeUrl(url.pathname);
96
+ const protoGuard = guardProtocolRelativeUrl(url.pathname, url.search);
95
97
  if (protoGuard) return protoGuard;
96
98
  let decoded;
97
99
  try {
@@ -2,5 +2,5 @@ import { VinextResponseStageDispatchOptions } from "./multi-stage.js";
2
2
  import { AppWorkerResponseStageProps } from "./app-worker-stages.js";
3
3
  import { AppRscHandlerRoute, CreateAppRscHandlerOptions } from "./app-rsc-handler.js";
4
4
  //#region src/server/app-rsc-response-stage.d.ts
5
- export declare function renderAppWorkerResponseStage<TRoute extends AppRscHandlerRoute>(options: CreateAppRscHandlerOptions<TRoute>, rawRequest: Request, ctx: unknown, props: AppWorkerResponseStageProps, _stageOptions?: VinextResponseStageDispatchOptions): Promise<Response>;
5
+ export declare function renderAppWorkerResponseStage<TRoute extends AppRscHandlerRoute>(options: CreateAppRscHandlerOptions<TRoute>, rawRequest: Request, ctx: unknown, props: AppWorkerResponseStageProps, stageOptions?: VinextResponseStageDispatchOptions): Promise<Response>;
6
6
  //#endregion
@@ -60,14 +60,15 @@ function rematchAppWorkerResponseStageRoute(options, normalized, props) {
60
60
  let match = null;
61
61
  if (props.matchKind === "interception") {
62
62
  if (normalized.interceptionContextHeader !== null) match = options.matchInterceptRoute?.(props.routePathname, normalized.interceptionContextHeader, props.interceptionId) ?? null;
63
- } else if (props.matchKind === "request") match = options.matchRequestRoute?.(props.routePathname) ?? null;
63
+ } else if (props.matchKind === "raw-request") match = options.matchRawRequestRoute?.(props.routePathname) ?? null;
64
+ else if (props.matchKind === "request") match = options.matchRequestRoute?.(props.routePathname) ?? null;
64
65
  else match = options.matchRoute(props.routePathname);
65
66
  if (!match) return null;
66
67
  if (match.route.pattern !== props.routePattern || !haveSamePageParams(match.params, props.params)) return null;
67
68
  return match;
68
69
  }
69
- async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, _stageOptions) {
70
- if (props.protocolVersion !== 10 || props.buildId !== options.buildId) return new Response("Incompatible vinext App response stage", { status: 409 });
70
+ async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, stageOptions) {
71
+ if (props.protocolVersion !== 11 || props.buildId !== options.buildId) return new Response("Incompatible vinext App response stage", { status: 409 });
71
72
  if (props.kind === "app-full-request") return new Response("Invalid vinext App response stage", { status: 400 });
72
73
  options.registerCacheAdapters();
73
74
  await options.ensureInstrumentation?.();
@@ -232,7 +233,8 @@ async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, _st
232
233
  route,
233
234
  searchParams,
234
235
  scriptNonce: props.scriptNonce ?? void 0,
235
- renderMode: normalized.renderMode
236
+ renderMode: normalized.renderMode,
237
+ renderWholeDocument: stageOptions?.renderWholeDocument === true
236
238
  });
237
239
  }, {
238
240
  cacheComponents: options.createPprFallbackShells !== void 0,
@@ -120,7 +120,11 @@ type AppRscInterceptLookupEntry = {
120
120
  slotId: string | null;
121
121
  targetRouteGraphId: string | null;
122
122
  };
123
- export declare function createAppRscRouteMatcher<Route extends AppRscRouteForMatching>(routes: Route[]): {
123
+ /**
124
+ * @param decodedPathnamePatterns Static page patterns that answer their
125
+ * segment-decoded pathname (built by `buildAppDecodedPathnamePatterns`).
126
+ */
127
+ export declare function createAppRscRouteMatcher<Route extends AppRscRouteForMatching>(routes: Route[], decodedPathnamePatterns?: readonly string[]): {
124
128
  hasInterceptionId(interceptionId: string): boolean;
125
129
  matchRoute(url: string): {
126
130
  route: Route;
@@ -130,6 +134,10 @@ export declare function createAppRscRouteMatcher<Route extends AppRscRouteForMat
130
134
  route: Route;
131
135
  params: AppRscRouteParams;
132
136
  } | null;
137
+ matchRawRequestRoute(url: string): {
138
+ route: Route;
139
+ params: AppRscRouteParams;
140
+ } | null;
133
141
  findIntercept(pathname: string, sourcePathname?: string | null, interceptionId?: string | null): AppRscInterceptMatch | null;
134
142
  };
135
143
  //#endregion