vinext 1.1.0 → 1.2.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 (234) hide show
  1. package/README.md +21 -9
  2. package/dist/build/client-build-config.d.ts +10 -0
  3. package/dist/build/client-build-config.js +18 -2
  4. package/dist/build/pages-client-assets-module.d.ts +8 -0
  5. package/dist/build/pages-client-assets-module.js +21 -1
  6. package/dist/build/prerender-paths.js +5 -3
  7. package/dist/build/run-prerender.js +1 -1
  8. package/dist/build/ssr-manifest.d.ts +2 -0
  9. package/dist/build/ssr-manifest.js +6 -2
  10. package/dist/check.js +7 -6
  11. package/dist/cli.js +2 -1
  12. package/dist/client/client-redirect-matcher.d.ts +14 -0
  13. package/dist/client/client-redirect-matcher.js +37 -0
  14. package/dist/client/client-redirects.d.ts +27 -0
  15. package/dist/client/client-redirects.js +28 -0
  16. package/dist/client/client-rewrites.d.ts +2 -1
  17. package/dist/client/client-rewrites.js +1 -1
  18. package/dist/config/config-matchers.d.ts +26 -11
  19. package/dist/config/config-matchers.js +78 -81
  20. package/dist/config/destination-params.d.ts +47 -0
  21. package/dist/config/destination-params.js +116 -0
  22. package/dist/config/next-config.d.ts +7 -0
  23. package/dist/config/next-config.js +3 -0
  24. package/dist/entries/app-rsc-entry.d.ts +4 -0
  25. package/dist/entries/app-rsc-entry.js +20 -6
  26. package/dist/entries/pages-client-entry.js +3 -1
  27. package/dist/entries/pages-server-entry.js +13 -37
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.js +134 -43
  30. package/dist/init-cloudflare.d.ts +9 -0
  31. package/dist/init-cloudflare.js +107 -31
  32. package/dist/init-platform.d.ts +4 -0
  33. package/dist/init-platform.js +27 -2
  34. package/dist/init.d.ts +1 -0
  35. package/dist/init.js +67 -3
  36. package/dist/plugins/ast-scope.d.ts +7 -0
  37. package/dist/plugins/ast-scope.js +59 -3
  38. package/dist/plugins/cache-flight-codec.d.ts +1 -1
  39. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  40. package/dist/plugins/dynamic-preload-metadata.js +9 -8
  41. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  42. package/dist/plugins/ignore-dynamic-requests.js +3 -4
  43. package/dist/plugins/middleware-server-only.d.ts +5 -4
  44. package/dist/plugins/middleware-server-only.js +8 -5
  45. package/dist/plugins/pages-node-externals.js +1 -1
  46. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  47. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  48. package/dist/plugins/strip-server-exports.js +262 -98
  49. package/dist/plugins/transform-result.d.ts +9 -5
  50. package/dist/plugins/transitive-externals.js +1 -1
  51. package/dist/plugins/typeof-window.d.ts +2 -2
  52. package/dist/plugins/use-cache-callable.js +8 -2
  53. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  54. package/dist/plugins/worker-server-only-imports.js +31 -0
  55. package/dist/plugins/worker-use-server.d.ts +25 -0
  56. package/dist/plugins/worker-use-server.js +129 -0
  57. package/dist/server/api-handler.d.ts +1 -0
  58. package/dist/server/api-handler.js +2 -1
  59. package/dist/server/app-browser-action-result.d.ts +1 -1
  60. package/dist/server/app-browser-action-result.js +9 -2
  61. package/dist/server/app-browser-navigation-controller.js +1 -1
  62. package/dist/server/app-browser-server-action-client.js +1 -1
  63. package/dist/server/app-middleware.d.ts +11 -0
  64. package/dist/server/app-middleware.js +17 -2
  65. package/dist/server/app-page-cache.d.ts +1 -1
  66. package/dist/server/app-page-cache.js +1 -1
  67. package/dist/server/app-page-dispatch.d.ts +3 -1
  68. package/dist/server/app-page-dispatch.js +4 -3
  69. package/dist/server/app-page-ppr-runtime.js +1 -1
  70. package/dist/server/app-page-render-observation.js +1 -1
  71. package/dist/server/app-page-request.js +53 -15
  72. package/dist/server/app-page-route-wiring.js +2 -2
  73. package/dist/server/app-pages-bridge.d.ts +1 -1
  74. package/dist/server/app-pages-bridge.js +2 -3
  75. package/dist/server/app-prerender-endpoints.js +9 -6
  76. package/dist/server/app-request-stage-dispatch.js +6 -2
  77. package/dist/server/app-request-stage-independent-entry.js +25 -5
  78. package/dist/server/app-response-stage-entry.js +10 -2
  79. package/dist/server/app-route-handler-cache.d.ts +1 -0
  80. package/dist/server/app-route-handler-cache.js +1 -0
  81. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  82. package/dist/server/app-route-handler-dispatch.js +5 -2
  83. package/dist/server/app-route-handler-execution.d.ts +1 -0
  84. package/dist/server/app-route-handler-execution.js +1 -0
  85. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  86. package/dist/server/app-route-handler-runtime.js +45 -24
  87. package/dist/server/app-router-entry.js +24 -5
  88. package/dist/server/app-rsc-cache-busting.js +1 -1
  89. package/dist/server/app-rsc-errors.js +20 -16
  90. package/dist/server/app-rsc-handler.d.ts +7 -0
  91. package/dist/server/app-rsc-handler.js +85 -35
  92. package/dist/server/app-rsc-response-stage.js +4 -2
  93. package/dist/server/app-segment-config.d.ts +2 -1
  94. package/dist/server/app-server-action-execution.d.ts +2 -1
  95. package/dist/server/app-server-action-execution.js +61 -22
  96. package/dist/server/app-ssr-entry.js +2 -1
  97. package/dist/server/app-worker-stages.d.ts +5 -0
  98. package/dist/server/app-worker-stages.js +1 -1
  99. package/dist/server/cacheability-request.js +2 -2
  100. package/dist/server/client-build-files.d.ts +16 -0
  101. package/dist/server/client-build-files.js +36 -0
  102. package/dist/server/client-reuse-manifest.js +1 -1
  103. package/dist/server/dev-server.js +38 -27
  104. package/dist/server/dev-stack-sourcemap.js +18 -23
  105. package/dist/server/document-initial-head.d.ts +0 -11
  106. package/dist/server/document-initial-head.js +1 -1
  107. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  108. package/dist/server/forwarded-middleware-context.js +22 -0
  109. package/dist/server/headers.d.ts +4 -1
  110. package/dist/server/headers.js +4 -1
  111. package/dist/server/http-conditional.d.ts +11 -0
  112. package/dist/server/http-conditional.js +10 -1
  113. package/dist/server/http-error-responses.d.ts +1 -1
  114. package/dist/server/image-optimization.d.ts +38 -0
  115. package/dist/server/image-optimization.js +93 -4
  116. package/dist/server/isr-cache.d.ts +10 -0
  117. package/dist/server/isr-cache.js +41 -6
  118. package/dist/server/metadata-route-response.js +9 -8
  119. package/dist/server/middleware-matcher.d.ts +2 -0
  120. package/dist/server/middleware-matcher.js +23 -2
  121. package/dist/server/middleware-runtime.d.ts +13 -0
  122. package/dist/server/middleware-runtime.js +33 -20
  123. package/dist/server/middleware.d.ts +1 -1
  124. package/dist/server/middleware.js +3 -1
  125. package/dist/server/multi-stage.d.ts +11 -0
  126. package/dist/server/normalize-path.d.ts +0 -13
  127. package/dist/server/normalize-path.js +1 -34
  128. package/dist/server/pages-api-route.d.ts +3 -2
  129. package/dist/server/pages-api-route.js +4 -3
  130. package/dist/server/pages-cache-tag.d.ts +12 -0
  131. package/dist/server/pages-cache-tag.js +19 -0
  132. package/dist/server/pages-client-assets.d.ts +2 -0
  133. package/dist/server/pages-data-route.d.ts +10 -0
  134. package/dist/server/pages-data-route.js +19 -2
  135. package/dist/server/pages-document-asset-props.d.ts +5 -1
  136. package/dist/server/pages-document-asset-props.js +13 -5
  137. package/dist/server/pages-document-splice.d.ts +19 -0
  138. package/dist/server/pages-document-splice.js +55 -0
  139. package/dist/server/pages-i18n.d.ts +12 -1
  140. package/dist/server/pages-i18n.js +18 -4
  141. package/dist/server/pages-node-compat.d.ts +16 -5
  142. package/dist/server/pages-node-compat.js +20 -12
  143. package/dist/server/pages-page-data.d.ts +32 -24
  144. package/dist/server/pages-page-data.js +50 -120
  145. package/dist/server/pages-page-handler.d.ts +0 -6
  146. package/dist/server/pages-page-handler.js +188 -126
  147. package/dist/server/pages-page-response.d.ts +12 -6
  148. package/dist/server/pages-page-response.js +41 -34
  149. package/dist/server/pages-request-pipeline.d.ts +10 -2
  150. package/dist/server/pages-request-pipeline.js +37 -18
  151. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  152. package/dist/server/pages-request-stage-entry.js +71 -12
  153. package/dist/server/pages-response-stage-entry.js +4 -0
  154. package/dist/server/pages-revalidate.js +1 -1
  155. package/dist/server/pages-router-entry.js +4 -3
  156. package/dist/server/prerender-manifest.d.ts +3 -3
  157. package/dist/server/prerender-manifest.js +4 -3
  158. package/dist/server/prerender-work-unit-setup.js +4 -0
  159. package/dist/server/prod-server.d.ts +12 -0
  160. package/dist/server/prod-server.js +199 -69
  161. package/dist/server/proper-error.d.ts +4 -0
  162. package/dist/server/proper-error.js +31 -0
  163. package/dist/server/request-pipeline.d.ts +4 -1
  164. package/dist/server/request-pipeline.js +3 -2
  165. package/dist/server/revalidation-request.d.ts +1 -0
  166. package/dist/server/revalidation-request.js +1 -1
  167. package/dist/server/rsc-stream-hints.js +31 -2
  168. package/dist/server/seed-cache.d.ts +1 -0
  169. package/dist/server/seed-cache.js +5 -4
  170. package/dist/server/server-reference-loader.d.ts +4 -0
  171. package/dist/server/server-reference-loader.js +29 -0
  172. package/dist/server/socket-error-backstop.d.ts +9 -1
  173. package/dist/server/socket-error-backstop.js +46 -17
  174. package/dist/server/static-file-signal.d.ts +15 -4
  175. package/dist/server/static-file-signal.js +46 -12
  176. package/dist/server/worker-image-optimization.d.ts +34 -0
  177. package/dist/server/worker-image-optimization.js +86 -0
  178. package/dist/server/worker-stages.d.ts +1 -1
  179. package/dist/server/worker-stages.js +2 -2
  180. package/dist/server/worker-utils.d.ts +8 -3
  181. package/dist/server/worker-utils.js +12 -7
  182. package/dist/shims/app-router-scroll.js +4 -1
  183. package/dist/shims/app-router-tree-context.d.ts +5 -0
  184. package/dist/shims/app-router-tree-context.js +27 -0
  185. package/dist/shims/cache-callable-runtime.d.ts +4 -2
  186. package/dist/shims/cache-callable-runtime.js +21 -12
  187. package/dist/shims/cache-handler.js +1 -9
  188. package/dist/shims/cache-runtime.d.ts +6 -1
  189. package/dist/shims/cache-runtime.js +2 -1
  190. package/dist/shims/cache.d.ts +2 -2
  191. package/dist/shims/cache.js +40 -13
  192. package/dist/shims/cdn-cache.d.ts +8 -0
  193. package/dist/shims/cdn-cache.js +17 -1
  194. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  195. package/dist/shims/disable-smooth-scroll.js +36 -0
  196. package/dist/shims/document-placeholders.d.ts +19 -0
  197. package/dist/shims/document-placeholders.js +45 -0
  198. package/dist/shims/document.js +21 -5
  199. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  200. package/dist/shims/dynamic-preload-chunks.js +13 -9
  201. package/dist/shims/dynamic.d.ts +1 -1
  202. package/dist/shims/dynamic.js +60 -8
  203. package/dist/shims/fetch-cache-state.d.ts +66 -0
  204. package/dist/shims/fetch-cache-state.js +92 -0
  205. package/dist/shims/fetch-cache.d.ts +3 -67
  206. package/dist/shims/fetch-cache.js +160 -125
  207. package/dist/shims/head.d.ts +10 -6
  208. package/dist/shims/head.js +15 -13
  209. package/dist/shims/image.js +2 -1
  210. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  211. package/dist/shims/internal/work-store-scope.js +24 -0
  212. package/dist/shims/navigation.js +31 -5
  213. package/dist/shims/request-context.d.ts +2 -0
  214. package/dist/shims/request-state-types.d.ts +2 -1
  215. package/dist/shims/router.js +12 -20
  216. package/dist/shims/server.d.ts +16 -0
  217. package/dist/shims/server.js +24 -4
  218. package/dist/shims/unified-request-context.d.ts +1 -1
  219. package/dist/shims/url-utils.d.ts +2 -0
  220. package/dist/shims/url-utils.js +3 -1
  221. package/dist/utils/client-runtime-metadata.js +1 -1
  222. package/dist/utils/hash.d.ts +7 -0
  223. package/dist/utils/hash.js +11 -1
  224. package/dist/utils/package-name.d.ts +6 -0
  225. package/dist/utils/package-name.js +12 -1
  226. package/dist/utils/plugin-rsc-version.d.ts +4 -0
  227. package/dist/utils/plugin-rsc-version.js +15 -0
  228. package/dist/utils/prerender-output-paths.js +15 -4
  229. package/dist/utils/public-file-path.d.ts +10 -2
  230. package/dist/utils/public-file-path.js +13 -9
  231. package/dist/utils/public-routes.js +2 -2
  232. package/dist/utils/react-version.d.ts +3 -0
  233. package/dist/utils/react-version.js +1 -1
  234. package/package.json +7 -3
