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
@@ -14,6 +14,8 @@ export type BundleBackfillChunk = {
14
14
  * `toSlash` where the result becomes a module id / manifest key.
15
15
  */
16
16
  export declare function tryRealpathSync(candidate: string): string | null;
17
+ /** Forward-slash `path.relative(root, moduleId)`, which may escape with `../`. */
18
+ export declare function relativeToRoot(root: string, moduleId: string): string;
17
19
  export declare function relativeWithinRoot(root: string, moduleId: string): string | null;
18
20
  export declare function augmentSsrManifestFromBundle(ssrManifest: Record<string, string[]>, bundle: Record<string, BundleBackfillChunk | {
19
21
  type: string;
@@ -16,8 +16,12 @@ function tryRealpathSync(candidate) {
16
16
  function isWindowsAbsolutePath(candidate) {
17
17
  return /^[a-zA-Z]:[\\/]/.test(candidate) || candidate.startsWith("\\\\");
18
18
  }
19
+ /** Forward-slash `path.relative(root, moduleId)`, which may escape with `../`. */
20
+ function relativeToRoot(root, moduleId) {
21
+ return isWindowsAbsolutePath(root) || isWindowsAbsolutePath(moduleId) ? path.win32.relative(root, moduleId) : path.relative(root, moduleId);
22
+ }
19
23
  function relativeWithinRoot(root, moduleId) {
20
- const relativeId = isWindowsAbsolutePath(root) || isWindowsAbsolutePath(moduleId) ? path.win32.relative(root, moduleId) : path.relative(root, moduleId);
24
+ const relativeId = relativeToRoot(root, moduleId);
21
25
  if (!relativeId || relativeId === ".." || relativeId.startsWith("../")) return null;
22
26
  return relativeId;
23
27
  }
@@ -70,4 +74,4 @@ function augmentSsrManifestFromBundle(ssrManifest, bundle, root, base = "/") {
70
74
  return Object.fromEntries(Object.entries(nextManifest).map(([key, files]) => [key, [...files]]));
71
75
  }
72
76
  //#endregion
73
- export { augmentSsrManifestFromBundle, relativeWithinRoot, tryRealpathSync };
77
+ export { augmentSsrManifestFromBundle, relativeToRoot, relativeWithinRoot, tryRealpathSync };
@@ -74,7 +74,13 @@ export type CacheAdapterBuildOutput = {
74
74
  export type CacheAdapterDescriptor<O extends Record<string, unknown> = Record<string, unknown>> = {
75
75
  /**
76
76
  * Module specifier (or absolute path, e.g. from `require.resolve(...)`) whose
77
- * default export is a cache adapter factory.
77
+ * default export creates the adapter from one `{ env, options }` argument:
78
+ * either a factory `({ env, options }) => adapter` or a class
79
+ * `new Adapter({ env, options })`. Class syntax is invoked with `new`; any
80
+ * other function is called. For a constructor that is not class syntax (an
81
+ * ES5-compiled, bound or Proxy-wrapped class), export
82
+ * `(args) => new Adapter(args)`. The result must be the adapter object
83
+ * itself, not a Promise.
78
84
  */
79
85
  adapter: string;
80
86
  /** JSON-serializable options forwarded to the factory at runtime. */
@@ -103,7 +103,7 @@ function generateCacheAdaptersModule(cache) {
103
103
  "export function registerConfiguredCacheAdapters() {}",
104
104
  ""
105
105
  ].join("\n");
106
- const lines = ["// vinext: generated from the `cache` option in your vinext() plugin config."];
106
+ const lines = ["// vinext: generated from the `cache` option in your vinext() plugin config.", `import { instantiateCacheAdapter } from "vinext/shims/cache-adapter-instantiate";`];
107
107
  if (data?.adapter) {
108
108
  lines.push(`import __vinextDataAdapterFactory from ${JSON.stringify(data.adapter)};`);
109
109
  lines.push(`import { registerDataCacheHandler } from "vinext/shims/cache-handler";`);
@@ -114,11 +114,11 @@ function generateCacheAdaptersModule(cache) {
114
114
  } else if (dataProvidesBuildIdentity) lines.push(`import { DefaultCdnCacheAdapter } from "vinext/shims/cdn-cache";`, `import { registerCdnCacheAdapter } from "vinext/shims/cdn-cache-state";`);
115
115
  lines.push("", "// A factory that throws (e.g. a missing binding on an incompatible runtime)", "// is logged and skipped so the default handler stays in place.", "function __vinextFormatAdapterError(error) {", " if (error instanceof Error && error.message) return error.message;", " try {", " return String(error);", " } catch {", " return '<unknown error>';", " }", "}", "", "let __vinextCacheAdaptersRegistered = false;", "", "export function registerConfiguredCacheAdapters(env) {", " if (typeof process !== 'undefined' && process.env?.__VINEXT_PRERENDER_PATH_DISCOVERY === '1') return;", " if (__vinextCacheAdaptersRegistered) return;", " __vinextCacheAdaptersRegistered = true;");
116
116
  if (data?.adapter) {
117
- lines.push(" try {", ` registerDataCacheHandler(() => __vinextDataAdapterFactory({ env, options: ${inlineOptions(data.adapter, data.options)} }));`);
117
+ lines.push(" try {", ` registerDataCacheHandler(() => instantiateCacheAdapter(__vinextDataAdapterFactory, { env, options: ${inlineOptions(data.adapter, data.options)} }, "data"));`);
118
118
  if (dataProvidesBuildIdentity) lines.push(" registerCdnCacheAdapter(() => new DefaultCdnCacheAdapter(", " process.env.__VINEXT_RSC_BUILD_IDENTITY || process.env.__VINEXT_BUILD_ID,", " ));");
119
119
  lines.push(" } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured data cache adapter; using the default handler.\\n\" + __vinextFormatAdapterError(error));", " }");
120
120
  }
121
- if (cdn?.adapter) lines.push(" try {", ` registerCdnCacheAdapter(() => __vinextCdnAdapterFactory({ env, options: ${inlineOptions(cdn.adapter, cdn.options)} }));`, " } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured CDN cache adapter; using the default adapter.\\n\" + __vinextFormatAdapterError(error));", " }");
121
+ if (cdn?.adapter) lines.push(" try {", ` registerCdnCacheAdapter(() => instantiateCacheAdapter(__vinextCdnAdapterFactory, { env, options: ${inlineOptions(cdn.adapter, cdn.options)} }, "cdn"));`, " } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured CDN cache adapter; using the default adapter.\\n\" + __vinextFormatAdapterError(error));", " }");
122
122
  lines.push("}", "");
123
123
  return lines.join("\n");
124
124
  }
package/dist/check.js CHANGED
@@ -210,7 +210,7 @@ const CONFIG_SUPPORT = {
210
210
  },
211
211
  transpilePackages: {
212
212
  status: "supported",
213
- detail: "listed packages are bundled instead of externalized on the server"
213
+ detail: "listed packages are bundled instead of externalized on the server, and their next/dynamic() calls get SSR preload links"
214
214
  },
215
215
  serverExternalPackages: {
216
216
  status: "supported",
@@ -252,9 +252,10 @@ const CONFIG_SUPPORT = {
252
252
  status: "unsupported",
253
253
  detail: "custom cacheLife profiles are ignored; only the built-in profiles are available"
254
254
  },
255
- skipTrailingSlashRedirect: {
256
- status: "unsupported",
257
- detail: "ignored; trailing-slash redirects are always applied"
255
+ skipTrailingSlashRedirect: { status: "supported" },
256
+ "experimental.skipTrailingSlashRedirect": {
257
+ status: "supported",
258
+ detail: "legacy alias of skipTrailingSlashRedirect"
258
259
  },
259
260
  reactCompiler: {
260
261
  status: "partial",
@@ -433,8 +434,8 @@ const LIBRARY_SUPPORT = {
433
434
  detail: "renders client-side; by default events are only collected when deployed on Vercel"
434
435
  },
435
436
  "next-intl": {
436
- status: "partial",
437
- detail: "auto-detected from (src/)i18n/request.{ts,tsx,js,jsx}, so createNextIntlPlugin isn't needed; client components can fail with \"No intl context found\" (cloudflare/vinext#177)"
437
+ status: "supported",
438
+ detail: "createNextIntlPlugin is only optional with the request config at (src/)i18n/request.*; its experimental messages, precompile and extract options don't work (they need webpack/Turbopack loaders)"
438
439
  },
439
440
  "@clerk/nextjs": {
440
441
  status: "supported",
package/dist/cli.js CHANGED
@@ -4,8 +4,8 @@ import { detectPackageManager, ensureViteConfigCompatibility, findViteConfigPath
4
4
  import { formatReport, runCheck } from "./check.js";
5
5
  import { parseArgs } from "./cli-args.js";
6
6
  import { findViteRoot, getViteCliInvocation, valueOptionName } from "./utils/vite-cli-invocation.js";
7
- import { init } from "./init.js";
8
7
  import { resolveInitOptions } from "./init-platform.js";
8
+ import { init } from "./init.js";
9
9
  import { loadDotenv } from "./config/dotenv.js";
10
10
  import { PHASE_PRODUCTION_BUILD } from "./shims/constants.js";
11
11
  import { loadNextConfig, resolveNextConfig } from "./config/next-config.js";
@@ -275,6 +275,7 @@ function printHelp(cmd) {
275
275
  --no-install Update dependency entries without installing them
276
276
  --force Overwrite existing vite.config.ts
277
277
  --platform <target> Deployment target: cloudflare or node
278
+ --name <name> Cloudflare Worker name (default: derived from package.json)
278
279
  --prerender Configure Vite builds to pre-render static routes
279
280
  (Cloudflare deploy won't serve them unless using Static Assets)
280
281
  --warm-cache Add cache warming to the Cloudflare deploy script
@@ -0,0 +1,14 @@
1
+ import { RequestContext } from "../config/request-context.js";
2
+ import { BasePathMatchState, RedirectMatch } from "../config/config-matchers.js";
3
+ import { ClientRedirect } from "./client-redirects.js";
4
+ //#region src/client/client-redirect-matcher.d.ts
5
+ /**
6
+ * Resolve the first matching client-safe redirect. A matching server-owned
7
+ * rule stops client evaluation and returns null so a later client-safe rule
8
+ * cannot take its place. The navigation then continues as it does in Next.js,
9
+ * which never resolves config redirects in the browser: a document request (or
10
+ * a data request that falls back to one) reaches the server rule, while a page
11
+ * without data fetching renders without it.
12
+ */
13
+ export declare function matchClientRedirect(pathname: string, redirects: ClientRedirect[], context: RequestContext, basePathState: BasePathMatchState): RedirectMatch | null;
14
+ //#endregion
@@ -0,0 +1,37 @@
1
+ import { matchRedirect } from "../config/config-matchers.js";
2
+ //#region src/client/client-redirect-matcher.ts
3
+ const matcherRules = /* @__PURE__ */ new WeakMap();
4
+ function getMatcherRule(redirect) {
5
+ let rule = matcherRules.get(redirect);
6
+ if (!rule) {
7
+ rule = [{
8
+ source: redirect.source,
9
+ destination: redirect.destination ?? "/",
10
+ permanent: redirect.permanent ?? false,
11
+ has: redirect.has,
12
+ missing: redirect.missing,
13
+ locale: redirect.locale,
14
+ basePath: redirect.basePath
15
+ }];
16
+ matcherRules.set(redirect, rule);
17
+ }
18
+ return rule;
19
+ }
20
+ /**
21
+ * Resolve the first matching client-safe redirect. A matching server-owned
22
+ * rule stops client evaluation and returns null so a later client-safe rule
23
+ * cannot take its place. The navigation then continues as it does in Next.js,
24
+ * which never resolves config redirects in the browser: a document request (or
25
+ * a data request that falls back to one) reaches the server rule, while a page
26
+ * without data fetching renders without it.
27
+ */
28
+ function matchClientRedirect(pathname, redirects, context, basePathState) {
29
+ for (const redirect of redirects) {
30
+ const match = matchRedirect(pathname, getMatcherRule(redirect), context, basePathState);
31
+ if (match === null) continue;
32
+ return redirect.requiresServerEvaluation ? null : match;
33
+ }
34
+ return null;
35
+ }
36
+ //#endregion
37
+ export { matchClientRedirect };
@@ -0,0 +1,27 @@
1
+ import { NextRedirect, ResolvedNextConfig } from "../config/next-config.js";
2
+ import { ClientHasCondition } from "./client-rewrites.js";
3
+ //#region src/client/client-redirects.d.ts
4
+ type ClientRedirectFields = Pick<NextRedirect, "basePath" | "locale" | "source"> & {
5
+ has?: ClientHasCondition[];
6
+ missing?: ClientHasCondition[];
7
+ };
8
+ /**
9
+ * Redirect data that is safe to publish in a browser bundle.
10
+ *
11
+ * Next.js keeps redirects in the server routes manifest only. Vinext resolves
12
+ * browser-authoritative redirects during Pages Router navigation, but rules
13
+ * with header or cookie conditions are server-owned: their condition values
14
+ * can be private, so the rule keeps only its source and client-safe
15
+ * conditions to preserve first-match ordering, and drops its destination.
16
+ */
17
+ export type ClientRedirect = (ClientRedirectFields & {
18
+ destination: string;
19
+ permanent: boolean;
20
+ requiresServerEvaluation?: never;
21
+ }) | (ClientRedirectFields & {
22
+ destination?: never;
23
+ permanent?: never;
24
+ requiresServerEvaluation: true;
25
+ });
26
+ export declare function toClientRedirects(redirects: ResolvedNextConfig["redirects"]): ClientRedirect[];
27
+ //#endregion
@@ -0,0 +1,28 @@
1
+ import { isClientHasCondition } from "./client-rewrites.js";
2
+ //#region src/client/client-redirects.ts
3
+ function toClientRedirect(redirect) {
4
+ const hasServerOnlyCondition = (redirect.has?.some((condition) => !isClientHasCondition(condition)) ?? false) || (redirect.missing?.some((condition) => !isClientHasCondition(condition)) ?? false);
5
+ const clientHas = redirect.has?.filter(isClientHasCondition);
6
+ const clientMissing = redirect.missing?.filter(isClientHasCondition);
7
+ const common = {
8
+ source: redirect.source,
9
+ has: clientHas?.length ? clientHas : void 0,
10
+ missing: clientMissing?.length ? clientMissing : void 0,
11
+ locale: redirect.locale,
12
+ basePath: redirect.basePath
13
+ };
14
+ if (hasServerOnlyCondition) return {
15
+ ...common,
16
+ requiresServerEvaluation: true
17
+ };
18
+ return {
19
+ ...common,
20
+ destination: redirect.destination,
21
+ permanent: redirect.permanent
22
+ };
23
+ }
24
+ function toClientRedirects(redirects) {
25
+ return redirects.map(toClientRedirect);
26
+ }
27
+ //#endregion
28
+ export { toClientRedirects };
@@ -1,6 +1,6 @@
1
1
  import { HasCondition, NextRewrite, ResolvedNextConfig } from "../config/next-config.js";
2
2
  //#region src/client/client-rewrites.d.ts
3
- type ClientHasCondition = Omit<HasCondition, "type"> & {
3
+ export type ClientHasCondition = Omit<HasCondition, "type"> & {
4
4
  type: "host" | "query";
5
5
  };
6
6
  type ClientRewriteFields = Pick<NextRewrite, "basePath" | "locale" | "source"> & {
@@ -26,5 +26,6 @@ export type ClientRewrites = {
26
26
  beforeFiles: ClientRewrite[];
27
27
  fallback: ClientRewrite[];
28
28
  };
29
+ export declare function isClientHasCondition(condition: HasCondition): condition is ClientHasCondition;
29
30
  export declare function toClientRewrites(rewrites: ResolvedNextConfig["rewrites"]): ClientRewrites;
30
31
  //#endregion
@@ -38,4 +38,4 @@ function toClientRewrites(rewrites) {
38
38
  };
39
39
  }
40
40
  //#endregion
41
- export { toClientRewrites };
41
+ export { isClientHasCondition, toClientRewrites };
@@ -57,6 +57,17 @@ export type NavigationRuntimeFunctions = {
57
57
  preparePrefetchResponse?: (response: Response) => Promise<unknown>;
58
58
  claimCurrentHistoryTreeSnapshot?: (historyUpdateMode: NavigationRuntimeHistoryUpdateMode, previousHistoryState: unknown) => void;
59
59
  commitAppOwnedHistoryStateWrite?: (historyUpdateMode: NavigationRuntimeHistoryUpdateMode, previousHistoryState: unknown) => void;
60
+ /**
61
+ * Called after a raw (non-app-owned) history.pushState/replaceState with a
62
+ * URL so an App Router navigation that has not committed yet is discarded,
63
+ * matching Next.js' ACTION_RESTORE.
64
+ */
65
+ discardPendingNavigation?: () => void;
66
+ /**
67
+ * Called before any history.pushState/replaceState so a navigation React is
68
+ * committing writes its URL first, as Next.js does from an insertion effect.
69
+ */
70
+ flushCommittingNavigationUrl?: () => void;
60
71
  };
61
72
  export type NavigationRuntimeBootstrap = {
62
73
  routeManifest: RouteManifest | null;
@@ -31,7 +31,7 @@ function readRuntimeWindow() {
31
31
  }
32
32
  function isNavigationRuntimeFunctions(value) {
33
33
  if (!isUnknownRecord(value)) return false;
34
- return isOptionalRuntimeFunction(Reflect.get(value, "clearNavigationCaches")) && isOptionalRuntimeFunction(Reflect.get(value, "commitHashNavigation")) && isOptionalRuntimeFunction(Reflect.get(value, "navigateExternal")) && isOptionalRuntimeFunction(Reflect.get(value, "navigate")) && isOptionalRuntimeFunction(Reflect.get(value, "getPrefetchRouterState")) && isOptionalRuntimeFunction(Reflect.get(value, "notifyLinkNavigationStart")) && isOptionalRuntimeFunction(Reflect.get(value, "pingVisibleLinks")) && isOptionalRuntimeFunction(Reflect.get(value, "preparePrefetchResponse")) && isOptionalRuntimeFunction(Reflect.get(value, "claimCurrentHistoryTreeSnapshot")) && isOptionalRuntimeFunction(Reflect.get(value, "commitAppOwnedHistoryStateWrite"));
34
+ return isOptionalRuntimeFunction(Reflect.get(value, "clearNavigationCaches")) && isOptionalRuntimeFunction(Reflect.get(value, "commitHashNavigation")) && isOptionalRuntimeFunction(Reflect.get(value, "navigateExternal")) && isOptionalRuntimeFunction(Reflect.get(value, "navigate")) && isOptionalRuntimeFunction(Reflect.get(value, "getPrefetchRouterState")) && isOptionalRuntimeFunction(Reflect.get(value, "notifyLinkNavigationStart")) && isOptionalRuntimeFunction(Reflect.get(value, "pingVisibleLinks")) && isOptionalRuntimeFunction(Reflect.get(value, "preparePrefetchResponse")) && isOptionalRuntimeFunction(Reflect.get(value, "claimCurrentHistoryTreeSnapshot")) && isOptionalRuntimeFunction(Reflect.get(value, "commitAppOwnedHistoryStateWrite")) && isOptionalRuntimeFunction(Reflect.get(value, "discardPendingNavigation")) && isOptionalRuntimeFunction(Reflect.get(value, "flushCommittingNavigationUrl"));
35
35
  }
36
36
  function isNavigationRuntimeRscChunk(value) {
37
37
  if (typeof value === "string") return true;
@@ -1,5 +1,6 @@
1
1
  import { HasCondition, NextHeader, NextI18nConfig, NextRedirect, NextRewrite } from "./next-config.js";
2
2
  import { RequestContext, normalizeHost, parseCookies, requestContextFromRequest } from "./request-context.js";
3
+ import { RedirectDestinationQueryPart } from "./destination-params.js";
3
4
  import { isExternalUrl } from "../utils/external-url.js";
4
5
  //#region src/config/config-matchers.d.ts
5
6
  /**
@@ -75,6 +76,15 @@ export declare function applyMiddlewareRequestHeaders(middlewareHeaders: Record<
75
76
  };
76
77
  export declare function checkHasConditions(has: HasCondition[] | undefined, missing: HasCondition[] | undefined, ctx: RequestContext): boolean;
77
78
  export declare function matchConfigPattern(pathname: string, pattern: string): Record<string, string> | null;
79
+ /**
80
+ * A matched config redirect. `destinationQuery` holds the destination query's
81
+ * parts, split before params were substituted, for merging the request query.
82
+ */
83
+ export type RedirectMatch = {
84
+ destination: string;
85
+ permanent: boolean;
86
+ destinationQuery: RedirectDestinationQueryPart[];
87
+ };
78
88
  /**
79
89
  * Apply redirect rules from next.config.js.
80
90
  * Returns the redirect info if a redirect was matched, or null.
@@ -110,10 +120,7 @@ export declare function matchConfigPattern(pathname: string, pattern: string): R
110
120
  * an original index < N are checked via matchConfigPattern first — they are
111
121
  * few in practice (typically zero) so this is not a hot-path concern.
112
122
  */
113
- export declare function matchRedirect(pathname: string, redirects: NextRedirect[], ctx: RequestContext, basePathState?: BasePathMatchState, onRuleSourceMatch?: (rule: NextRedirect) => void): {
114
- destination: string;
115
- permanent: boolean;
116
- } | null;
123
+ export declare function matchRedirect(pathname: string, redirects: NextRedirect[], ctx: RequestContext, basePathState?: BasePathMatchState, onRuleSourceMatch?: (rule: NextRedirect) => void): RedirectMatch | null;
117
124
  /**
118
125
  * Apply rewrite rules from next.config.js.
119
126
  * Returns the rewritten URL or null if no rewrite matched.
@@ -142,7 +149,8 @@ export declare function matchesRewriteSource(pathname: string, rewrite: NextRewr
142
149
  * users off-site.
143
150
  *
144
151
  * This function collapses any leading double (or more) slashes to a single
145
- * slash for non-external (relative) destinations.
152
+ * slash for non-external (relative) destinations, including tab/newline and
153
+ * leading C0 control characters that browsers ignore while parsing.
146
154
  */
147
155
  export declare function sanitizeDestination(dest: string): string;
148
156
  /**
@@ -162,13 +170,20 @@ export declare function sanitizeDestination(dest: string): string;
162
170
  * still treated as an RSC fetch. Dropping it breaks RSC fetch semantics
163
171
  * (issue #1529).
164
172
  *
165
- * Destination query params win — a request param is only carried over when
166
- * the destination does not already specify that key. Mirrors the merge
167
- * semantics in `proxyExternalRequest`. External destinations are returned
168
- * untouched (a config redirect to another origin should not leak the
169
- * original request's query).
173
+ * Ported from Next.js's `{ ...requestQuery, ...destinationQuery }` merge and
174
+ * `stringifyQuery`: request keys keep their order (integer-like keys first, as
175
+ * in a JS object), a destination key overrides the request value in place,
176
+ * and destination-only keys follow. Request keys and values are re-encoded;
177
+ * destination query text is emitted verbatim, except that, like Next.js,
178
+ * destination strings equal to a request key or value are re-encoded too.
179
+ * Pass the match's
180
+ * `destinationQuery` so substituted params keep their value boundaries;
181
+ * without it the destination's own query is split. External destinations are returned untouched (a config redirect to another
182
+ * origin should not leak the original request's query).
183
+ *
184
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
170
185
  */
171
- export declare function preserveRedirectDestinationQuery(destination: string, requestSearch: string): string;
186
+ export declare function preserveRedirectDestinationQuery(destination: string, requestSearch: string, destinationQuery?: RedirectDestinationQueryPart[]): string;
172
187
  /**
173
188
  * Proxy an incoming request to an external URL and return the upstream response.
174
189
  *
@@ -3,6 +3,7 @@ import { buildRequestHeadersFromMiddlewareResponse, getUnconsumedMiddlewareReque
3
3
  import { analyzeRegexSafety } from "../utils/regex-safety.js";
4
4
  import { normalizeHost, parseCookies, requestContextFromRequest } from "./request-context.js";
5
5
  import { isExternalUrl } from "../utils/external-url.js";
6
+ import { substituteDestinationParams, substituteRedirectDestinationQuery } from "./destination-params.js";
6
7
  //#region src/config/config-matchers.ts
7
8
  /**
8
9
  * Cache for compiled regex patterns in matchConfigPattern.
@@ -44,14 +45,6 @@ const _compiledHeaderSourceCache = /* @__PURE__ */ new Map();
44
45
  */
45
46
  const _compiledConditionCache = /* @__PURE__ */ new Map();
46
47
  /**
47
- * Cache for destination substitution regexes in substituteDestinationParams.
48
- *
49
- * The regex depends only on the set of param keys captured from the matched
50
- * source pattern. Caching by sorted key list avoids recompiling a new RegExp
51
- * for repeated redirect/rewrite calls that use the same param shape.
52
- */
53
- const _compiledDestinationParamCache = /* @__PURE__ */ new Map();
54
- /**
55
48
  * Generic helper for the regex compilation caches above.
56
49
  *
57
50
  * Each cache stores the compiled artifact (or `null` when safeRegExp rejected
@@ -307,6 +300,7 @@ function applyMiddlewareRequestHeaders(middlewareHeaders, request) {
307
300
  method: request.method,
308
301
  headers: nextHeaders,
309
302
  body: request.body,
303
+ signal: request.signal,
310
304
  duplex: request.body ? "half" : void 0
311
305
  });
312
306
  return {
@@ -560,13 +554,10 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
560
554
  onRuleSourceMatch?.(redirect);
561
555
  const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
562
556
  if (!conditionParams) continue;
563
- localeMatch = {
564
- destination: substituteAndSanitizeDestination(redirect.destination, {
565
- [entry.paramName]: "",
566
- ...conditionParams
567
- }),
568
- permanent: redirect.permanent
569
- };
557
+ localeMatch = resolveRedirectMatch(redirect, {
558
+ [entry.paramName]: "",
559
+ ...conditionParams
560
+ });
570
561
  localeMatchIndex = entry.originalIndex;
571
562
  break;
572
563
  }
@@ -583,13 +574,10 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
583
574
  onRuleSourceMatch?.(redirect);
584
575
  const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
585
576
  if (!conditionParams) continue;
586
- localeMatch = {
587
- destination: substituteAndSanitizeDestination(redirect.destination, {
588
- [entry.paramName]: localePart,
589
- ...conditionParams
590
- }),
591
- permanent: redirect.permanent
592
- };
577
+ localeMatch = resolveRedirectMatch(redirect, {
578
+ [entry.paramName]: localePart,
579
+ ...conditionParams
580
+ });
593
581
  localeMatchIndex = entry.originalIndex;
594
582
  break;
595
583
  }
@@ -603,13 +591,10 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
603
591
  onRuleSourceMatch?.(redirect);
604
592
  const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
605
593
  if (!conditionParams) continue;
606
- return {
607
- destination: substituteAndSanitizeDestination(redirect.destination, {
608
- ...params,
609
- ...conditionParams
610
- }),
611
- permanent: redirect.permanent
612
- };
594
+ return resolveRedirectMatch(redirect, {
595
+ ...params,
596
+ ...conditionParams
597
+ });
613
598
  }
614
599
  }
615
600
  return localeMatch;
@@ -635,12 +620,24 @@ function matchRewrite(pathname, rewrites, ctx, basePathState = _BASEPATH_DEFAULT
635
620
  ...params,
636
621
  ...conditionParams
637
622
  };
638
- return substituteAndSanitizeRewriteDestination(rewrite.destination, rewriteParams);
623
+ return substituteAndSanitizeRewriteDestination(serializeRewriteDestinationTemplate(rewrite.destination), rewriteParams);
639
624
  }
640
625
  }
641
626
  return null;
642
627
  }
643
628
  /**
629
+ * Next.js prepareDestination URL-parses the destination template before it
630
+ * substitutes params, so a literal `é` routes as `%C3%A9` while substituted
631
+ * values stay verbatim: a captured `../admin` is never resolved to `/admin`.
632
+ *
633
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
634
+ */
635
+ function serializeRewriteDestinationTemplate(destination) {
636
+ if (!destination.startsWith("/") || destination.startsWith("//")) return destination;
637
+ const parsed = new URL(destination, "http://n");
638
+ return parsed.pathname + parsed.search + parsed.hash;
639
+ }
640
+ /**
644
641
  * Check whether a rewrite source can match a pathname without evaluating its
645
642
  * request-dependent `has` / `missing` conditions.
646
643
  *
@@ -653,47 +650,20 @@ function matchesRewriteSource(pathname, rewrite, basePathState = _BASEPATH_DEFAU
653
650
  return shouldEvaluateRule(rewrite.basePath, basePathState) && matchConfigPattern(pathname, rewrite.source) !== null;
654
651
  }
655
652
  /**
656
- * Substitute all matched route params into a redirect/rewrite destination.
657
- *
658
- * Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
659
- * (`:path*`, `:path+`) in a single pass. Unknown params are left intact.
660
- */
661
- function substituteDestinationParams(destination, params) {
662
- const keys = Object.keys(params);
663
- if (keys.length === 0) return destination;
664
- const sortedKeys = [...keys].sort((a, b) => b.length - a.length);
665
- const cacheKey = sortedKeys.join("\0");
666
- let paramRe = _compiledDestinationParamCache.get(cacheKey);
667
- if (!paramRe) {
668
- const paramAlternation = sortedKeys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
669
- paramRe = new RegExp(`:(${paramAlternation})([+*])?(?![A-Za-z0-9_])`, "g");
670
- _compiledDestinationParamCache.set(cacheKey, paramRe);
671
- }
672
- const replaceParams = (value, encodeParam) => value.replace(paramRe, (_token, key) => encodeParam(params[key]));
673
- const hashIndex = destination.indexOf("#");
674
- const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
675
- const hash = hashIndex === -1 ? "" : destination.slice(hashIndex);
676
- const queryIndex = beforeHash.indexOf("?");
677
- if (queryIndex !== -1) {
678
- const beforeQuery = beforeHash.slice(0, queryIndex);
679
- const query = beforeHash.slice(queryIndex + 1);
680
- return `${replaceParams(beforeQuery, (value) => value)}?${replaceParams(query, encodeDestinationQueryParamValue)}${replaceParams(hash, (value) => value)}`;
681
- }
682
- return replaceParams(destination, (value) => value);
683
- }
684
- function encodeDestinationQueryParamValue(value) {
685
- const params = new URLSearchParams();
686
- params.set("", value);
687
- return params.toString().slice(1);
688
- }
689
- /**
690
653
  * Substitute params into a redirect/rewrite destination and sanitize the
691
654
  * result. Used by every redirect/rewrite branch — the substitution can
692
655
  * introduce protocol-relative URLs (e.g. `//evil.com` from a decoded `%2F`
693
656
  * in a catch-all param), which sanitizeDestination collapses.
694
657
  */
695
- function substituteAndSanitizeDestination(destination, params) {
696
- return sanitizeDestination(substituteDestinationParams(destination, params));
658
+ function substituteAndSanitizeDestination(destination, params, kind) {
659
+ return sanitizeDestination(substituteDestinationParams(destination, params, kind));
660
+ }
661
+ function resolveRedirectMatch(redirect, params) {
662
+ return {
663
+ destination: substituteAndSanitizeDestination(redirect.destination, params, "redirect"),
664
+ permanent: redirect.permanent,
665
+ destinationQuery: substituteRedirectDestinationQuery(redirect.destination, params)
666
+ };
697
667
  }
698
668
  /**
699
669
  * Match Next.js's rewrite-specific prepareDestination behavior: source params
@@ -703,7 +673,7 @@ function substituteAndSanitizeDestination(destination, params) {
703
673
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
704
674
  */
705
675
  function substituteAndSanitizeRewriteDestination(destination, params) {
706
- const rewritten = substituteAndSanitizeDestination(destination, params);
676
+ const rewritten = substituteAndSanitizeDestination(destination, params, "rewrite");
707
677
  if (!shouldAppendRewriteParamsToQuery(destination, params)) return rewritten;
708
678
  const existingQueryKeys = getDestinationQueryKeys(destination);
709
679
  const paramsToAppend = [];
@@ -770,11 +740,12 @@ function appendQueryParams(url, params) {
770
740
  * users off-site.
771
741
  *
772
742
  * This function collapses any leading double (or more) slashes to a single
773
- * slash for non-external (relative) destinations.
743
+ * slash for non-external (relative) destinations, including tab/newline and
744
+ * leading C0 control characters that browsers ignore while parsing.
774
745
  */
775
746
  function sanitizeDestination(dest) {
776
747
  if (dest.startsWith("http://") || dest.startsWith("https://")) return dest;
777
- dest = dest.replace(/^[\\/]+/, "/");
748
+ dest = dest.replace(/^[\u0000-\u0020]*(?:[\\/][\t\n\r]*)+/, "/");
778
749
  return dest;
779
750
  }
780
751
  /**
@@ -794,13 +765,20 @@ function sanitizeDestination(dest) {
794
765
  * still treated as an RSC fetch. Dropping it breaks RSC fetch semantics
795
766
  * (issue #1529).
796
767
  *
797
- * Destination query params win — a request param is only carried over when
798
- * the destination does not already specify that key. Mirrors the merge
799
- * semantics in `proxyExternalRequest`. External destinations are returned
800
- * untouched (a config redirect to another origin should not leak the
801
- * original request's query).
768
+ * Ported from Next.js's `{ ...requestQuery, ...destinationQuery }` merge and
769
+ * `stringifyQuery`: request keys keep their order (integer-like keys first, as
770
+ * in a JS object), a destination key overrides the request value in place,
771
+ * and destination-only keys follow. Request keys and values are re-encoded;
772
+ * destination query text is emitted verbatim, except that, like Next.js,
773
+ * destination strings equal to a request key or value are re-encoded too.
774
+ * Pass the match's
775
+ * `destinationQuery` so substituted params keep their value boundaries;
776
+ * without it the destination's own query is split. External destinations are returned untouched (a config redirect to another
777
+ * origin should not leak the original request's query).
778
+ *
779
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
802
780
  */
803
- function preserveRedirectDestinationQuery(destination, requestSearch) {
781
+ function preserveRedirectDestinationQuery(destination, requestSearch, destinationQuery) {
804
782
  if (requestSearch === "" || requestSearch === "?" || isExternalUrl(destination)) return destination;
805
783
  const requestParams = new URLSearchParams(requestSearch);
806
784
  if ([...requestParams.keys()].length === 0) return destination;
@@ -809,13 +787,29 @@ function preserveRedirectDestinationQuery(destination, requestSearch) {
809
787
  const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
810
788
  const queryIndex = beforeHash.indexOf("?");
811
789
  const pathPart = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
812
- const destQuery = queryIndex === -1 ? "" : beforeHash.slice(queryIndex + 1);
813
- const merged = new URLSearchParams(destQuery);
814
- const destKeys = new Set(merged.keys());
815
- for (const [key, value] of requestParams) if (!destKeys.has(key)) merged.append(key, value);
816
- const mergedQuery = merged.toString();
790
+ const requestStrings = /* @__PURE__ */ new Set();
791
+ const requestParts = Object.create(null);
792
+ for (const [key, value] of requestParams) {
793
+ requestStrings.add(key);
794
+ requestStrings.add(value);
795
+ (requestParts[key] ??= []).push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
796
+ }
797
+ const encode = (text) => requestStrings.has(text) ? encodeURIComponent(text) : text;
798
+ const destinationParts = Object.create(null);
799
+ for (const [key, keyText, valueText] of destinationQuery ?? substituteRedirectDestinationQuery(destination, {})) (destinationParts[key] ??= []).push(valueText === null ? encode(keyText) : `${encode(keyText)}=${encode(valueText)}`);
800
+ const mergedQuery = Object.values({
801
+ ...requestParts,
802
+ ...destinationParts
803
+ }).flat().join("&");
817
804
  return mergedQuery === "" ? `${pathPart}${hash}` : `${pathPart}?${mergedQuery}${hash}`;
818
805
  }
806
+ function parseExternalRewriteUrl(externalUrl) {
807
+ try {
808
+ return new URL(externalUrl);
809
+ } catch {
810
+ return null;
811
+ }
812
+ }
819
813
  /**
820
814
  * Proxy an incoming request to an external URL and return the upstream response.
821
815
  *
@@ -827,7 +821,11 @@ function preserveRedirectDestinationQuery(destination, requestSearch) {
827
821
  */
828
822
  async function proxyExternalRequest(request, externalUrl) {
829
823
  const originalUrl = new URL(request.url);
830
- const targetUrl = new URL(externalUrl);
824
+ const targetUrl = parseExternalRewriteUrl(externalUrl);
825
+ if (!targetUrl) {
826
+ console.error("[vinext] Invalid external rewrite destination:", externalUrl);
827
+ return new Response("Internal Server Error", { status: 500 });
828
+ }
831
829
  const destinationKeys = new Set(targetUrl.searchParams.keys());
832
830
  for (const [key, value] of originalUrl.searchParams) if (!destinationKeys.has(key)) targetUrl.searchParams.append(key, value);
833
831
  const headers = new Headers(request.headers);
@@ -859,9 +857,10 @@ async function proxyExternalRequest(request, externalUrl) {
859
857
  try {
860
858
  upstreamResponse = await fetch(targetUrl.href, {
861
859
  ...init,
862
- signal: controller.signal
860
+ signal: AbortSignal.any([request.signal, controller.signal])
863
861
  });
864
862
  } catch (e) {
863
+ if (request.signal.aborted && !controller.signal.aborted) return new Response(null, { status: 499 });
865
864
  if (e instanceof Error && e.name === "AbortError") {
866
865
  console.error("[vinext] External rewrite proxy timeout:", targetUrl.href);
867
866
  return new Response("Gateway Timeout", { status: 504 });