vinext 1.0.1 → 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 (334) hide show
  1. package/README.md +93 -10
  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/client-build-config.d.ts +10 -0
  5. package/dist/build/client-build-config.js +18 -2
  6. package/dist/build/pages-client-assets-module.d.ts +8 -0
  7. package/dist/build/pages-client-assets-module.js +21 -1
  8. package/dist/build/prerender-paths.js +8 -81
  9. package/dist/build/prerender.js +33 -8
  10. package/dist/build/run-prerender.js +1 -1
  11. package/dist/build/server-manifest.d.ts +7 -0
  12. package/dist/build/server-manifest.js +11 -1
  13. package/dist/build/ssr-manifest.d.ts +2 -0
  14. package/dist/build/ssr-manifest.js +6 -2
  15. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  16. package/dist/cache/cache-adapters-virtual.js +3 -3
  17. package/dist/check.js +7 -6
  18. package/dist/cli.js +2 -1
  19. package/dist/client/client-redirect-matcher.d.ts +14 -0
  20. package/dist/client/client-redirect-matcher.js +37 -0
  21. package/dist/client/client-redirects.d.ts +27 -0
  22. package/dist/client/client-redirects.js +28 -0
  23. package/dist/client/client-rewrites.d.ts +2 -1
  24. package/dist/client/client-rewrites.js +1 -1
  25. package/dist/client/navigation-runtime.d.ts +11 -0
  26. package/dist/client/navigation-runtime.js +1 -1
  27. package/dist/config/config-matchers.d.ts +26 -11
  28. package/dist/config/config-matchers.js +81 -82
  29. package/dist/config/destination-params.d.ts +47 -0
  30. package/dist/config/destination-params.js +116 -0
  31. package/dist/config/next-config.d.ts +14 -0
  32. package/dist/config/next-config.js +21 -3
  33. package/dist/entries/app-rsc-entry.d.ts +6 -0
  34. package/dist/entries/app-rsc-entry.js +84 -49
  35. package/dist/entries/pages-client-entry.js +3 -1
  36. package/dist/entries/pages-server-entry.js +13 -37
  37. package/dist/index.d.ts +7 -2
  38. package/dist/index.js +193 -73
  39. package/dist/init-cloudflare.d.ts +9 -0
  40. package/dist/init-cloudflare.js +107 -31
  41. package/dist/init-platform.d.ts +4 -0
  42. package/dist/init-platform.js +27 -2
  43. package/dist/init.d.ts +1 -0
  44. package/dist/init.js +67 -3
  45. package/dist/plugins/ast-scope.d.ts +7 -0
  46. package/dist/plugins/ast-scope.js +59 -3
  47. package/dist/plugins/cache-flight-codec.d.ts +19 -0
  48. package/dist/plugins/cache-flight-codec.js +68 -0
  49. package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
  50. package/dist/plugins/commonjs-esm-facade.js +80 -0
  51. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  52. package/dist/plugins/dynamic-preload-metadata.js +9 -8
  53. package/dist/plugins/fonts.d.ts +8 -11
  54. package/dist/plugins/fonts.js +71 -34
  55. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  56. package/dist/plugins/ignore-dynamic-requests.js +3 -4
  57. package/dist/plugins/middleware-server-only.d.ts +5 -4
  58. package/dist/plugins/middleware-server-only.js +8 -5
  59. package/dist/plugins/pages-node-externals.js +1 -1
  60. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  61. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  62. package/dist/plugins/strip-server-exports.js +262 -98
  63. package/dist/plugins/transform-result.d.ts +9 -5
  64. package/dist/plugins/transitive-externals.js +1 -1
  65. package/dist/plugins/typeof-window.d.ts +2 -2
  66. package/dist/plugins/use-cache-callable.js +8 -2
  67. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  68. package/dist/plugins/worker-server-only-imports.js +31 -0
  69. package/dist/plugins/worker-use-server.d.ts +25 -0
  70. package/dist/plugins/worker-use-server.js +129 -0
  71. package/dist/routing/file-matcher.d.ts +1 -0
  72. package/dist/routing/file-matcher.js +1 -1
  73. package/dist/server/api-handler.d.ts +1 -0
  74. package/dist/server/api-handler.js +19 -8
  75. package/dist/server/app-browser-action-result.d.ts +1 -1
  76. package/dist/server/app-browser-action-result.js +9 -2
  77. package/dist/server/app-browser-entry.js +11 -7
  78. package/dist/server/app-browser-history-controller.d.ts +2 -0
  79. package/dist/server/app-browser-history-controller.js +8 -0
  80. package/dist/server/app-browser-interception-context.js +26 -2
  81. package/dist/server/app-browser-navigation-controller.d.ts +7 -1
  82. package/dist/server/app-browser-navigation-controller.js +94 -11
  83. package/dist/server/app-browser-server-action-client.js +1 -1
  84. package/dist/server/app-fallback-renderer.d.ts +2 -0
  85. package/dist/server/app-fallback-renderer.js +1 -0
  86. package/dist/server/app-history-state.d.ts +3 -0
  87. package/dist/server/app-history-state.js +7 -0
  88. package/dist/server/app-middleware.d.ts +11 -0
  89. package/dist/server/app-middleware.js +19 -3
  90. package/dist/server/app-optimistic-routing.js +1 -1
  91. package/dist/server/app-page-boundary-render.d.ts +5 -0
  92. package/dist/server/app-page-boundary-render.js +6 -4
  93. package/dist/server/app-page-cache-finalizer.d.ts +11 -0
  94. package/dist/server/app-page-cache-finalizer.js +41 -7
  95. package/dist/server/app-page-cache-render.d.ts +18 -0
  96. package/dist/server/app-page-cache-render.js +63 -43
  97. package/dist/server/app-page-cache.d.ts +6 -1
  98. package/dist/server/app-page-cache.js +28 -8
  99. package/dist/server/app-page-dispatch.d.ts +12 -2
  100. package/dist/server/app-page-dispatch.js +51 -33
  101. package/dist/server/app-page-element-builder.d.ts +7 -0
  102. package/dist/server/app-page-element-builder.js +27 -42
  103. package/dist/server/app-page-execution.d.ts +15 -12
  104. package/dist/server/app-page-execution.js +30 -40
  105. package/dist/server/app-page-head.d.ts +5 -0
  106. package/dist/server/app-page-head.js +9 -3
  107. package/dist/server/app-page-ppr-runtime.js +1 -1
  108. package/dist/server/app-page-probe.d.ts +0 -109
  109. package/dist/server/app-page-probe.js +3 -136
  110. package/dist/server/app-page-render-identity.d.ts +0 -1
  111. package/dist/server/app-page-render-identity.js +1 -1
  112. package/dist/server/app-page-render-observation.js +3 -3
  113. package/dist/server/app-page-render.d.ts +13 -5
  114. package/dist/server/app-page-render.js +219 -119
  115. package/dist/server/app-page-request.d.ts +0 -1
  116. package/dist/server/app-page-request.js +54 -17
  117. package/dist/server/app-page-response.d.ts +4 -3
  118. package/dist/server/app-page-response.js +4 -3
  119. package/dist/server/app-page-route-wiring.d.ts +17 -5
  120. package/dist/server/app-page-route-wiring.js +57 -35
  121. package/dist/server/app-page-rsc-render-status.d.ts +48 -0
  122. package/dist/server/app-page-rsc-render-status.js +54 -0
  123. package/dist/server/app-page-stream.d.ts +15 -8
  124. package/dist/server/app-page-stream.js +6 -0
  125. package/dist/server/app-pages-bridge.d.ts +1 -1
  126. package/dist/server/app-pages-bridge.js +2 -3
  127. package/dist/server/app-prerender-endpoints.js +9 -6
  128. package/dist/server/app-request-stage-dispatch.js +7 -3
  129. package/dist/server/app-request-stage-independent-entry.js +29 -9
  130. package/dist/server/app-response-header-provenance.d.ts +2 -0
  131. package/dist/server/app-response-header-provenance.js +10 -4
  132. package/dist/server/app-response-stage-entry.js +10 -2
  133. package/dist/server/app-route-handler-cache.d.ts +1 -0
  134. package/dist/server/app-route-handler-cache.js +1 -0
  135. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  136. package/dist/server/app-route-handler-dispatch.js +6 -3
  137. package/dist/server/app-route-handler-execution.d.ts +1 -0
  138. package/dist/server/app-route-handler-execution.js +1 -0
  139. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  140. package/dist/server/app-route-handler-runtime.js +45 -24
  141. package/dist/server/app-router-entry.js +27 -8
  142. package/dist/server/app-rsc-cache-busting.js +1 -1
  143. package/dist/server/app-rsc-errors.js +20 -16
  144. package/dist/server/app-rsc-handler.d.ts +11 -0
  145. package/dist/server/app-rsc-handler.js +291 -125
  146. package/dist/server/app-rsc-request-normalization.d.ts +3 -1
  147. package/dist/server/app-rsc-request-normalization.js +4 -2
  148. package/dist/server/app-rsc-response-stage.d.ts +1 -1
  149. package/dist/server/app-rsc-response-stage.js +10 -6
  150. package/dist/server/app-rsc-route-matching.d.ts +9 -1
  151. package/dist/server/app-rsc-route-matching.js +36 -19
  152. package/dist/server/app-segment-config.d.ts +2 -1
  153. package/dist/server/app-server-action-execution.d.ts +2 -1
  154. package/dist/server/app-server-action-execution.js +62 -23
  155. package/dist/server/app-ssr-entry.d.ts +3 -2
  156. package/dist/server/app-ssr-entry.js +8 -5
  157. package/dist/server/app-ssr-stream.d.ts +6 -1
  158. package/dist/server/app-ssr-stream.js +34 -15
  159. package/dist/server/app-worker-stages.d.ts +7 -2
  160. package/dist/server/app-worker-stages.js +4 -4
  161. package/dist/server/cacheability-request.js +11 -7
  162. package/dist/server/client-build-files.d.ts +16 -0
  163. package/dist/server/client-build-files.js +36 -0
  164. package/dist/server/client-reuse-manifest.js +1 -1
  165. package/dist/server/config-headers.js +2 -0
  166. package/dist/server/dev-server.js +50 -29
  167. package/dist/server/dev-stack-sourcemap.js +18 -23
  168. package/dist/server/document-initial-head.d.ts +0 -11
  169. package/dist/server/document-initial-head.js +1 -1
  170. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  171. package/dist/server/forwarded-middleware-context.js +22 -0
  172. package/dist/server/headers.d.ts +18 -1
  173. package/dist/server/headers.js +19 -2
  174. package/dist/server/http-conditional.d.ts +11 -0
  175. package/dist/server/http-conditional.js +10 -1
  176. package/dist/server/http-error-responses.d.ts +1 -1
  177. package/dist/server/image-optimization.d.ts +54 -0
  178. package/dist/server/image-optimization.js +118 -9
  179. package/dist/server/isr-cache.d.ts +10 -0
  180. package/dist/server/isr-cache.js +43 -8
  181. package/dist/server/metadata-route-response.js +9 -8
  182. package/dist/server/middleware-matcher-pattern.js +8 -4
  183. package/dist/server/middleware-matcher.d.ts +2 -0
  184. package/dist/server/middleware-matcher.js +23 -2
  185. package/dist/server/middleware-response-headers.js +2 -0
  186. package/dist/server/middleware-runtime.d.ts +13 -0
  187. package/dist/server/middleware-runtime.js +33 -20
  188. package/dist/server/middleware.d.ts +1 -1
  189. package/dist/server/middleware.js +3 -1
  190. package/dist/server/multi-stage.d.ts +19 -0
  191. package/dist/server/navigation-planner.js +1 -1
  192. package/dist/server/node-response-signal.d.ts +16 -0
  193. package/dist/server/node-response-signal.js +26 -0
  194. package/dist/server/normalize-path.d.ts +0 -13
  195. package/dist/server/normalize-path.js +1 -34
  196. package/dist/server/open-redirect.d.ts +47 -0
  197. package/dist/server/open-redirect.js +74 -1
  198. package/dist/server/pages-api-route.d.ts +3 -2
  199. package/dist/server/pages-api-route.js +4 -3
  200. package/dist/server/pages-cache-tag.d.ts +12 -0
  201. package/dist/server/pages-cache-tag.js +19 -0
  202. package/dist/server/pages-client-assets.d.ts +2 -0
  203. package/dist/server/pages-data-route.d.ts +10 -0
  204. package/dist/server/pages-data-route.js +19 -2
  205. package/dist/server/pages-document-asset-props.d.ts +5 -1
  206. package/dist/server/pages-document-asset-props.js +13 -5
  207. package/dist/server/pages-document-splice.d.ts +19 -0
  208. package/dist/server/pages-document-splice.js +55 -0
  209. package/dist/server/pages-i18n.d.ts +12 -1
  210. package/dist/server/pages-i18n.js +18 -4
  211. package/dist/server/pages-node-compat.d.ts +16 -5
  212. package/dist/server/pages-node-compat.js +20 -12
  213. package/dist/server/pages-page-data.d.ts +32 -24
  214. package/dist/server/pages-page-data.js +50 -120
  215. package/dist/server/pages-page-handler.d.ts +0 -6
  216. package/dist/server/pages-page-handler.js +209 -130
  217. package/dist/server/pages-page-response.d.ts +12 -6
  218. package/dist/server/pages-page-response.js +41 -34
  219. package/dist/server/pages-request-pipeline.d.ts +10 -2
  220. package/dist/server/pages-request-pipeline.js +37 -18
  221. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  222. package/dist/server/pages-request-stage-entry.js +79 -16
  223. package/dist/server/pages-response-stage-entry.js +4 -0
  224. package/dist/server/pages-revalidate.js +1 -1
  225. package/dist/server/pages-router-entry.js +4 -3
  226. package/dist/server/prerender-manifest.d.ts +3 -3
  227. package/dist/server/prerender-manifest.js +4 -3
  228. package/dist/server/prerender-work-unit-setup.js +4 -0
  229. package/dist/server/prod-server.d.ts +20 -7
  230. package/dist/server/prod-server.js +343 -129
  231. package/dist/server/proper-error.d.ts +4 -0
  232. package/dist/server/proper-error.js +31 -0
  233. package/dist/server/request-pipeline.d.ts +24 -9
  234. package/dist/server/request-pipeline.js +24 -9
  235. package/dist/server/response-compression.d.ts +43 -0
  236. package/dist/server/response-compression.js +111 -0
  237. package/dist/server/revalidation-request.d.ts +1 -0
  238. package/dist/server/revalidation-request.js +1 -1
  239. package/dist/server/rsc-stream-hints.js +31 -2
  240. package/dist/server/seed-cache.d.ts +1 -0
  241. package/dist/server/seed-cache.js +15 -11
  242. package/dist/server/server-reference-loader.d.ts +4 -0
  243. package/dist/server/server-reference-loader.js +29 -0
  244. package/dist/server/socket-error-backstop.d.ts +23 -1
  245. package/dist/server/socket-error-backstop.js +69 -17
  246. package/dist/server/static-file-signal.d.ts +15 -4
  247. package/dist/server/static-file-signal.js +46 -12
  248. package/dist/server/worker-image-optimization.d.ts +34 -0
  249. package/dist/server/worker-image-optimization.js +86 -0
  250. package/dist/server/worker-stages.d.ts +1 -1
  251. package/dist/server/worker-stages.js +2 -2
  252. package/dist/server/worker-utils.d.ts +8 -3
  253. package/dist/server/worker-utils.js +12 -7
  254. package/dist/shims/app-router-scroll.js +4 -1
  255. package/dist/shims/app-router-tree-context.d.ts +5 -0
  256. package/dist/shims/app-router-tree-context.js +27 -0
  257. package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
  258. package/dist/shims/cache-adapter-instantiate.js +118 -0
  259. package/dist/shims/cache-callable-runtime.d.ts +6 -42
  260. package/dist/shims/cache-callable-runtime.js +65 -174
  261. package/dist/shims/cache-flight-arguments.d.ts +22 -0
  262. package/dist/shims/cache-flight-arguments.js +56 -0
  263. package/dist/shims/cache-handler.js +1 -9
  264. package/dist/shims/cache-request-state.d.ts +15 -0
  265. package/dist/shims/cache-request-state.js +31 -8
  266. package/dist/shims/cache-runtime.d.ts +12 -20
  267. package/dist/shims/cache-runtime.js +166 -225
  268. package/dist/shims/cache.d.ts +4 -4
  269. package/dist/shims/cache.js +44 -16
  270. package/dist/shims/cacheability-classification.d.ts +7 -0
  271. package/dist/shims/cdn-cache.d.ts +8 -0
  272. package/dist/shims/cdn-cache.js +17 -1
  273. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  274. package/dist/shims/disable-smooth-scroll.js +36 -0
  275. package/dist/shims/document-placeholders.d.ts +19 -0
  276. package/dist/shims/document-placeholders.js +45 -0
  277. package/dist/shims/document.js +21 -5
  278. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  279. package/dist/shims/dynamic-preload-chunks.js +13 -9
  280. package/dist/shims/dynamic.d.ts +1 -1
  281. package/dist/shims/dynamic.js +60 -8
  282. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  283. package/dist/shims/error-boundary-navigation.js +19 -1
  284. package/dist/shims/error-boundary.d.ts +10 -10
  285. package/dist/shims/error-boundary.js +26 -7
  286. package/dist/shims/fetch-cache-state.d.ts +66 -0
  287. package/dist/shims/fetch-cache-state.js +92 -0
  288. package/dist/shims/fetch-cache.d.ts +3 -61
  289. package/dist/shims/fetch-cache.js +161 -108
  290. package/dist/shims/form.js +1 -1
  291. package/dist/shims/head.d.ts +10 -6
  292. package/dist/shims/head.js +15 -13
  293. package/dist/shims/headers.d.ts +13 -0
  294. package/dist/shims/headers.js +47 -4
  295. package/dist/shims/image.js +2 -1
  296. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
  297. package/dist/shims/internal/app-page-props-cache-key.js +24 -15
  298. package/dist/shims/internal/headers-state.d.ts +0 -1
  299. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  300. package/dist/shims/internal/work-store-scope.js +24 -0
  301. package/dist/shims/metadata.js +3 -3
  302. package/dist/shims/navigation-errors.d.ts +6 -0
  303. package/dist/shims/navigation-errors.js +11 -1
  304. package/dist/shims/navigation.d.ts +7 -0
  305. package/dist/shims/navigation.js +45 -9
  306. package/dist/shims/request-context.d.ts +11 -0
  307. package/dist/shims/request-state-types.d.ts +2 -1
  308. package/dist/shims/router.js +12 -20
  309. package/dist/shims/server.d.ts +16 -0
  310. package/dist/shims/server.js +24 -4
  311. package/dist/shims/thenable-params.d.ts +4 -0
  312. package/dist/shims/thenable-params.js +25 -3
  313. package/dist/shims/unified-request-context.d.ts +1 -1
  314. package/dist/shims/unified-request-context.js +1 -0
  315. package/dist/shims/url-utils.d.ts +2 -0
  316. package/dist/shims/url-utils.js +3 -1
  317. package/dist/utils/client-runtime-metadata.js +1 -1
  318. package/dist/utils/hash.d.ts +7 -0
  319. package/dist/utils/hash.js +11 -1
  320. package/dist/utils/mdx-scan.d.ts +5 -0
  321. package/dist/utils/mdx-scan.js +8 -1
  322. package/dist/utils/package-name.d.ts +6 -0
  323. package/dist/utils/package-name.js +12 -1
  324. package/dist/utils/plugin-rsc-version.d.ts +4 -0
  325. package/dist/utils/plugin-rsc-version.js +15 -0
  326. package/dist/utils/prerender-output-paths.js +15 -4
  327. package/dist/utils/public-file-path.d.ts +10 -2
  328. package/dist/utils/public-file-path.js +13 -9
  329. package/dist/utils/public-routes.js +2 -2
  330. package/dist/utils/react-version.d.ts +3 -0
  331. package/dist/utils/react-version.js +1 -1
  332. package/dist/utils/regex-safety.d.ts +10 -0
  333. package/dist/utils/regex-safety.js +70 -18
  334. package/package.json +9 -5