@@ -2,22 +2,25 @@ import { isBotUserAgent } from "../utils/html-limited-bots.js";
2
2
  import { getRequestExecutionContext } from "../shims/request-context.js";
3
3
  import { NEXTJS_CACHE_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER } from "./headers.js";
4
4
  import { reportRequestError } from "./instrumentation.js";
5
+ import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
5
6
  import { markRouteCacheabilityExplicitResponsePolicy } from "../shims/cacheability-classification.js";
6
7
  import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy, shouldUseNextDeployCacheControl } from "./cache-control.js";
7
8
  import { buildMissIsrCacheControl } from "./isr-decision.js";
8
- import { fnv1a52 } from "../utils/hash.js";
9
9
  import { isrCacheControl } from "./isr-cache.js";
10
- import { encodeCacheTag } from "../utils/encode-cache-tag.js";
10
+ import { fnv1a52 } from "../utils/hash.js";
11
11
  import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
12
12
  import { withScriptNonce } from "../shims/script-nonce-context.js";
13
13
  import { createNonceAttribute, escapeHtmlAttr } from "./html.js";
14
+ import { createDocumentPlaceholderToken, withDocumentPlaceholders } from "../shims/document-placeholders.js";
14
15
  import { applyDocumentAssetProps, extractDocumentAssetProps } from "./pages-document-asset-props.js";
16
+ import { spliceDocumentHeadClose, spliceDocumentHeadOpen, spliceDocumentScripts, splitDocumentAtMain } from "./pages-document-splice.js";
15
17
  import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
16
18
  import { setCacheStateHeaders } from "./cache-headers.js";
19
+ import { pagesPathCacheTag } from "./pages-cache-tag.js";
17
20
  import { readStreamAsText } from "../utils/text-stream.js";
18
21
  import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
19
22
  import { callDocumentGetInitialProps } from "./document-initial-head.js";
20
- import { matchesIfNoneMatch } from "./http-conditional.js";
23
+ import { isPageNotModified } from "./http-conditional.js";
21
24
  import React from "react";
22
25
  //#region src/server/pages-page-response.ts
23
26
  /**
@@ -31,14 +34,13 @@ function generatePagesETag(payload) {
31
34
  return "\"" + fnv1a52(payload).toString(36) + payload.length.toString(36) + "\"";
32
35
  }
33
36
  /**
34
- * Returns true when a request `Cache-Control` header asks to bypass the 304
35
- * short-circuit. Mirrors the `fresh` package's check used by Next.js's
36
- * `sendEtagResponse` (`/(?:^|,)\s*?no-cache\s*?(?:,|$)/`). Shared by the
37
- * fresh-MISS bot path here and the ISR HIT/STALE paths in
38
- * `pages-page-data.ts` so the two cannot drift.
37
+ * Whether a render answers a matching If-None-Match with a 304. Next.js stores
38
+ * the page before it evaluates the ETag, so when the CDN adapter stores the
39
+ * completed response, the render returns the page and the adapter sends the 304.
40
+ * A page whose status carries no body has nothing to store.
39
41
  */
40
- function requestsNoCache(cacheControl) {
41
- return /(?:^|,)\s*no-cache\s*(?:,|$)/.test(cacheControl ?? "");
42
+ function rendersPagesNotModified(status) {
43
+ return status === 204 || status === 205 || status === 304 || getCdnCacheAdapter().deferNotModifiedResponse?.() !== true;
42
44
  }
43
45
  function buildPagesFontHeadHtml(fontLinks, fontPreloads, fontStyles, scriptNonce) {
44
46
  let html = "";
@@ -71,33 +73,34 @@ function buildPagesNextDataScript(options) {
71
73
  };
72
74
  return `<script id="__NEXT_DATA__" type="application/json"${createNonceAttribute(options.scriptNonce)}>${options.safeJsonStringify(nextDataPayload)}<\/script>`;
73
75
  }
74
- async function buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, options) {
76
+ async function buildPagesShellHtml(fontHeadHTML, nextDataScript, options) {
75
77
  if (options.DocumentComponent) {
76
78
  const docProps = options.resolvedDocProps ?? await loadUserDocumentInitialProps(options.DocumentComponent);
77
79
  const docElement = docProps ? React.createElement(options.DocumentComponent, docProps) : React.createElement(options.DocumentComponent);
78
- const renderedDocument = extractDocumentAssetProps(await options.renderDocumentToString(docElement));
80
+ const token = createDocumentPlaceholderToken();
81
+ const renderedDocument = extractDocumentAssetProps(await options.renderDocumentToString(withDocumentPlaceholders(docElement, token)), token);
79
82
  let html = renderedDocument.html;
80
83
  const generatedAssetTags = applyDocumentAssetProps(options.assetTags, renderedDocument.props, {
81
84
  configuredCrossOrigin: options.crossOrigin,
82
85
  scriptOwner: options.disableOptimizedLoading ? "next-script" : "head"
83
86
  });
84
87
  const generatedNextDataScript = applyDocumentAssetProps(nextDataScript, renderedDocument.props, { configuredCrossOrigin: options.crossOrigin });
85
- html = html.replace("__NEXT_MAIN__", bodyMarker);
86
- if (options.ssrHeadHTML) html = html.replace(/<head(?:\s[^>]*)?>/i, (openingHead) => `${openingHead}${options.ssrHeadHTML}`);
87
- if (generatedAssetTags || fontHeadHTML || options.tailHeadHTML) html = html.replace("</head>", ` ${fontHeadHTML}\n ${generatedAssetTags}\n ${options.tailHeadHTML}\n</head>`);
88
- html = html.replace("<!-- __NEXT_SCRIPTS__ -->", generatedNextDataScript);
89
- if (!html.includes("__NEXT_DATA__")) html = html.replace("</body>", ` ${generatedNextDataScript}\n</body>`);
90
- return html;
88
+ html = spliceDocumentHeadOpen(html, token, options.ssrHeadHTML);
89
+ html = spliceDocumentHeadClose(html, token, generatedAssetTags || fontHeadHTML || options.tailHeadHTML ? ` ${fontHeadHTML}\n ${generatedAssetTags}\n ${options.tailHeadHTML}\n` : "");
90
+ html = spliceDocumentScripts(html, token, generatedNextDataScript);
91
+ return splitDocumentAtMain(html, token);
91
92
  }
92
93
  const generatedAssetTags = applyDocumentAssetProps(options.assetTags, {}, { configuredCrossOrigin: options.crossOrigin });
93
94
  const generatedNextDataScript = applyDocumentAssetProps(nextDataScript, {}, { configuredCrossOrigin: options.crossOrigin });
94
- return `<!DOCTYPE html>
95
+ return {
96
+ prefix: `<!DOCTYPE html>
95
97
  <html>
96
98
  <head>
97
99
  ${options.ssrHeadHTML}${fontHeadHTML}\n ${generatedAssetTags}\n ${options.tailHeadHTML}\n</head>
98
100
  <body>
99
- <div id="__next">${bodyMarker}</div>\n ${generatedNextDataScript}\n</body>
100
- </html>`;
101
+ <div id="__next">`,
102
+ suffix: `</div>\n ${generatedNextDataScript}\n</body>\n</html>`
103
+ };
101
104
  }
102
105
  async function buildPagesCompositeStream(bodyStream, shellPrefix, shellSuffix) {
103
106
  const encoder = new TextEncoder();
@@ -182,7 +185,6 @@ async function renderPagesPageResponse(options) {
182
185
  nextData: options.nextData,
183
186
  vinext: options.vinext
184
187
  });