@@ -10,6 +10,8 @@ export type MiddlewareResult = {
10
10
  redirectUrl?: string;
11
11
  redirectStatus?: number;
12
12
  rewriteUrl?: string;
13
+ /** False when a same-origin rewrite target was outside the basePath. */
14
+ rewriteHadBasePath?: boolean;
13
15
  rewriteStatus?: number;
14
16
  status?: number;
15
17
  responseHeaders?: Headers;
@@ -41,6 +43,11 @@ type ExecuteMiddlewareOptions = {
41
43
  * must derive and forward this from trusted URL normalization.
42
44
  */
43
45
  isDataRequest?: boolean;
46
+ /**
47
+ * The data request names a buildId other than the current build. Adapters
48
+ * pass it raw to middleware only under `skipProxyUrlNormalize`.
49
+ */
50
+ isStaleDataRequest?: boolean;
44
51
  isProxy: boolean;
45
52
  module: MiddlewareModule;
46
53
  normalizedPathname?: string;
@@ -63,6 +70,12 @@ type ExecuteMiddlewareOptions = {
63
70
  * `new URL('/x', req.url)`.
64
71
  */
65
72
  trailingSlash?: boolean;
73
+ /**
74
+ * next.config `skipTrailingSlashRedirect`. Middleware then sees requests in
75
+ * either slash form, so NextURL keeps each request's own trailing slash and
76
+ * redirect Locations keep theirs, as Next.js's `getNextPathnameInfo` does.
77
+ */
78
+ skipTrailingSlashRedirect?: boolean;
66
79
  };
67
80
  type RunGeneratedMiddlewareOptions = ExecuteMiddlewareOptions & {
68
81
  ctx?: ExecutionContextLike;
@@ -8,12 +8,14 @@ import { getRequestExecutionContext, runWithExecutionContext } from "../shims/re
8
8
  import "./server-globals.js";
9
9
  import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
10
10
  import { NextFetchEvent, NextRequest } from "../shims/server.js";
11
+ import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
11
12
  import { normalizePath } from "./normalize-path.js";
12
13
  import { matchesMiddleware, matchesMiddlewarePathname } from "./middleware-matcher.js";
13
14
  import { badRequestResponse, internalServerErrorResponse } from "./http-error-responses.js";
14
15
  import { isOpenRedirectShaped } from "./open-redirect.js";
15
16
  import { processMiddlewareHeaders } from "./request-pipeline.js";
16
17
  import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
18
+ import { nextDataMiddlewareMatchPathname } from "./pages-data-route.js";
17
19
  import { reportRequestError } from "./instrumentation.js";
18
20
  import { isValidNavigationSignalError } from "../utils/navigation-signal.js";
19
21
  import { frameworkTracer } from "./tracer.js";
@@ -133,7 +135,7 @@ function resolveMiddlewarePathname(request) {
133
135
  return badRequestResponse();
134
136
  }
135
137
  }
136
- function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBasePath, requestBodyAlreadyIsolated = false) {
138
+ function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBasePath, requestBodyAlreadyIsolated = false, skipTrailingSlashRedirect = false) {
137
139
  const url = new URL(request.url);
138
140
  let mwRequest = !requestBodyAlreadyIsolated && request.body && !request.bodyUsed ? request.clone() : request;
139
141
  const requestPathname = url.pathname;
@@ -143,10 +145,11 @@ function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBase
143
145
  mwUrl.pathname = mwPathname;
144
146
  mwRequest = new Request(mwUrl, mwRequest);
145
147
  }
146
- const nextConfig = basePath || i18nConfig || trailingSlash ? {
148
+ const nextConfig = basePath || i18nConfig || trailingSlash || skipTrailingSlashRedirect ? {
147
149
  basePath: basePath ?? "",
148
150
  i18n: i18nConfig ?? void 0,
149
- trailingSlash: trailingSlash ?? void 0
151
+ trailingSlash: trailingSlash ?? void 0,
152
+ skipTrailingSlashRedirect
150
153
  } : void 0;
151
154
  const nextRequest = mwRequest instanceof NextRequest && (!mwRequest.body || mwRequest.bodyUsed) ? mwRequest : new NextRequest(mwRequest, nextConfig ? { nextConfig } : void 0);
152
155
  if (mwRequest !== request && mwRequest.body && !mwRequest.bodyUsed && !mwRequest.body.locked) mwRequest.body.cancel().catch(() => {});
@@ -164,7 +167,10 @@ function releaseMiddlewareRequestBody(request, waitUntilPromises, retainedByResp
164
167
  }
165
168
  Promise.allSettled(waitUntilPromises).then(cancel);
166
169
  }
167
- async function executeMiddleware(options) {
170
+ function executeMiddleware(options) {
171
+ return runInWorkStoreScope(() => executeMiddlewareModule(options));
172
+ }
173
+ async function executeMiddlewareModule(options) {
168
174
  const middlewareFn = resolveMiddlewareModuleHandler(options.module, {
169
175
  filePath: options.filePath,
170
176
  isProxy: options.isProxy
@@ -187,14 +193,17 @@ async function executeMiddleware(options) {
187
193
  if (options.hadBasePath === true) return candidate;
188
194
  return matcher === void 0 ? candidate : null;
189
195
  };
190
- const encodedMatchPathname = prepareMatcherPathname(encodedRequestPathname);
196
+ const isStaleDataRequest = options.isDataRequest === true && options.isStaleDataRequest === true;
197
+ const toPageMatcherPathname = (candidate) => candidate !== null && options.isDataRequest && !isStaleDataRequest ? nextDataMiddlewareMatchPathname(candidate) ?? candidate : candidate;
198
+ const encodedMatchPathname = toPageMatcherPathname(prepareMatcherPathname(encodedRequestPathname));
191
199
  let decodedMatchPathname = encodedMatchPathname;
192
200
  try {
193
201
  if (encodedMatchPathname !== null) decodedMatchPathname = decodeURIComponent(encodedMatchPathname);
194
- else if (!options.i18nConfig) decodedMatchPathname = prepareMatcherPathname(decodeURIComponent(encodedRequestPathname));
202
+ else if (!options.i18nConfig) decodedMatchPathname = toPageMatcherPathname(prepareMatcherPathname(decodeURIComponent(encodedRequestPathname)));
195
203
  } catch {}
196
204
  let localeContext;
197
- if (options.i18nConfig && encodedMatchPathname !== null) {
205
+ if (isStaleDataRequest) localeContext = { kind: "next-data" };
206
+ else if (options.i18nConfig && encodedMatchPathname !== null) {
198
207
  const hostname = normalizeHost(options.request.headers.get("host"), requestUrl.hostname);
199
208
  const firstSegment = encodedMatchPathname.split("/", 3)[1];
200
209
  if (firstSegment !== void 0 && options.i18nConfig.locales.some((locale) => locale.toLowerCase() === firstSegment.toLowerCase())) localeContext = { kind: "literal" };
@@ -210,7 +219,7 @@ async function executeMiddleware(options) {
210
219
  const decodedMatches = !encodedMatches && decodedMatchPathname !== null && decodedMatchPathname !== encodedMatchPathname && matchesMiddleware(decodedMatchPathname, matcher, options.request, options.i18nConfig, localeContext);
211
220
  if (!encodedMatches && !decodedMatches) return { continue: true };
212
221
  options.onMatch?.();
213
- const nextRequest = createNextRequest(options.request, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath, options.requestBodyAlreadyIsolated);
222
+ const nextRequest = createNextRequest(options.request, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath, options.requestBodyAlreadyIsolated, options.skipTrailingSlashRedirect);
214
223
  if (options.isDataRequest) Object.defineProperty(nextRequest, "__isData", {
215
224
  enumerable: false,
216
225
  value: true
@@ -248,21 +257,12 @@ async function executeMiddleware(options) {
248
257
  waitUntilPromises
249
258
  };
250
259
  }
251
- if (response.headers.get("x-middleware-next") === "1") {
252
- releaseMiddlewareRequestBody(nextRequest, waitUntilPromises);
253
- return {
254
- continue: true,
255
- responseHeaders: collectMiddlewareHeaders(response),
256
- status: response.status !== 200 ? response.status : void 0,
257
- waitUntilPromises
258
- };
259
- }
260
260
  if (response.status >= 300 && response.status < 400) {
261
261
  const location = response.headers.get("Location") ?? response.headers.get("location");
262
262
  if (location) {
263
263
  const relativeLocation = relativizeLocation(location, options.request.url);
264
264
  let normalizedLocation = relativeLocation;
265
- if (options.trailingSlash !== void 0) try {
265
+ if (options.trailingSlash !== void 0 && !options.skipTrailingSlashRedirect) try {
266
266
  const loc = new URL(relativeLocation, options.request.url);
267
267
  if (loc.origin === new URL(options.request.url).origin) {
268
268
  const p = loc.pathname;
@@ -309,11 +309,14 @@ async function executeMiddleware(options) {
309
309
  const rewriteUrl = response.headers.get(MIDDLEWARE_REWRITE_HEADER);
310
310
  if (rewriteUrl) {
311
311
  let rewritePath;
312
+ let rewriteHadBasePath;
312
313
  try {
313
314
  const rewriteParsed = new URL(rewriteUrl, options.request.url);
314
315
  const requestOrigin = new URL(options.request.url).origin;
315
- if (rewriteParsed.origin === requestOrigin) rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
316
- else rewritePath = rewriteParsed.href;
316
+ if (rewriteParsed.origin === requestOrigin) {
317
+ rewriteHadBasePath = !options.basePath || hasBasePath(rewriteParsed.pathname, options.basePath);
318
+ rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
319
+ } else rewritePath = rewriteParsed.href;
317
320
  } catch {
318
321
  rewritePath = rewriteUrl;
319
322
  }
@@ -321,12 +324,22 @@ async function executeMiddleware(options) {
321
324
  return {
322
325
  continue: true,
323
326
  rewriteUrl: rewritePath,
327
+ rewriteHadBasePath,
324
328
  rewriteStatus: response.status !== 200 ? response.status : void 0,
325
329
  responseHeaders: collectMiddlewareHeaders(response),
326
330
  status: response.status !== 200 ? response.status : void 0,
327
331
  waitUntilPromises
328
332
  };
329
333
  }
334
+ if (response.headers.get("x-middleware-next") === "1") {
335
+ releaseMiddlewareRequestBody(nextRequest, waitUntilPromises);
336
+ return {
337
+ continue: true,
338
+ responseHeaders: collectMiddlewareHeaders(response),
339
+ status: response.status !== 200 ? response.status : void 0,
340
+ waitUntilPromises
341
+ };
342
+ }
330
343
  releaseMiddlewareRequestBody(nextRequest, waitUntilPromises, response.body === nextRequest.body);
331
344
  return {
332
345
  continue: false,
@@ -44,6 +44,6 @@ export declare function findMiddlewareFile(root: string, fileMatcher: ValidFileM
44
44
  * @param request - The incoming Request object
45
45
  * @returns Middleware result describing what action to take
46
46
  */
47
- export declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean, normalizedPathname?: string): Promise<MiddlewareResult>;
47
+ export declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean, normalizedPathname?: string, isStaleDataRequest?: boolean, skipTrailingSlashRedirect?: boolean): Promise<MiddlewareResult>;
48
48
  //#endregion
49
49
  export { matchPattern, matchesMiddleware };
@@ -77,7 +77,7 @@ function isMiddlewareModule(value) {
77
77
  * @param request - The incoming Request object
78
78
  * @returns Middleware result describing what action to take
79
79
  */
80
- async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest, normalizedPathname) {
80
+ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest, normalizedPathname, isStaleDataRequest, skipTrailingSlashRedirect) {
81
81
  const mod = await runner.import(middlewarePath);
82
82
  if (!isMiddlewareModule(mod)) throw new Error(`Middleware module "${middlewarePath}" did not evaluate to an object.`);
83
83
  return runGeneratedMiddleware({
@@ -87,10 +87,12 @@ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePa
87
87
  i18nConfig,
88
88
  includeErrorDetails: process.env.NODE_ENV !== "production",
89
89
  isDataRequest,
90
+ isStaleDataRequest,
90
91
  isProxy: isProxyFile(middlewarePath),
91
92
  module: mod,
92
93
  normalizedPathname,
93
94
  request,
95
+ skipTrailingSlashRedirect,
94
96
  trailingSlash
95
97
  });
96
98
  }
@@ -21,6 +21,25 @@ export type VinextResponseStageDispatchOptions = {
21
21
  * opt into completed-response admission and honor core's `no-store` policy.
22
22
  */
23
23
  cache: "shared" | "bypass";
24
+ /**
25
+ * Render an App page document whole before its first byte, as Next.js
26
+ * renders a static page, so its streamed metadata is in `<head>`. An adapter
27
+ * sets this for a render no client is waiting on, such as a regeneration or
28
+ * a deployment warm-up. It is not a cache selector: a transport must not
29
+ * partition its lookup or stored entry by it.
30
+ */
31
+ renderWholeDocument?: boolean;
32
+ /**
33
+ * Regenerate an entry the adapter already stores for this build. Set only
34
+ * from a platform-invoked regeneration callback or a platform cache's
35
+ * conditional revalidation of a stored entry, never from anything a client
36
+ * can send: core then treats the path as admitted and skips
37
+ * request-time admission work such as a Pages `getStaticPaths()` call, as
38
+ * Next.js regenerates a generated path without consulting it. It is not a
39
+ * cache selector: a transport must not partition its lookup or stored entry
40
+ * by it.
41
+ */
42
+ regeneratesStoredEntry?: boolean;
24
43
  /**
25
44
  * Query-free identity of a shared App page GET/HEAD dispatch, supplied only
26
45
  * to adapters that declare `responseStageCacheIdentity: "query-free"` and
@@ -1,8 +1,8 @@
1
1
  import { splitPathnameForRouteMatch } from "../routing/utils.js";
2
2
  import { stripBasePath } from "../utils/base-path.js";
3
+ import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
3
4
  import { AppElementsWire, compareAppElementsSlotIds } from "./app-elements-wire.js";
4
5
  import "./app-elements.js";
5
- import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
6
6
  import { resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
7
7
  import { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop } from "./app-browser-rsc-redirect.js";
8
8
  import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
@@ -0,0 +1,16 @@
1
+ import { Writable } from "node:stream";
2
+ //#region src/server/node-response-signal.d.ts
3
+ export declare class ResponseAborted extends Error {
4
+ readonly name = "ResponseAborted";
5
+ }
6
+ /**
7
+ * Creates an AbortSignal tied to the closing of a ServerResponse.
8
+ *
9
+ * The incoming request cannot be used: once its body has been fully read the
10
+ * readable side is done, so a later client disconnect fires nothing on it
11
+ * (and its `close` event also fires on normal requests). If `finish` fires
12
+ * first, `res.end()` completed and the following `close` is our own teardown;
13
+ * if `close` fires first, the client disconnected before we finished.
14
+ */
15
+ export declare function signalFromNodeResponse(response: Writable): AbortSignal;
16
+ //#endregion
@@ -0,0 +1,26 @@
1
+ //#region src/server/node-response-signal.ts
2
+ const ResponseAbortedName = "ResponseAborted";
3
+ var ResponseAborted = class extends Error {
4
+ name = ResponseAbortedName;
5
+ };
6
+ /**
7
+ * Creates an AbortSignal tied to the closing of a ServerResponse.
8
+ *
9
+ * The incoming request cannot be used: once its body has been fully read the
10
+ * readable side is done, so a later client disconnect fires nothing on it
11
+ * (and its `close` event also fires on normal requests). If `finish` fires
12
+ * first, `res.end()` completed and the following `close` is our own teardown;
13
+ * if `close` fires first, the client disconnected before we finished.
14
+ */
15
+ function signalFromNodeResponse(response) {
16
+ const { errored, destroyed } = response;
17
+ if (errored || destroyed) return AbortSignal.abort(errored ?? new ResponseAborted());
18
+ const controller = new AbortController();
19
+ response.once("close", () => {
20
+ if (response.writableFinished) return;
21
+ controller.abort(new ResponseAborted());
22
+ });
23
+ return controller.signal;
24
+ }
25
+ //#endregion
26
+ export { ResponseAborted, signalFromNodeResponse };
@@ -1,17 +1,4 @@
1
1
  //#region src/server/normalize-path.d.ts
2
- export declare function escapePathDelimiters(segment: string, escapeEncoded?: boolean): string;
3
- /**
4
- * Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
5
- * Non-ASCII characters (e.g. %C3%A9 -> e) are decoded, but structural characters
6
- * like %2F (/) %23 (#) %3F (?) %5C (\) are re-encoded after decoding.
7
- *
8
- * This prevents encoded slashes from changing the path structure (e.g.
9
- * /admin%2Fpanel stays as a single segment, not /admin/panel).
10
- *
11
- * Ported from Next.js: packages/next/src/server/lib/router-utils/decode-path-params.ts
12
- * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/decode-path-params.ts
13
- */
14
- export declare function decodePathParams(pathname: string): string;
15
2
  export declare function isInterceptionMatchedUrlPath(value: string): boolean;
16
3
  /**
17
4
  * Path normalization utility for request handling.
@@ -1,37 +1,4 @@
1
1
  //#region src/server/normalize-path.ts
2
- /**
3
- * Re-encode path delimiter characters that were decoded by decodeURIComponent.
4
- * After decoding a URL segment, characters like / # ? \ need to be re-encoded
5
- * so they don't change the path structure.
6
- *
7
- * Ported from Next.js: packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
8
- * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
9
- */
10
- const PATH_DELIMITERS_RE = /([/#?])/gi;
11
- const PATH_DELIMITERS_ENCODED_RE = /([/#?]|%(2f|23|3f|5c))/gi;
12
- function escapePathDelimiters(segment, escapeEncoded) {
13
- return segment.replace(escapeEncoded ? PATH_DELIMITERS_ENCODED_RE : PATH_DELIMITERS_RE, (char) => encodeURIComponent(char));
14
- }
15
- /**
16
- * Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
17
- * Non-ASCII characters (e.g. %C3%A9 -> e) are decoded, but structural characters
18
- * like %2F (/) %23 (#) %3F (?) %5C (\) are re-encoded after decoding.
19
- *
20
- * This prevents encoded slashes from changing the path structure (e.g.
21
- * /admin%2Fpanel stays as a single segment, not /admin/panel).
22
- *
23
- * Ported from Next.js: packages/next/src/server/lib/router-utils/decode-path-params.ts
24
- * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/decode-path-params.ts
25
- */
26
- function decodePathParams(pathname) {
27
- return pathname.split("/").map((seg) => {
28
- try {
29
- return escapePathDelimiters(decodeURIComponent(seg), true);
30
- } catch {
31
- return seg;
32
- }
33
- }).join("/");
34
- }
35
2
  function isInterceptionMatchedUrlPath(value) {
36
3
  return value.startsWith("/") && !value.startsWith("//") && !value.includes("?") && !value.includes("#") && !value.includes("\\") && !value.includes("\0");
37
4
  }
@@ -67,4 +34,4 @@ function normalizePath(pathname) {
67
34
  return "/" + resolved.join("/");
68
35
  }
69
36
  //#endregion
70
- export { decodePathParams, escapePathDelimiters, isInterceptionMatchedUrlPath, normalizePath };
37
+ export { isInterceptionMatchedUrlPath, normalizePath };
@@ -8,4 +8,51 @@
8
8
  * to resolve a `Location` containing the pathname as protocol-relative.
9
9
  */
10
10
  export declare function isOpenRedirectShaped(rawPathname: string): boolean;
11
+ /**
12
+ * What a request entry answers for a raw path containing a backslash or a
13
+ * repeated slash: Next.js's 308, or a 404 when the collapsed path would still
14
+ * be open-redirect shaped.
15
+ */
16
+ export type RepeatedSlashRedirect = {
17
+ status: 308;
18
+ location: string;
19
+ } | {
20
+ status: 404;
21
+ };
22
+ /**
23
+ * Returns Next.js's redirect for a raw request path containing a backslash or
24
+ * a repeated slash, or `null` when the path needs no change.
25
+ *
26
+ * Ported from Next.js's request entry (`base-server.ts` / `resolve-routes.ts`,
27
+ * via `normalizeRepeatedSlashes` in `shared/lib/utils.ts`): backslashes in the
28
+ * path become `/`, runs of slashes collapse to one and the query is kept.
29
+ * `next start` then builds the Location with `url.format(parseUrl(...))` in
30
+ * `router-server.ts`, so the result is serialized through a WHATWG URL here
31
+ * too (`/a/%2e%2e//b` → `/b`, `?q="x"` → `?q=%22x%22`, as observed). It runs
32
+ * before basePath handling, so `//` → `/`, `/docs//` → `/docs/` and
33
+ * `//evil.com` → `/evil.com`. Percent-encoded `%2F`/`%5C` are not touched and
34
+ * keep falling through to `isOpenRedirectShaped` (404), as in Next.js.
35
+ *
36
+ * The Location is always same-origin. Absolute-form request targets
37
+ * (`GET http://host//x`) are left alone. A collapsed path that still starts
38
+ * with an encoded slash or backslash (`//%2Fevil.com`, `/.//%2Fevil.com`) is
39
+ * never echoed into a Location or routed: it gets a 404 instead.
40
+ *
41
+ * @param rawUrl - The raw request target: pathname plus optional `?query`
42
+ */
43
+ export declare function getRepeatedSlashRedirect(rawUrl: string): RepeatedSlashRedirect | null;
44
+ /**
45
+ * Build the Response for a raw request path containing a backslash or a
46
+ * repeated slash. See `getRepeatedSlashRedirect`.
47
+ */
48
+ export declare function repeatedSlashRedirectResponse(rawUrl: string): Response | null;
49
+ /**
50
+ * Node `ServerResponse` counterpart of `repeatedSlashRedirectResponse`, for
51
+ * entries that see the raw request target (dev and `vinext start`). Returns
52
+ * true when a response was sent.
53
+ */
54
+ export declare function sendRepeatedSlashRedirect(rawUrl: string, res: {
55
+ writeHead(status: number, headers?: Record<string, string>): unknown;
56
+ end(body: string): unknown;
57
+ }): boolean;
11
58
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { notFoundResponse } from "./http-error-responses.js";
1
2
  //#region src/server/open-redirect.ts
2
3
  /**
3
4
  * Returns true if a request pathname looks like a protocol-relative open
@@ -17,5 +18,77 @@ function isOpenRedirectShaped(rawPathname) {
17
18
  }
18
19
  return false;
19
20
  }
21
+ const REPEATED_SLASH_OR_BACKSLASH_RE = /\\|\/\//;
22
+ const REDIRECT_LOCATION_BASE = "http://vinext.invalid";
23
+ /**
24
+ * Returns Next.js's redirect for a raw request path containing a backslash or
25
+ * a repeated slash, or `null` when the path needs no change.
26
+ *
27
+ * Ported from Next.js's request entry (`base-server.ts` / `resolve-routes.ts`,
28
+ * via `normalizeRepeatedSlashes` in `shared/lib/utils.ts`): backslashes in the
29
+ * path become `/`, runs of slashes collapse to one and the query is kept.
30
+ * `next start` then builds the Location with `url.format(parseUrl(...))` in
31
+ * `router-server.ts`, so the result is serialized through a WHATWG URL here
32
+ * too (`/a/%2e%2e//b` → `/b`, `?q="x"` → `?q=%22x%22`, as observed). It runs
33
+ * before basePath handling, so `//` → `/`, `/docs//` → `/docs/` and
34
+ * `//evil.com` → `/evil.com`. Percent-encoded `%2F`/`%5C` are not touched and
35
+ * keep falling through to `isOpenRedirectShaped` (404), as in Next.js.
36
+ *
37
+ * The Location is always same-origin. Absolute-form request targets
38
+ * (`GET http://host//x`) are left alone. A collapsed path that still starts
39
+ * with an encoded slash or backslash (`//%2Fevil.com`, `/.//%2Fevil.com`) is
40
+ * never echoed into a Location or routed: it gets a 404 instead.
41
+ *
42
+ * @param rawUrl - The raw request target: pathname plus optional `?query`
43
+ */
44
+ function getRepeatedSlashRedirect(rawUrl) {
45
+ if (!rawUrl.startsWith("/")) return null;
46
+ const urlParts = rawUrl.split("?");
47
+ const pathname = urlParts[0];
48
+ if (!REPEATED_SLASH_OR_BACKSLASH_RE.test(pathname)) return null;
49
+ const cleanUrl = pathname.replaceAll("\\", "/").replace(/\/\/+/g, "/") + (urlParts[1] ? `?${urlParts.slice(1).join("?")}` : "");
50
+ const parsed = new URL(REDIRECT_LOCATION_BASE + cleanUrl);
51
+ const location = parsed.pathname + parsed.search + parsed.hash;
52
+ return isOpenRedirectShaped(location) ? { status: 404 } : {
53
+ status: 308,
54
+ location
55
+ };
56
+ }
57
+ function repeatedSlashRedirectHeaders(location) {
58
+ return {
59
+ Location: location,
60
+ Refresh: `0;url=${location}`
61
+ };
62
+ }
63
+ /**
64
+ * Build the Response for a raw request path containing a backslash or a
65
+ * repeated slash. See `getRepeatedSlashRedirect`.
66
+ */
67
+ function repeatedSlashRedirectResponse(rawUrl) {
68
+ const redirect = getRepeatedSlashRedirect(rawUrl);
69
+ if (!redirect) return null;
70
+ if (redirect.status === 404) return notFoundResponse();
71
+ return new Response(redirect.location, {
72
+ status: 308,
73
+ headers: repeatedSlashRedirectHeaders(redirect.location)
74
+ });
75
+ }
76
+ /**
77
+ * Node `ServerResponse` counterpart of `repeatedSlashRedirectResponse`, for
78
+ * entries that see the raw request target (dev and `vinext start`). Returns
79
+ * true when a response was sent.
80
+ */
81
+ function sendRepeatedSlashRedirect(rawUrl, res) {
82
+ const redirect = getRepeatedSlashRedirect(rawUrl);
83
+ if (!redirect) return false;
84
+ if (redirect.status === 404) {
85
+ res.writeHead(404);
86
+ res.end("This page could not be found");
87
+ return true;
88
+ }
89
+ res.writeHead(308, repeatedSlashRedirectHeaders(redirect.location));
90
+ res.end(redirect.location);
91
+ return true;
92
+ }
20
93
  //#endregion
21
- export { isOpenRedirectShaped };
94
+ export { getRepeatedSlashRedirect, isOpenRedirectShaped, repeatedSlashRedirectResponse, sendRepeatedSlashRedirect };
@@ -2,7 +2,7 @@ import { NextI18nConfig } from "../config/next-config.js";
2
2
  import { Route } from "../routing/pages-router.js";
3
3
  import { ExecutionContextLike } from "../shims/request-context.js";
4
4
  import { EdgeApiExecutionRuntime } from "./edge-api-runtime.js";
5
- import { PagesReqResRequest, PagesReqResResponse, PagesRequestQuery } from "./pages-node-compat.js";
5
+ import { PagesApiReqResResponse, PagesReqResRequest, PagesRequestQuery } from "./pages-node-compat.js";
6
6
  //#region src/server/pages-api-route.d.ts
7
7
  type PagesApiRouteConfig = {
8
8
  runtime?: string;
@@ -36,7 +36,7 @@ type PagesApiRouteConfig = {
36
36
  externalResolver?: boolean;
37
37
  };
38
38
  };
39
- type PagesNodeApiRouteHandler = (req: PagesReqResRequest, res: PagesReqResResponse) => unknown;
39
+ type PagesNodeApiRouteHandler = (req: PagesReqResRequest, res: PagesApiReqResResponse) => unknown;
40
40
  type PagesEdgeApiRouteHandler = (request: Request) => Response | Promise<Response>;
41
41
  type PagesApiRouteModule = {
42
42
  /**
@@ -80,6 +80,7 @@ type HandlePagesApiRouteOptions = {
80
80
  allowedRevalidateHeaderKeys?: readonly string[];
81
81
  i18n?: NextI18nConfig | null;
82
82
  trailingSlash?: boolean;
83
+ skipTrailingSlashRedirect?: boolean;
83
84
  };
84
85
  trustedRevalidateOrigin?: string;
85
86
  };
@@ -9,7 +9,7 @@ import { mergeRouteParamsIntoQuery, parseQueryString, urlQueryToSearchParams } f
9
9
  import { tracePagesApiHandler } from "./pages-execution-tracing.js";
10
10
  import { resolveBodyParserConfig } from "./pages-body-parser-config.js";
11
11
  import { PagesBodyParseError } from "./pages-media-type.js";
12
- import { createPagesReqRes, markVinextStreamedApiResponse, parsePagesApiBody } from "./pages-node-compat.js";
12
+ import { createPagesApiReqRes, markVinextStreamedApiResponse, parsePagesApiBody } from "./pages-node-compat.js";
13
13
  import { finalizeEdgeApiResponse, isEdgeApiRuntime } from "./edge-api-runtime.js";
14
14
  //#region src/server/pages-api-route.ts
15
15
  function resolveModuleRuntime(module) {
@@ -47,7 +47,8 @@ async function _handlePagesApiRoute(options) {
47
47
  const nextRequest = new NextRequest(createEdgeApiRequest(options.request, options.url, params), options.nextConfig ? { nextConfig: {
48
48
  basePath: options.nextConfig.basePath,
49
49
  i18n: options.nextConfig.i18n ?? void 0,
50
- trailingSlash: options.nextConfig.trailingSlash
50
+ trailingSlash: options.nextConfig.trailingSlash,
51
+ skipTrailingSlashRedirect: options.nextConfig.skipTrailingSlashRedirect
51
52
  } } : void 0);
52
53
  const response = await tracePagesApiHandler(route.pattern, () => handler(nextRequest));
53
54
  if (response instanceof Response) {
@@ -70,7 +71,7 @@ async function _handlePagesApiRoute(options) {
70
71
  const query = buildPagesApiQuery(options.url, params);
71
72
  const bodyParserConfig = resolveBodyParserConfig(route.module.config);
72
73
  const body = bodyParserConfig.enabled ? await parsePagesApiBody(options.request, bodyParserConfig.sizeLimit) : void 0;
73
- const { req, res, responsePromise } = createPagesReqRes({
74
+ const { req, res, responsePromise } = createPagesApiReqRes({
74
75
  allowedRevalidateHeaderKeys: options.nextConfig?.allowedRevalidateHeaderKeys,
75
76
  body,
76
77
  initialResponseHeaders: options.initialResponseHeaders,
@@ -0,0 +1,12 @@
1
+ //#region src/server/pages-cache-tag.d.ts
2
+ /**
3
+ * Build the edge path tag for a Pages ISR pathname.
4
+ *
5
+ * Every spelling of one pathname shares a single ISR entry, because
6
+ * `pagesIsrCacheKey()` decodes each segment first. The tag must use that same
7
+ * decoded identity. Otherwise `/users/%40alice` is stored at the edge under its
8
+ * own tag, and `res.revalidate("/users/@alice")` or
9
+ * `revalidatePath("/users/@alice")` never purges it.
10
+ */
11
+ export declare function pagesPathCacheTag(isrPathname: string): string;
12
+ //#endregion
@@ -0,0 +1,19 @@
1
+ import { normalizePathnameForRouteMatch } from "../routing/utils.js";
2
+ import { encodeCacheTag } from "../utils/encode-cache-tag.js";
3
+ //#region src/server/pages-cache-tag.ts
4
+ /**
5
+ * Build the edge path tag for a Pages ISR pathname.
6
+ *
7
+ * Every spelling of one pathname shares a single ISR entry, because
8
+ * `pagesIsrCacheKey()` decodes each segment first. The tag must use that same
9
+ * decoded identity. Otherwise `/users/%40alice` is stored at the edge under its
10
+ * own tag, and `res.revalidate("/users/@alice")` or
11
+ * `revalidatePath("/users/@alice")` never purges it.
12
+ */
13
+ function pagesPathCacheTag(isrPathname) {
14
+ const pathname = normalizePathnameForRouteMatch(isrPathname);
15
+ const stem = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
16
+ return encodeCacheTag(`_N_T_${stem || "/"}`);
17
+ }
18
+ //#endregion
19
+ export { pagesPathCacheTag };
@@ -14,6 +14,8 @@ export type PagesClientAssets = {
14
14
  dynamicPreloads?: Record<string, string[]>;
15
15
  /** next.config crossOrigin, defaulting to anonymous for Vite's CORS-fetched assets. */
16
16
  crossOrigin?: AssetCrossOrigin;
17
+ /** Client bundle files outside `_next/static`, as URL pathnames. */
18
+ clientBuildFiles?: string[];
17
19
  };
18
20
  export declare function setPagesClientAssets(assets: PagesClientAssets | undefined): void;
19
21
  export declare function getPagesClientAssets(): PagesClientAssets;
@@ -61,6 +61,16 @@ export declare function encodeUrlParserIgnoredCharacters(pathname: string): stri
61
61
  * `/_next/data/<buildId>/about` → null (missing .json suffix)
62
62
  */
63
63
  export declare function parseNextDataPathname(pathname: string, buildId: string): NextDataMatch | null;
64
+ /**
65
+ * The page pathname a `_next/data` URL addresses, whatever its buildId. Next.js
66
+ * compiles every middleware matcher with an optional `/_next/data/<buildId>`
67
+ * prefix and `.json` suffix, so a matcher written for `/admin` also guards that
68
+ * page's data URL, even one with a stale buildId. Middleware matching uses this
69
+ * when the request reaches middleware with its raw data URL
70
+ * (`skipProxyUrlNormalize`).
71
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/build/analysis/get-page-static-info.ts
72
+ */
73
+ export declare function nextDataMiddlewareMatchPathname(pathname: string): string | null;
64
74
  export declare function normalizeNextDataPagePathname(pagePathname: string, trailingSlash?: boolean): string;
65
75
  /**
66
76
  * Next.js only applies `trailingSlash` while normalizing a data URL for
@@ -67,7 +67,24 @@ function parseNextDataPathname(pathname, buildId) {
67
67
  if (!isNextDataPathname(pathname)) return null;
68
68
  const expectedPrefix = `${NEXT_DATA_PREFIX}${buildId}/`;
69
69
  if (!pathname.startsWith(expectedPrefix)) return null;
70
- const rest = pathname.slice(expectedPrefix.length, -5);
70
+ return pageFromNextDataRest(pathname.slice(expectedPrefix.length, -5));
71
+ }
72
+ /**
73
+ * The page pathname a `_next/data` URL addresses, whatever its buildId. Next.js
74
+ * compiles every middleware matcher with an optional `/_next/data/<buildId>`
75
+ * prefix and `.json` suffix, so a matcher written for `/admin` also guards that
76
+ * page's data URL, even one with a stale buildId. Middleware matching uses this
77
+ * when the request reaches middleware with its raw data URL
78
+ * (`skipProxyUrlNormalize`).
79
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/build/analysis/get-page-static-info.ts
80
+ */
81
+ function nextDataMiddlewareMatchPathname(pathname) {
82
+ if (!isNextDataPathname(pathname)) return null;
83
+ const buildIdEnd = pathname.indexOf("/", 12);
84
+ if (buildIdEnd <= 12) return null;
85
+ return pageFromNextDataRest(pathname.slice(buildIdEnd + 1, -5))?.pagePathname ?? null;
86
+ }
87
+ function pageFromNextDataRest(rest) {
71
88
  if (rest.length === 0) return null;
72
89
  if (rest === "index") return { pagePathname: "/" };
73
90
  if (rest.endsWith("/index")) return { pagePathname: `/${rest.slice(0, -6)}` };
@@ -181,4 +198,4 @@ function normalizePagesDataRequest(request, buildId, basePath = "", trailingSlas
181
198
  };
182
199
  }
183
200
  //#endregion
184
- export { buildMiddlewarePrefetchSkipResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, encodeUrlParserIgnoredCharacters, isNextDataPathname, normalizeNextDataPagePathname, normalizePagesDataRequest, parseNextDataPathname, shouldAddTrailingSlashToPagesDataPath, urlParserCreatesPagesDataPath };
201
+ export { buildMiddlewarePrefetchSkipResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, encodeUrlParserIgnoredCharacters, isNextDataPathname, nextDataMiddlewareMatchPathname, normalizeNextDataPagePathname, normalizePagesDataRequest, parseNextDataPathname, shouldAddTrailingSlashToPagesDataPath, urlParserCreatesPagesDataPath };