185
- const bodyMarker = "<!--VINEXT_STREAM_BODY-->";
186
188
  const documentResult = await (options.traceDocument ?? ((callback) => callback()))(async () => {
187
189
  const documentRenderPage = await runDocumentRenderPage({
188
190
  DocumentComponent: options.DocumentComponent,
@@ -200,7 +202,7 @@ async function renderPagesPageResponse(options) {
200
202
  asPath: options.routeUrl
201
203
  }
202
204
  });
203
- if (options.documentReqRes?.res.headersSent && options.documentReqRes.responsePromise) return {
205
+ if (!options.isStaticPropsRoute && options.documentReqRes?.res.headersSent && options.documentReqRes.responsePromise) return {
204
206
  documentRenderPage,
205
207
  responseSent: true
206
208
  };
@@ -233,7 +235,7 @@ async function renderPagesPageResponse(options) {
233
235
  if (tailHeadHTML) tailHeadHTML += "\n ";
234
236
  tailHeadHTML += documentRenderPage.stylesHTML;
235
237
  }
236
- const shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {
238
+ const { prefix: shellPrefix, suffix: shellSuffix } = await buildPagesShellHtml(fontHeadHTML, nextDataScript, {
237
239
  assetTags: options.assetTags,
238
240
  disableOptimizedLoading: options.disableOptimizedLoading,
239
241
  DocumentComponent: options.DocumentComponent,
@@ -244,9 +246,6 @@ async function renderPagesPageResponse(options) {
244
246
  crossOrigin: options.crossOrigin
245
247
  });
246
248
  options.clearSsrContext();
247
- const markerIndex = shellHtml.indexOf(bodyMarker);
248
- const shellPrefix = shellHtml.slice(0, markerIndex);
249
- const shellSuffix = shellHtml.slice(markerIndex + 25);
250
249
  const responseHeaders = new Headers({ "Content-Type": "text/html; charset=utf-8" });
251
250
  const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes ?? options.documentReqRes?.res ?? null, options.statusCode);
252
251
  let responseBodyStream = bodyStream;
@@ -264,26 +263,30 @@ async function renderPagesPageResponse(options) {
264
263
  setCache: options.isrSet,
265
264
  shellPrefix,
266
265
  shellSuffix,
267
- status: finalStatus,
266
+ status: options.isStaticPropsRoute ? options.statusCode ?? 200 : finalStatus,
268
267
  stream: cacheBodyStream
269
268
  };
270
- if (options.isOnDemandRevalidate) await writePagesIsrCache(cacheWriteOptions);
269
+ if (options.isOnDemandRevalidate || options.isStaleRegeneration) await writePagesIsrCache(cacheWriteOptions);
271
270
  else schedulePagesIsrCacheWrite(cacheWriteOptions);
272
271
  }
272
+ if (!options.isOnDemandRevalidate && options.documentReqRes?.res.headersSent && options.documentReqRes.responsePromise) {
273
+ responseBodyStream.cancel().catch(() => {});
274
+ return options.documentReqRes.responsePromise;
275
+ }
273
276
  const compositeStream = await buildPagesCompositeStream(responseBodyStream, shellPrefix, shellSuffix);
274
277
  const userSetCacheControl = responseHeaders.has("Cache-Control");
275
278
  if (hasCdnResponsePolicy(responseHeaders)) markRouteCacheabilityExplicitResponsePolicy();
276
279
  if (options.scriptNonce) responseHeaders.set("Cache-Control", NO_STORE_CACHE_CONTROL);
277
280
  else if (options.isrRevalidateSeconds !== null) {
278
281
  const isrPathname = options.isrCachePathname ?? options.routeUrl.split("?")[0];
279
- const stem = isrPathname.endsWith("/") ? isrPathname.slice(0, -1) : isrPathname;
282
+ const pathTag = pagesPathCacheTag(isrPathname);
280
283
  applyCdnResponseHeaders(responseHeaders, {
281
284
  cacheControl: buildMissIsrCacheControl(options.isrRevalidateSeconds, options.expireSeconds),
282
- tags: [encodeCacheTag(`_N_T_${stem || "/"}`)]
285
+ tags: [pathTag]
283
286
  });
284
287
  if (options.isOnDemandRevalidate) {
285
288
  responseHeaders.set(NEXTJS_CACHE_HEADER, "REVALIDATED");
286
- responseHeaders.set(VINEXT_REVALIDATED_CACHE_TAG_HEADER, encodeCacheTag(`_N_T_${stem || "/"}`));
289
+ responseHeaders.set(VINEXT_REVALIDATED_CACHE_TAG_HEADER, encodeURIComponent(pathTag));
287
290
  } else setCacheStateHeaders(responseHeaders, "MISS");
288
291
  } else if (options.isStaticPropsRoute && shouldUseNextDeployCacheControl()) responseHeaders.set("Cache-Control", BROWSER_REVALIDATE_CACHE_CONTROL);
289
292
  else if (options.gsspRes && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
@@ -292,7 +295,11 @@ async function renderPagesPageResponse(options) {
292
295
  const fullHtml = await readStreamAsText(compositeStream);
293
296
  const etag = generatePagesETag(fullHtml);
294
297
  responseHeaders.set("ETag", etag);
295
- if (!requestsNoCache(options.requestCacheControl) && options.ifNoneMatch && matchesIfNoneMatch(options.ifNoneMatch, etag)) return new Response(null, {
298
+ if (isPageNotModified({
299
+ cacheControl: options.requestCacheControl,
300
+ ifModifiedSince: options.ifModifiedSince,
301
+ ifNoneMatch: options.ifNoneMatch
302
+ }, etag) && rendersPagesNotModified(finalStatus)) return new Response(null, {
296
303
  status: 304,
297
304
  headers: responseHeaders
298
305
  });
@@ -307,4 +314,4 @@ async function renderPagesPageResponse(options) {
307
314
  }), { __vinextStreamedHtmlResponse: true });
308
315
  }
309
316
  //#endregion
310
- export { buildPagesNextDataScript, generatePagesETag, isPagesStreamingBot, renderPagesPageResponse, requestsNoCache };
317
+ export { buildPagesNextDataScript, generatePagesETag, isPagesStreamingBot, renderPagesPageResponse, rendersPagesNotModified };
@@ -15,7 +15,7 @@ type PageRouteMatch = {
15
15
  dataKind?: PagesRouteDataKind;
16
16
  };
17
17
  };
18
- export declare function fetchWorkerFilesystemRoute(request: Request, requestPathname: string, phase: FilesystemRoutePhase, fetchAsset: (request: Request) => Promise<Response>, publicFiles: ReadonlySet<string>, basePath?: string, assetPathPrefix?: string): Promise<Response | false>;
18
+ export declare function fetchWorkerFilesystemRoute(request: Request, requestPathname: string, phase: FilesystemRoutePhase, fetchAsset: (request: Request) => Promise<Response>, publicFiles: ReadonlySet<string>, basePath?: string, assetPathPrefix?: string, hadBasePath?: boolean): Promise<Response | false>;
19
19
  export type MiddlewareResult = {
20
20
  continue: boolean;
21
21
  /** The pathname matches middleware, irrespective of request `has`/`missing` conditions. */
@@ -23,6 +23,8 @@ export type MiddlewareResult = {
23
23
  redirectUrl?: string;
24
24
  redirectStatus?: number;
25
25
  rewriteUrl?: string;
26
+ /** False when a same-origin rewrite target was outside the basePath. */
27
+ rewriteHadBasePath?: boolean;
26
28
  rewriteStatus?: number;
27
29
  status?: number;
28
30
  responseHeaders?: Iterable<[string, string]>;
@@ -33,6 +35,8 @@ export type PagesPipelineDeps = {
33
35
  assetPrefix?: string;
34
36
  basePath: string;
35
37
  trailingSlash: boolean;
38
+ /** next.config `skipTrailingSlashRedirect`: serve both slash forms without a 308. */
39
+ skipTrailingSlashRedirect?: boolean;
36
40
  i18nConfig: NextI18nConfig | null;
37
41
  configRedirects: NextRedirect[];
38
42
  configRewrites: {
@@ -67,6 +71,7 @@ export type PagesPipelineDeps = {
67
71
  matchApiRoute?: ((url: string, request: Request) => PageRouteMatch | null | Promise<PageRouteMatch | null>) | null;
68
72
  runMiddleware?: ((request: Request, ctx: unknown, opts: {
69
73
  isDataRequest: boolean;
74
+ isStaleDataRequest?: boolean;
70
75
  }) => Promise<MiddlewareResult>) | null;
71
76
  /** Original URL presented to middleware when URL normalization is disabled. */
72
77
  middlewareRequest?: Request;
@@ -86,10 +91,13 @@ export type PagesPipelineDeps = {
86
91
  * Optional filesystem/static-asset probe supplied by each runtime adapter.
87
92
  * Called post-middleware (so middleware can intercept/redirect public files) with the
88
93
  * resolved basePath-stripped pathname and URL plus the staged middleware response headers.
94
+ * `request` carries the request headers middleware overrode.
89
95
  * Node may write directly to `res` and return true; dev/Workers return a Response.
90
96
  * Resolves false to continue through rewrites, API routes, and page rendering.
91
97
  */
92
- serveFilesystemRoute?: ((requestPathname: string, stagedHeaders: HeaderRecord, phase: FilesystemRoutePhase, resolvedUrl: string) => Promise<boolean | Response>) | null;
98
+ serveFilesystemRoute?: ((requestPathname: string, stagedHeaders: HeaderRecord, phase: FilesystemRoutePhase, resolvedUrl: string, request: Request,
99
+ /** Whether the resolved pathname is under the basePath, as Next.js checks before serving filesystem items. */
100
+ resolvedPathnameHasBasePath: boolean) => Promise<boolean | Response>) | null;
93
101
  };
94
102
  /**
95
103
  * Wrap an adapter's `runMiddleware` callback so middleware receives the original
@@ -3,16 +3,17 @@ import { requestContextFromRequest } from "../config/request-context.js";
3
3
  import { isExternalUrl } from "../utils/external-url.js";
4
4
  import { applyMiddlewareRequestHeaders, matchRedirect, matchRewrite, preserveRedirectDestinationQuery, proxyExternalRequest, sanitizeDestination } from "../config/config-matchers.js";
5
5
  import { assetPrefixPathname, isNextStaticPath } from "../utils/asset-prefix.js";
6
+ import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
6
7
  import { addBasePathToPathname, hasBasePath } from "../utils/base-path.js";
7
8
  import { patternToNextFormat } from "../routing/route-validation.js";
8
9
  import { VINEXT_PRERENDER_REWRITTEN_HEADER } from "./headers.js";
9
10
  import { methodNotAllowedResponse, notFoundStaticAssetResponse, sanitizeMethodNotAllowedHeaders } from "./http-error-responses.js";
10
11
  import { cloneRequestWithUrl, normalizeTrailingSlash } from "./request-pipeline.js";
11
- import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pages-i18n.js";
12
+ import { detectPathnameLocale, normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pages-i18n.js";
13
+ import { buildMiddlewarePrefetchSkipResponse } from "./pages-data-route.js";
12
14
  import { markRouteCacheabilityDynamic } from "../shims/cacheability-classification.js";
13
15
  import { isOnDemandRevalidateRequest } from "./revalidation-request.js";
14
16
  import { mergeRewriteQuery } from "../utils/query.js";
15
- import { buildMiddlewarePrefetchSkipResponse } from "./pages-data-route.js";
16
17
  import { setFrameworkRequestRoute } from "./request-tracing.js";
17
18
  import { applyConfigHeadersToHeaderRecord } from "./config-headers.js";
18
19
  import { finalizeMissingStaticAssetResponse, mergeHeaders } from "./worker-utils.js";
@@ -32,19 +33,23 @@ function headersFromRecord(record) {
32
33
  else headers.set(name, value);
33
34
  return headers;
34
35
  }
35
- async function fetchWorkerFilesystemRoute(request, requestPathname, phase, fetchAsset, publicFiles, basePath = "", assetPathPrefix = "") {
36
+ async function fetchWorkerFilesystemRoute(request, requestPathname, phase, fetchAsset, publicFiles, basePath = "", assetPathPrefix = "", hadBasePath = true) {
36
37
  const isRetrievalMethod = request.method === "GET" || request.method === "HEAD";
37
- if (phase === "direct" && isRetrievalMethod || requestPathname === "/api" || requestPathname.startsWith("/api/")) return false;
38
38
  const assetUrl = new URL(request.url);
39
39
  assetUrl.pathname = requestPathname;
40
40
  assetUrl.search = "";
41
41
  const decodedAssetUrl = new URL(assetUrl);
42
+ let publicFileKey;
42
43
  try {
43
44
  decodedAssetUrl.pathname = decodeURIComponent(assetUrl.pathname);
45
+ publicFileKey = normalizePathnameForRouteMatchStrict(assetUrl.pathname);
44
46
  } catch {
45
47
  return false;
46
48
  }
47
- if (!publicFiles.has(assetUrl.pathname) && !publicFiles.has(decodedAssetUrl.pathname) && !isNextStaticPath(decodedAssetUrl.pathname, basePath, assetPathPrefix)) return false;
49
+ const isPublicFile = (phase !== "direct" || hadBasePath) && publicFiles.has(publicFileKey);
50
+ if (!isPublicFile && (requestPathname === "/api" || requestPathname.startsWith("/api/"))) return false;
51
+ if (phase === "direct" && isRetrievalMethod && !isPublicFile) return false;
52
+ if (!isPublicFile && !isNextStaticPath(decodedAssetUrl.pathname, basePath, assetPathPrefix)) return false;
48
53
  const response = await fetchAsset(isRetrievalMethod ? new Request(assetUrl, request) : new Request(assetUrl, {
49
54
  method: "HEAD",
50
55
  headers: request.headers
@@ -89,7 +94,7 @@ function wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) {
89
94
  * runPagesRequest receives a "clean" request with basePath-stripped URL.
90
95
  */
91
96
  async function runPagesRequest(request, deps) {
92
- const { basePath, trailingSlash, i18nConfig, configRedirects, configRewrites, configHeaders, hadBasePath, isDataReq, isDataRequest } = deps;
97
+ const { basePath, trailingSlash, skipTrailingSlashRedirect, i18nConfig, configRedirects, configRewrites, configHeaders, hadBasePath, isDataReq, isDataRequest } = deps;
93
98
  const conditionalRedirectCacheability = deps.recordCacheability === false ? void 0 : markConditionalRedirectCacheability;
94
99
  const conditionalRewriteCacheability = deps.recordCacheability === false ? void 0 : markConditionalRewriteCacheability;
95
100
  const proxyExternal = (currentReq, externalUrl) => deps.proxyExternal ? deps.proxyExternal(currentReq, externalUrl) : proxyExternalRequest(currentReq, externalUrl);
@@ -104,7 +109,7 @@ async function runPagesRequest(request, deps) {
104
109
  hadBasePath
105
110
  };
106
111
  {
107
- const trailingSlashRedirect = isDataReq ? null : normalizeTrailingSlash(pathname, basePath, trailingSlash, search);
112
+ const trailingSlashRedirect = isDataReq || skipTrailingSlashRedirect ? null : normalizeTrailingSlash(pathname, hadBasePath ? basePath : "", trailingSlash, search);
108
113
  if (trailingSlashRedirect) return {
109
114
  type: "response",
110
115
  response: trailingSlashRedirect
@@ -117,7 +122,7 @@ async function runPagesRequest(request, deps) {
117
122
  const redirect = matchRedirect(requestConfigMatchPathname, configRedirects, reqCtx, basePathState, conditionalRedirectCacheability);
118
123
  if (redirect) {
119
124
  const dest = sanitizeDestination(basePath && hadBasePath && !isExternalUrl(redirect.destination) && !hasBasePath(redirect.destination, basePath) ? basePath + redirect.destination : redirect.destination);
120
- const location = preserveRedirectDestinationQuery(dest, deps.rawSearch ?? search);
125
+ const location = preserveRedirectDestinationQuery(dest, deps.rawSearch ?? search, redirect.destinationQuery);
121
126
  return {
122
127
  type: "response",
123
128
  response: new Response(null, {
@@ -130,6 +135,11 @@ async function runPagesRequest(request, deps) {
130
135
  const originalResolvedUrl = pathname + search;
131
136
  let resolvedUrl = originalResolvedUrl;
132
137
  let resolvedPathnameIsRequestPathname = true;
138
+ let resolvedPathnameHasBasePath = hadBasePath;
139
+ const currentBasePathState = () => ({
140
+ basePath,
141
+ hadBasePath: resolvedPathnameHasBasePath
142
+ });
133
143
  const middlewareHeaders = {};
134
144
  const mergeConfigHeadersIntoEarlyResponse = (response) => {
135
145
  if (configHeaders.length === 0) return response;
@@ -146,10 +156,10 @@ async function runPagesRequest(request, deps) {
146
156
  let middlewareStatus;
147
157
  const serveFilesystemRoute = async (requestPathname, phase) => {
148
158
  if (!deps.serveFilesystemRoute) return null;
149
- const served = await deps.serveFilesystemRoute(requestPathname, middlewareHeaders, phase, resolvedUrl);
159
+ const served = await deps.serveFilesystemRoute(requestPathname, middlewareHeaders, phase, resolvedUrl, request, resolvedPathnameHasBasePath);
150
160
  if (served instanceof Response) {
151
161
  const isStaticMethodNotAllowed = served.status === 405 && served.headers.get("allow") === "GET, HEAD";
152
- const response = mergeHeaders(served, middlewareHeaders, isStaticMethodNotAllowed ? void 0 : middlewareStatus);
162
+ const response = mergeHeaders(served, middlewareHeaders, isStaticMethodNotAllowed || !served.ok || served.status === 206 ? void 0 : middlewareStatus);
153
163
  if (isStaticMethodNotAllowed) sanitizeMethodNotAllowedHeaders(response.headers, "GET, HEAD");
154
164
  return {
155
165
  type: "response",
@@ -159,7 +169,10 @@ async function runPagesRequest(request, deps) {
159
169
  return served ? { type: "handled" } : null;
160
170
  };
161
171
  if (!isOnDemandRevalidate && typeof deps.runMiddleware === "function") {
162
- const result = await deps.runMiddleware(deps.middlewareRequest ?? request, deps.ctx ?? null, { isDataRequest });
172
+ const result = await deps.runMiddleware(deps.middlewareRequest ?? request, deps.ctx ?? null, {
173
+ isDataRequest,
174
+ isStaleDataRequest: isDataRequest && !!deps.dataNotFoundResponse
175
+ });
163
176
  if (deps.recordCacheability !== false && result.pathnameEligible) markRouteCacheabilityDynamic("middleware can match this pathname");
164
177
  if (result.waitUntilPromises && result.waitUntilPromises.length > 0) {
165
178
  const ctx = deps.ctx;
@@ -200,6 +213,7 @@ async function runPagesRequest(request, deps) {
200
213
  if (result.rewriteUrl) {
201
214
  resolvedUrl = result.rewriteUrl;
202
215
  resolvedPathnameIsRequestPathname = false;
216
+ resolvedPathnameHasBasePath = result.rewriteHadBasePath ?? true;
203
217
  }
204
218
  middlewareStatus = result.status ?? result.rewriteStatus;
205
219
  }
@@ -220,8 +234,8 @@ async function runPagesRequest(request, deps) {
220
234
  const matchedPathnameForRoute = (routePattern) => {
221
235
  const matchedPathname = routePattern ? patternToNextFormat(routePattern) : resolvedPathname;
222
236
  if (!i18nConfig) return matchedPathname;
223
- const resolvedLocale = resolvedPathname.split("/", 3)[1];
224
- if (resolvedLocale && i18nConfig.locales.includes(resolvedLocale)) return matchedPathname === "/" ? `/${resolvedLocale}` : `/${resolvedLocale}${matchedPathname}`;
237
+ const resolvedLocale = detectPathnameLocale(resolvedPathname, i18nConfig.locales);
238
+ if (resolvedLocale) return matchedPathname === "/" ? `/${resolvedLocale}` : `/${resolvedLocale}${matchedPathname}`;
225
239
  return matchResolvedPathname(matchedPathname);
226
240
  };
227
241
  const buildMiddlewarePrefetchSkipResult = (match) => {
@@ -249,7 +263,7 @@ async function runPagesRequest(request, deps) {
249
263
  }
250
264
  let configRewriteFired = false;
251
265
  for (const rewrite of configRewrites.beforeFiles ?? []) {
252
- const rewritten = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState, configSourcePathname(), conditionalRewriteCacheability);
266
+ const rewritten = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), currentBasePathState(), configSourcePathname(), conditionalRewriteCacheability);
253
267
  if (rewritten) {
254
268
  if (isExternalUrl(rewritten)) return {
255
269
  type: "response",
@@ -258,6 +272,7 @@ async function runPagesRequest(request, deps) {
258
272
  resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
259
273
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
260
274
  resolvedPathnameIsRequestPathname = false;
275
+ resolvedPathnameHasBasePath = !basePath || rewrite.basePath !== false;
261
276
  configRewriteFired = true;
262
277
  }
263
278
  }
@@ -310,7 +325,7 @@ async function runPagesRequest(request, deps) {
310
325
  let pageMatch = !isOutsideBasePathUnclaimed() && deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
311
326
  let resolvedPathnameChanged = false;
312
327
  if (!pageMatch || pageMatch.route.isDynamic) for (const rewrite of configRewrites.afterFiles ?? []) {
313
- const rewritten = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState, configSourcePathname(), conditionalRewriteCacheability);
328
+ const rewritten = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), currentBasePathState(), configSourcePathname(), conditionalRewriteCacheability);
314
329
  if (rewritten) {
315
330
  if (isExternalUrl(rewritten)) return {
316
331
  type: "response",
@@ -319,6 +334,7 @@ async function runPagesRequest(request, deps) {
319
334
  resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
320
335
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
321
336
  resolvedPathnameIsRequestPathname = false;
337
+ resolvedPathnameHasBasePath = !basePath || rewrite.basePath !== false;
322
338
  configRewriteFired = true;
323
339
  resolvedPathnameChanged = true;
324
340
  const afterFilesFilesystemResult = await serveFilesystemRoute(resolvedPathname, "afterFiles");
@@ -337,7 +353,7 @@ async function runPagesRequest(request, deps) {
337
353
  if (typeof deps.renderPage === "function") {
338
354
  let renderPageMatch = pageMatch;
339
355
  if ((isOutsideBasePathUnclaimed() || isDataReq || isDataRequest) && !renderPageMatch && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
340
- const fallbackRewrite = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState, configSourcePathname(), conditionalRewriteCacheability);
356
+ const fallbackRewrite = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), currentBasePathState(), configSourcePathname(), conditionalRewriteCacheability);
341
357
  if (!fallbackRewrite) continue;
342
358
  if (isExternalUrl(fallbackRewrite)) return {
343
359
  type: "response",
@@ -346,6 +362,7 @@ async function runPagesRequest(request, deps) {
346
362
  resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
347
363
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
348
364
  resolvedPathnameIsRequestPathname = false;
365
+ resolvedPathnameHasBasePath = !basePath || rewrite.basePath !== false;
349
366
  configRewriteFired = true;
350
367
  const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
351
368
  if (fallbackFilesystemResult) return fallbackFilesystemResult;
@@ -365,7 +382,7 @@ async function runPagesRequest(request, deps) {
365
382
  let response = await deps.renderPage(request, resolvedUrl, initialRenderOptions, stagedHeaders);
366
383
  let matchedFallbackRewrite = false;
367
384
  if (response.status === 404 && shouldDeferErrorPageOnMiss && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
368
- const fallbackRewrite = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState, configSourcePathname(), conditionalRewriteCacheability);
385
+ const fallbackRewrite = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), currentBasePathState(), configSourcePathname(), conditionalRewriteCacheability);
369
386
  if (!fallbackRewrite) continue;
370
387
  if (isExternalUrl(fallbackRewrite)) return {
371
388
  type: "response",
@@ -374,6 +391,7 @@ async function runPagesRequest(request, deps) {
374
391
  resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
375
392
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
376
393
  resolvedPathnameIsRequestPathname = false;
394
+ resolvedPathnameHasBasePath = !basePath || rewrite.basePath !== false;
377
395
  configRewriteFired = true;
378
396
  const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
379
397
  if (fallbackFilesystemResult) return fallbackFilesystemResult;
@@ -417,7 +435,7 @@ async function runPagesRequest(request, deps) {
417
435
  }
418
436
  let devPageMatch = isOutsideBasePathUnclaimed() ? null : resolvedPathnameChanged ? deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null : pageMatch;
419
437
  if (!devPageMatch && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
420
- const fallbackRewrite = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState, configSourcePathname(), conditionalRewriteCacheability);
438
+ const fallbackRewrite = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), currentBasePathState(), configSourcePathname(), conditionalRewriteCacheability);
421
439
  if (!fallbackRewrite) continue;
422
440
  if (isExternalUrl(fallbackRewrite)) return {
423
441
  type: "response",
@@ -426,6 +444,7 @@ async function runPagesRequest(request, deps) {
426
444
  resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
427
445
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
428
446
  resolvedPathnameIsRequestPathname = false;
447
+ resolvedPathnameHasBasePath = !basePath || rewrite.basePath !== false;
429
448
  configRewriteFired = true;
430
449
  const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
431
450
  if (fallbackFilesystemResult) return fallbackFilesystemResult;
@@ -16,6 +16,12 @@ export type PagesWorkerExecutionContext = {
16
16
  export type PagesLocalResponseStage = (request: Request, env: PagesWorkerEnv | undefined, ctx: ExecutionContextLike, props: WorkerResponseStageProps) => Promise<Response>;
17
17
  export declare const pagesRequestStageBuildId: string | null;
18
18
  export declare const pagesRequestStagePrerenderSecret: string | null;
19
+ /**
20
+ * Whether the request stage serves this request as a client build file
21
+ * outside `_next/static`. Such responses resolve no route, so the Worker
22
+ * keeps their ASSETS cache policy out of route cacheability admission.
23
+ */
24
+ export declare function isPagesClientBuildFileRequest(request: Request, env: PagesWorkerEnv | undefined, ctx: PagesWorkerExecutionContext | undefined): boolean;
19
25
  /** Run the request-time stage with a host-owned response dispatcher. */
20
26
  export declare function handleRequestStage(request: Request, env: PagesWorkerEnv | undefined, ctx: PagesWorkerExecutionContext | undefined, dispatchResponseStage: DispatchWorkerResponseStage): Promise<Response>;
21
27
  /** Preserve the direct single-entry Worker path without retaining it in request-only builds. */
@@ -8,14 +8,17 @@ import { VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_EXPECTED_WORKER_VERSION_H
8
8
  import { notFoundStaticAssetResponse } from "./http-error-responses.js";
9
9
  import { attachRequestCfMetadata, cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, guardProtocolRelativeUrl } from "./request-pipeline.js";
10
10
  import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
11
+ import { isNextDataPathname } from "./pages-data-route.js";
11
12
  import { getExplicitCdnCacheAdapter } from "../shims/cdn-cache-state.js";
12
13
  import { CACHEABILITY_REQUEST_STATE } from "../shims/cacheability-classification.js";
13
14
  import { applyCdnResponseIdentityHeaders, captureCdnResponsePolicyOverrides, reconcileCdnResponseHeadersAfterOuterPolicy, validateCdnRequest } from "./cache-control.js";
14
15
  import { registerLazyDataCacheHandler } from "../shims/cache-handler.js";
15
- import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, handleConfiguredImageOptimization, isImageOptimizationPath } from "./image-optimization.js";
16
+ import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, handleConfiguredImageOptimization, isImageOptimizationPath, isImageSourceBodyStatus } from "./image-optimization.js";
16
17
  import { traceFrameworkRequest } from "./request-tracing.js";
17
18
  import { resolveResponseStageCachePolicy } from "./config-headers.js";
19
+ import { createStaticAssetRequest } from "./worker-utils.js";
18
20
  import { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath } from "./pages-request-pipeline.js";
21
+ import { resolveClientBuildFile } from "./client-build-files.js";
19
22
  import { createWorkerRevalidationContext } from "./worker-revalidation-context.js";
20
23
  import { createWorkerPrerenderDiscoveryContext, createWorkerPrerenderReadinessResponse, isWorkerPrerenderDiscoveryPath } from "./worker-prerender-discovery.js";
21
24
  import { getPagesResponseStageCacheDisposition } from "./pages-response-stage.js";
@@ -39,12 +42,13 @@ function haveSameHeaders(first, second) {
39
42
  function captureOuterResponsePolicyHeaders(stagedHeaders, transportedPolicyHeaders) {
40
43
  return captureCdnResponsePolicyOverrides(stagedHeaders, new Headers(transportedPolicyHeaders ?? []));
41
44
  }
42
- const { authorizeOnDemandRevalidate, hasMiddleware, hasRequestAwareDocument, matchApiRoute, matchPageRoute, normalizeDataRequest, publicFiles, runMiddleware, vinextConfig } = pagesEntry;
45
+ const { authorizeOnDemandRevalidate, hasMiddleware, hasRequestAwareDocument, matchApiRoute, matchPageRoute, normalizeDataRequest, publicFiles, clientBuildFiles, runMiddleware, vinextConfig } = pagesEntry;
43
46
  const pagesRequestStageBuildId = pagesEntry.buildId ?? null;
44
47
  const pagesRequestStagePrerenderSecret = pagesEntry.prerenderSecret ?? null;
45
48
  const basePath = vinextConfig?.basePath ?? "";
46
49
  const assetPathPrefix = assetPrefixPathname(vinextConfig?.assetPrefix ?? "");
47
50
  const trailingSlash = vinextConfig?.trailingSlash ?? false;
51
+ const skipTrailingSlashRedirect = vinextConfig?.skipTrailingSlashRedirect ?? false;
48
52
  const i18nConfig = vinextConfig?.i18n ?? null;
49
53
  const configRedirects = vinextConfig?.redirects ?? [];
50
54
  const configRewrites = vinextConfig?.rewrites ?? {
@@ -60,6 +64,17 @@ const imageConfig = vinextConfig?.images ? {
60
64
  contentDispositionType: vinextConfig.images.contentDispositionType,
61
65
  contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
62
66
  } : void 0;
67
+ /**
68
+ * Whether the request stage serves this request as a client build file
69
+ * outside `_next/static`. Such responses resolve no route, so the Worker
70
+ * keeps their ASSETS cache policy out of route cacheability admission.
71
+ */
72
+ function isPagesClientBuildFileRequest(request, env, ctx) {
73
+ return (request.method === "GET" || request.method === "HEAD") && resolveAssetFetcher(ctx?.assets, env, "worker") !== void 0 && resolveClientBuildFile(new URL(request.url).pathname, basePath, clientBuildFiles) !== null;
74
+ }
75
+ function resolveAssetFetcher(assets, env, defaultHostRuntime) {
76
+ return assets ?? getExplicitCdnCacheAdapter()?.assets ?? (defaultHostRuntime === "worker" ? env?.ASSETS : void 0);
77
+ }
63
78
  /** Run the request-time stage with a host-owned response dispatcher. */
64
79
  function handleRequestStage(request, env, ctx, dispatchResponseStage) {
65
80
  const originalRequest = request;
@@ -89,7 +104,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
89
104
  let sharedOuterPolicyHeaders = null;
90
105
  let ctx = createWorkerRevalidationContext(platformCtx, (internalRequest, internalCtx) => handleRequest(internalRequest, env, internalCtx, dispatchResponseStage, forceCacheBypass, defaultHostRuntime, assets), defaultHostRuntime);
91
106
  configuredCdnCacheAdapters.registerConfiguredCacheAdapters(env);
92
- assets ??= getExplicitCdnCacheAdapter()?.assets ?? (defaultHostRuntime === "worker" ? env?.ASSETS : void 0);
107
+ assets = resolveAssetFetcher(assets, env, defaultHostRuntime);
93
108
  if (configuredCdnCacheAdapters.hasConfiguredDataCache) registerLazyDataCacheHandler(async () => {
94
109
  (await import("virtual:vinext-cache-adapters")).registerConfiguredCacheAdapters(env);
95
110
  });
@@ -143,7 +158,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
143
158
  resolvedRoutePathname: pathname
144
159
  },
145
160
  kind: "pages-prerender-discovery",
146
- protocolVersion: 4,
161
+ protocolVersion: 5,
147
162
  requestHost: url.host,
148
163
  stagedHeaders: null
149
164
  }, { cache: "bypass" }, env, ctx);
@@ -154,6 +169,10 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
154
169
  } catch {
155
170
  return new Response("Bad Request", { status: 400 });
156
171
  }
172
+ if (assets && (request.method === "GET" || request.method === "HEAD")) {
173
+ const clientBuildFile = resolveClientBuildFile(pathname, basePath, clientBuildFiles);
174
+ if (clientBuildFile) return await assets.fetch(createStaticAssetRequest(clientBuildFile, request));
175
+ }
157
176
  const missingBuildAsset = isNextStaticPath(pathname, basePath, assetPathPrefix);
158
177
  const hadBasePath = !basePath || hasBasePath(pathname, basePath);
159
178
  {
@@ -189,6 +208,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
189
208
  assetPrefix: vinextConfig?.assetPrefix,
190
209
  basePath,
191
210
  trailingSlash,
211
+ skipTrailingSlashRedirect,
192
212
  i18nConfig,
193
213
  configRedirects,
194
214
  configRewrites,
@@ -228,7 +248,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
228
248
  resolvedRoutePathname: new URL(resolvedUrl, req.url).pathname
229
249
  },
230
250
  kind: "pages-page",
231
- protocolVersion: 4,
251
+ protocolVersion: 5,
232
252
  requestHost: new URL(req.url).host,
233
253
  renderOptions: isDataReq ? {
234
254
  ...options,
@@ -268,7 +288,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
268
288
  resolvedRoutePathname: new URL(apiUrl, req.url).pathname
269
289
  },
270
290
  kind: "pages-api",
271
- protocolVersion: 4,
291
+ protocolVersion: 5,
272
292
  requestHost: new URL(req.url).host,
273
293
  stagedHeaders: [...stagedHeaders]
274
294
  });
@@ -285,17 +305,56 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
285
305
  return response;
286
306
  }) : dispatched;
287
307
  },
288
- serveFilesystemRoute: async (requestPathname, _stagedHeaders, phase, resolvedUrl) => {
308
+ serveFilesystemRoute: async (requestPathname, _stagedHeaders, phase, resolvedUrl, filesystemRequest, resolvedPathnameHasBasePath) => {
289
309
  if (!assets) return false;
290
- if (isImageOptimizationPath(requestPathname)) {
310
+ if (resolvedPathnameHasBasePath && isImageOptimizationPath(requestPathname)) {
291
311
  const imageUrl = new URL(resolvedUrl, request.url);
292
- const imageRequest = new Request(imageUrl, request);
312
+ const imageRequest = cloneRequestWithUrl(request, imageUrl.href);
293
313
  const allowedWidths = [...vinextConfig?.images?.deviceSizes ?? DEFAULT_DEVICE_SIZES, ...vinextConfig?.images?.imageSizes ?? DEFAULT_IMAGE_SIZES];
294
- return handleConfiguredImageOptimization(imageRequest, (assetPath) => Promise.resolve(assets.fetch(new Request(new URL(assetPath, request.url)))), allowedWidths, imageConfig);
314
+ const { fetchWorkerImageSource } = await import("./worker-image-optimization.js");
315
+ return handleConfiguredImageOptimization(imageRequest, (sourceUrl, sourceImageRequest) => fetchWorkerImageSource(sourceUrl, sourceImageRequest, {
316
+ assets,
317
+ basePath,
318
+ assetPathPrefix,
319
+ clientBuildFiles,
320
+ fetchPublicFile: (sourceRequest) => resolvePublicImageSource(sourceRequest, assets)
321
+ }), allowedWidths, imageConfig);
295
322
  }
296
- return fetchWorkerFilesystemRoute(request, requestPathname, phase, (assetRequest) => Promise.resolve(assets.fetch(assetRequest)), publicFiles, basePath, assetPathPrefix);
323
+ return fetchWorkerFilesystemRoute(filesystemRequest, requestPathname, phase, (assetRequest) => Promise.resolve(assets.fetch(assetRequest)), publicFiles, basePath, assetPathPrefix, hadBasePath);
297
324
  }
298
325
  };
326
+ const resolvePublicImageSource = async (sourceRequest, sourceAssets) => {
327
+ const sourceUrl = new URL(sourceRequest.url);
328
+ if (guardProtocolRelativeUrl(sourceUrl.pathname, sourceUrl.search)) return null;
329
+ const sourceHadBasePath = !basePath || hasBasePath(sourceUrl.pathname, basePath);
330
+ const sourcePathname = stripBasePath(sourceUrl.pathname, basePath);
331
+ if (isNextDataPathname(sourcePathname)) return null;
332
+ let sourceAsset = null;
333
+ const sourceResult = await runPagesRequest(attachRequestCfMetadata(new Request(new URL(sourcePathname + sourceUrl.search, sourceUrl), { signal: sourceRequest.signal }), sourceRequest), {
334
+ ...deps,
335
+ hadBasePath: sourceHadBasePath,
336
+ isDataReq: false,
337
+ isDataRequest: false,
338
+ middlewareRequest: void 0,
339
+ dataNotFoundResponse: null,
340
+ recordCacheability: false,
341
+ runMiddleware: typeof runMiddleware === "function" ? wrapMiddlewareWithBasePath(runMiddleware, basePath, sourceHadBasePath) : null,
342
+ renderPage: null,
343
+ handleApi: null,
344
+ serveFilesystemRoute: async (candidate, _stagedHeaders, phase, _resolvedUrl, filesystemRequest) => {
345
+ if (isImageOptimizationPath(candidate)) return false;
346
+ const asset = await fetchWorkerFilesystemRoute(filesystemRequest, candidate, phase, (assetRequest) => Promise.resolve(sourceAssets.fetch(assetRequest)), publicFiles, basePath, assetPathPrefix, sourceHadBasePath);
347
+ if (asset === false) return false;
348
+ sourceAsset = asset;
349
+ return new Response(null);
350
+ }
351
+ });
352
+ if (sourceResult.type !== "response") return null;
353
+ await sourceResult.response.body?.cancel().catch(() => {});
354
+ if (isImageSourceBodyStatus(sourceResult.response.status)) return sourceAsset;
355
+ await sourceAsset?.body?.cancel().catch(() => {});
356
+ return null;
357
+ };
299
358
  const result = await runPagesRequest(request, deps);
300
359
  if (result.type === "response") {
301
360
  let response = result.response;
@@ -317,4 +376,4 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
317
376
  }
318
377
  }
319
378
  //#endregion
320
- export { handleRequestStage, handleRequestStageLocally, pagesRequestStageBuildId, pagesRequestStagePrerenderSecret };
379
+ export { handleRequestStage, handleRequestStageLocally, isPagesClientBuildFileRequest, pagesRequestStageBuildId, pagesRequestStagePrerenderSecret };