vinext 1.1.0 → 1.2.1

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 (288) hide show
  1. package/README.md +22 -10
  2. package/dist/build/client-build-config.d.ts +10 -0
  3. package/dist/build/client-build-config.js +18 -2
  4. package/dist/build/output-root-externals.d.ts +21 -0
  5. package/dist/build/output-root-externals.js +40 -0
  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 +6 -4
  9. package/dist/build/prerender.js +1 -1
  10. package/dist/build/run-prerender.js +1 -1
  11. package/dist/build/ssr-manifest.d.ts +2 -0
  12. package/dist/build/ssr-manifest.js +6 -2
  13. package/dist/check.js +12 -12
  14. package/dist/cli-dev-config.js +3 -3
  15. package/dist/cli.js +3 -2
  16. package/dist/client/client-redirect-matcher.d.ts +14 -0
  17. package/dist/client/client-redirect-matcher.js +37 -0
  18. package/dist/client/client-redirects.d.ts +27 -0
  19. package/dist/client/client-redirects.js +28 -0
  20. package/dist/client/client-rewrites.d.ts +2 -1
  21. package/dist/client/client-rewrites.js +1 -1
  22. package/dist/config/config-matchers.d.ts +26 -11
  23. package/dist/config/config-matchers.js +90 -88
  24. package/dist/config/destination-params.d.ts +47 -0
  25. package/dist/config/destination-params.js +116 -0
  26. package/dist/config/next-config.d.ts +7 -0
  27. package/dist/config/next-config.js +3 -0
  28. package/dist/entries/app-rsc-entry.d.ts +4 -0
  29. package/dist/entries/app-rsc-entry.js +20 -6
  30. package/dist/entries/pages-client-entry.js +3 -1
  31. package/dist/entries/pages-server-entry.js +13 -37
  32. package/dist/index.d.ts +3 -1
  33. package/dist/index.js +246 -136
  34. package/dist/init-cloudflare.d.ts +9 -0
  35. package/dist/init-cloudflare.js +107 -31
  36. package/dist/init-platform.d.ts +4 -0
  37. package/dist/init-platform.js +27 -2
  38. package/dist/init.d.ts +2 -1
  39. package/dist/init.js +153 -9
  40. package/dist/plugins/ast-scope.d.ts +7 -0
  41. package/dist/plugins/ast-scope.js +59 -3
  42. package/dist/plugins/ast-utils.d.ts +53 -2
  43. package/dist/plugins/ast-utils.js +90 -2
  44. package/dist/plugins/cloudflare-dev-module-urls.d.ts +41 -0
  45. package/dist/plugins/cloudflare-dev-module-urls.js +271 -0
  46. package/dist/plugins/commonjs-esm-facade.js +2 -2
  47. package/dist/plugins/css-module-imports.js +13 -20
  48. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  49. package/dist/plugins/dynamic-preload-metadata.js +11 -11
  50. package/dist/plugins/extensionless-dynamic-import.js +2 -3
  51. package/dist/plugins/fonts.d.ts +1 -1
  52. package/dist/plugins/fonts.js +3 -3
  53. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  54. package/dist/plugins/ignore-dynamic-requests.js +5 -7
  55. package/dist/plugins/import-meta-url.js +3 -4
  56. package/dist/plugins/instrumentation-client.js +3 -2
  57. package/dist/plugins/middleware-export-validation.js +2 -3
  58. package/dist/plugins/middleware-server-only.d.ts +5 -4
  59. package/dist/plugins/middleware-server-only.js +8 -5
  60. package/dist/plugins/og-harfbuzz.js +2 -3
  61. package/dist/plugins/optimize-imports.js +3 -4
  62. package/dist/plugins/pages-node-externals.js +3 -4
  63. package/dist/plugins/remove-console.js +2 -18
  64. package/dist/plugins/require-condition-resolution.js +3 -3
  65. package/dist/plugins/require-context.js +2 -3
  66. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  67. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  68. package/dist/plugins/strip-server-exports.js +265 -102
  69. package/dist/plugins/styled-jsx.js +2 -3
  70. package/dist/plugins/transform-result.d.ts +9 -5
  71. package/dist/plugins/transitive-externals.js +1 -1
  72. package/dist/plugins/typeof-window.d.ts +2 -2
  73. package/dist/plugins/use-cache-callable.d.ts +21 -2
  74. package/dist/plugins/use-cache-callable.js +84 -35
  75. package/dist/plugins/worker-image-imports.js +30 -33
  76. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  77. package/dist/plugins/worker-server-only-imports.js +31 -0
  78. package/dist/plugins/worker-use-server.d.ts +25 -0
  79. package/dist/plugins/worker-use-server.js +153 -0
  80. package/dist/server/api-handler.d.ts +1 -0
  81. package/dist/server/api-handler.js +3 -2
  82. package/dist/server/app-action-forwarding.js +2 -2
  83. package/dist/server/app-browser-action-result.d.ts +1 -1
  84. package/dist/server/app-browser-action-result.js +9 -2
  85. package/dist/server/app-browser-entry.js +3 -3
  86. package/dist/server/app-browser-interception-context.js +1 -1
  87. package/dist/server/app-browser-navigation-controller.js +1 -1
  88. package/dist/server/app-browser-prefetch-response.js +2 -2
  89. package/dist/server/app-browser-server-action-client.js +1 -1
  90. package/dist/server/app-browser-state.js +1 -1
  91. package/dist/server/app-browser-supplemental-refresh.js +1 -1
  92. package/dist/server/app-middleware.d.ts +11 -0
  93. package/dist/server/app-middleware.js +17 -2
  94. package/dist/server/app-optimistic-routing.js +1 -1
  95. package/dist/server/app-page-cache.d.ts +1 -1
  96. package/dist/server/app-page-cache.js +1 -1
  97. package/dist/server/app-page-dispatch.d.ts +3 -1
  98. package/dist/server/app-page-dispatch.js +5 -4
  99. package/dist/server/app-page-ppr-runtime.js +1 -1
  100. package/dist/server/app-page-render-observation.js +1 -1
  101. package/dist/server/app-page-render.js +1 -1
  102. package/dist/server/app-page-request.js +53 -15
  103. package/dist/server/app-page-route-wiring.js +2 -2
  104. package/dist/server/app-pages-bridge.d.ts +1 -1
  105. package/dist/server/app-pages-bridge.js +3 -4
  106. package/dist/server/app-prerender-endpoints.js +9 -6
  107. package/dist/server/app-render-abort-error.d.ts +19 -0
  108. package/dist/server/app-render-abort-error.js +31 -1
  109. package/dist/server/app-request-stage-dispatch.js +6 -2
  110. package/dist/server/app-request-stage-independent-entry.js +29 -9
  111. package/dist/server/app-response-stage-entry.js +11 -3
  112. package/dist/server/app-route-handler-cache.d.ts +1 -0
  113. package/dist/server/app-route-handler-cache.js +1 -0
  114. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  115. package/dist/server/app-route-handler-dispatch.js +5 -2
  116. package/dist/server/app-route-handler-execution.d.ts +1 -0
  117. package/dist/server/app-route-handler-execution.js +1 -0
  118. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  119. package/dist/server/app-route-handler-runtime.js +46 -25
  120. package/dist/server/app-router-entry.js +29 -10
  121. package/dist/server/app-rsc-cache-busting.d.ts +16 -0
  122. package/dist/server/app-rsc-cache-busting.js +30 -13
  123. package/dist/server/app-rsc-errors.js +20 -16
  124. package/dist/server/app-rsc-handler.d.ts +7 -0
  125. package/dist/server/app-rsc-handler.js +89 -39
  126. package/dist/server/app-rsc-request-normalization.js +3 -3
  127. package/dist/server/app-rsc-response-stage.js +5 -3
  128. package/dist/server/app-segment-config.d.ts +2 -1
  129. package/dist/server/app-server-action-execution.d.ts +2 -1
  130. package/dist/server/app-server-action-execution.js +62 -23
  131. package/dist/server/app-ssr-entry.js +7 -5
  132. package/dist/server/app-visited-response-cache.js +2 -2
  133. package/dist/server/app-worker-stages.d.ts +5 -0
  134. package/dist/server/app-worker-stages.js +1 -1
  135. package/dist/server/cacheability-request.js +2 -2
  136. package/dist/server/client-build-files.d.ts +16 -0
  137. package/dist/server/client-build-files.js +36 -0
  138. package/dist/server/client-reuse-manifest.js +1 -1
  139. package/dist/server/config-headers.js +1 -1
  140. package/dist/server/defer-until-stream-consumed.d.ts +2 -1
  141. package/dist/server/defer-until-stream-consumed.js +3 -1
  142. package/dist/server/dev-server.js +39 -28
  143. package/dist/server/dev-stack-sourcemap.js +18 -23
  144. package/dist/server/document-initial-head.d.ts +0 -11
  145. package/dist/server/document-initial-head.js +1 -1
  146. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  147. package/dist/server/forwarded-middleware-context.js +22 -0
  148. package/dist/server/headers.d.ts +4 -1
  149. package/dist/server/headers.js +4 -1
  150. package/dist/server/http-conditional.d.ts +11 -0
  151. package/dist/server/http-conditional.js +10 -1
  152. package/dist/server/http-error-responses.d.ts +1 -1
  153. package/dist/server/image-optimization.d.ts +38 -0
  154. package/dist/server/image-optimization.js +94 -4
  155. package/dist/server/isr-cache.d.ts +10 -0
  156. package/dist/server/isr-cache.js +41 -6
  157. package/dist/server/metadata-route-response.js +11 -10
  158. package/dist/server/middleware-matcher.d.ts +2 -0
  159. package/dist/server/middleware-matcher.js +23 -2
  160. package/dist/server/middleware-path-to-regexp.js +1 -1
  161. package/dist/server/middleware-runtime.d.ts +13 -0
  162. package/dist/server/middleware-runtime.js +38 -25
  163. package/dist/server/middleware.d.ts +1 -1
  164. package/dist/server/middleware.js +3 -1
  165. package/dist/server/multi-stage.d.ts +11 -0
  166. package/dist/server/navigation-planner.js +1 -1
  167. package/dist/server/normalize-path.d.ts +0 -13
  168. package/dist/server/normalize-path.js +1 -34
  169. package/dist/server/open-redirect.d.ts +11 -4
  170. package/dist/server/open-redirect.js +11 -4
  171. package/dist/server/pages-api-route.d.ts +3 -2
  172. package/dist/server/pages-api-route.js +6 -5
  173. package/dist/server/pages-cache-tag.d.ts +12 -0
  174. package/dist/server/pages-cache-tag.js +19 -0
  175. package/dist/server/pages-client-assets.d.ts +2 -0
  176. package/dist/server/pages-data-route.d.ts +10 -0
  177. package/dist/server/pages-data-route.js +21 -3
  178. package/dist/server/pages-document-asset-props.d.ts +5 -1
  179. package/dist/server/pages-document-asset-props.js +13 -5
  180. package/dist/server/pages-document-splice.d.ts +19 -0
  181. package/dist/server/pages-document-splice.js +55 -0
  182. package/dist/server/pages-i18n.d.ts +12 -1
  183. package/dist/server/pages-i18n.js +18 -4
  184. package/dist/server/pages-node-compat.d.ts +16 -5
  185. package/dist/server/pages-node-compat.js +20 -12
  186. package/dist/server/pages-page-data.d.ts +32 -24
  187. package/dist/server/pages-page-data.js +51 -121
  188. package/dist/server/pages-page-handler.d.ts +0 -6
  189. package/dist/server/pages-page-handler.js +189 -127
  190. package/dist/server/pages-page-response.d.ts +12 -6
  191. package/dist/server/pages-page-response.js +42 -35
  192. package/dist/server/pages-request-pipeline.d.ts +10 -2
  193. package/dist/server/pages-request-pipeline.js +41 -22
  194. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  195. package/dist/server/pages-request-stage-entry.js +75 -16
  196. package/dist/server/pages-response-stage-entry.js +4 -0
  197. package/dist/server/pages-revalidate.js +3 -3
  198. package/dist/server/pages-router-entry.js +5 -4
  199. package/dist/server/prerender-manifest.d.ts +3 -3
  200. package/dist/server/prerender-manifest.js +4 -3
  201. package/dist/server/prerender-work-unit-setup.js +4 -0
  202. package/dist/server/prod-server.d.ts +12 -0
  203. package/dist/server/prod-server.js +204 -74
  204. package/dist/server/proper-error.d.ts +4 -0
  205. package/dist/server/proper-error.js +31 -0
  206. package/dist/server/request-pipeline.d.ts +4 -1
  207. package/dist/server/request-pipeline.js +3 -2
  208. package/dist/server/revalidation-request.d.ts +1 -0
  209. package/dist/server/revalidation-request.js +1 -1
  210. package/dist/server/rsc-stream-hints.d.ts +1 -1
  211. package/dist/server/rsc-stream-hints.js +52 -5
  212. package/dist/server/seed-cache.d.ts +1 -0
  213. package/dist/server/seed-cache.js +5 -4
  214. package/dist/server/server-reference-loader.d.ts +4 -0
  215. package/dist/server/server-reference-loader.js +29 -0
  216. package/dist/server/socket-error-backstop.d.ts +9 -1
  217. package/dist/server/socket-error-backstop.js +46 -17
  218. package/dist/server/static-file-signal.d.ts +15 -4
  219. package/dist/server/static-file-signal.js +46 -12
  220. package/dist/server/worker-image-optimization.d.ts +34 -0
  221. package/dist/server/worker-image-optimization.js +99 -0
  222. package/dist/server/worker-stages.d.ts +1 -1
  223. package/dist/server/worker-stages.js +2 -2
  224. package/dist/server/worker-utils.d.ts +8 -3
  225. package/dist/server/worker-utils.js +12 -7
  226. package/dist/shims/app-router-scroll.js +4 -1
  227. package/dist/shims/app-router-tree-context.d.ts +5 -0
  228. package/dist/shims/app-router-tree-context.js +27 -0
  229. package/dist/shims/cache-callable-runtime.d.ts +4 -2
  230. package/dist/shims/cache-callable-runtime.js +21 -12
  231. package/dist/shims/cache-handler.js +1 -9
  232. package/dist/shims/cache-runtime.d.ts +6 -1
  233. package/dist/shims/cache-runtime.js +2 -1
  234. package/dist/shims/cache.d.ts +2 -2
  235. package/dist/shims/cache.js +40 -13
  236. package/dist/shims/cdn-cache.d.ts +8 -0
  237. package/dist/shims/cdn-cache.js +17 -1
  238. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  239. package/dist/shims/disable-smooth-scroll.js +36 -0
  240. package/dist/shims/document-placeholders.d.ts +19 -0
  241. package/dist/shims/document-placeholders.js +45 -0
  242. package/dist/shims/document.js +21 -5
  243. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  244. package/dist/shims/dynamic-preload-chunks.js +13 -9
  245. package/dist/shims/dynamic.d.ts +1 -1
  246. package/dist/shims/dynamic.js +60 -8
  247. package/dist/shims/fetch-cache-state.d.ts +66 -0
  248. package/dist/shims/fetch-cache-state.js +92 -0
  249. package/dist/shims/fetch-cache.d.ts +3 -67
  250. package/dist/shims/fetch-cache.js +160 -125
  251. package/dist/shims/head.d.ts +10 -6
  252. package/dist/shims/head.js +16 -14
  253. package/dist/shims/headers.js +2 -2
  254. package/dist/shims/image.js +2 -1
  255. package/dist/shims/internal/hybrid-client-route-owner-direct.js +1 -1
  256. package/dist/shims/internal/hybrid-client-route-owner.js +1 -0
  257. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  258. package/dist/shims/internal/work-store-scope.js +24 -0
  259. package/dist/shims/link-prefetch.js +5 -4
  260. package/dist/shims/link.js +2 -2
  261. package/dist/shims/navigation.js +34 -9
  262. package/dist/shims/request-context.d.ts +2 -0
  263. package/dist/shims/request-state-types.d.ts +2 -1
  264. package/dist/shims/router.js +21 -32
  265. package/dist/shims/server.d.ts +27 -4
  266. package/dist/shims/server.js +65 -9
  267. package/dist/shims/unified-request-context.d.ts +1 -1
  268. package/dist/shims/url-utils.d.ts +9 -0
  269. package/dist/shims/url-utils.js +15 -4
  270. package/dist/utils/client-runtime-metadata.js +1 -1
  271. package/dist/utils/domain-locale.js +1 -1
  272. package/dist/utils/hash.d.ts +7 -0
  273. package/dist/utils/hash.js +11 -1
  274. package/dist/utils/package-name.d.ts +6 -0
  275. package/dist/utils/package-name.js +12 -1
  276. package/dist/utils/plugin-rsc-version.d.ts +6 -0
  277. package/dist/utils/plugin-rsc-version.js +41 -0
  278. package/dist/utils/prerender-output-paths.js +15 -4
  279. package/dist/utils/public-file-path.d.ts +10 -2
  280. package/dist/utils/public-file-path.js +13 -9
  281. package/dist/utils/public-routes.js +2 -2
  282. package/dist/utils/react-version.d.ts +10 -0
  283. package/dist/utils/react-version.js +39 -16
  284. package/package.json +11 -7
  285. package/dist/build/nitro-route-rules.d.ts +0 -47
  286. package/dist/build/nitro-route-rules.js +0 -79
  287. package/dist/plugins/cache-flight-codec.d.ts +0 -19
  288. package/dist/plugins/cache-flight-codec.js +0 -68
@@ -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,8 @@ 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 { attachRequestCfMetadata } from "../server/request-pipeline.js";
7
+ import { substituteDestinationParams, substituteRedirectDestinationQuery } from "./destination-params.js";
6
8
  //#region src/config/config-matchers.ts
7
9
  /**
8
10
  * Cache for compiled regex patterns in matchConfigPattern.
@@ -44,14 +46,6 @@ const _compiledHeaderSourceCache = /* @__PURE__ */ new Map();
44
46
  */
45
47
  const _compiledConditionCache = /* @__PURE__ */ new Map();
46
48
  /**
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
49
  * Generic helper for the regex compilation caches above.
56
50
  *
57
51
  * Each cache stores the compiled artifact (or `null` when safeRegExp rejected
@@ -303,13 +297,17 @@ function applyMiddlewareRequestHeaders(middlewareHeaders, request) {
303
297
  const nextHeaders = buildRequestHeadersFromMiddlewareResponse(request.headers, middlewareHeaders);
304
298
  const unconsumedRequestHeaders = getUnconsumedMiddlewareRequestHeaders(middlewareHeaders);
305
299
  for (const key of Object.keys(middlewareHeaders)) if (key.startsWith("x-middleware-") && key !== "x-middleware-cache" && !unconsumedRequestHeaders.has(key)) delete middlewareHeaders[key];
306
- if (nextHeaders) request = new Request(request.url, {
307
- method: request.method,
308
- headers: nextHeaders,
309
- body: request.body,
310
- signal: request.signal,
311
- duplex: request.body ? "half" : void 0
312
- });
300
+ if (nextHeaders) {
301
+ const source = request;
302
+ request = new Request(request.url, {
303
+ method: request.method,
304
+ headers: nextHeaders,
305
+ body: request.body,
306
+ signal: request.signal,
307
+ duplex: request.body ? "half" : void 0
308
+ });
309
+ attachRequestCfMetadata(request, source);
310
+ }
313
311
  return {
314
312
  request,
315
313
  postMwReqCtx: requestContextFromRequest(request)
@@ -561,13 +559,10 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
561
559
  onRuleSourceMatch?.(redirect);
562
560
  const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
563
561
  if (!conditionParams) continue;
564
- localeMatch = {
565
- destination: substituteAndSanitizeDestination(redirect.destination, {
566
- [entry.paramName]: "",
567
- ...conditionParams
568
- }),
569
- permanent: redirect.permanent
570
- };
562
+ localeMatch = resolveRedirectMatch(redirect, {
563
+ [entry.paramName]: "",
564
+ ...conditionParams
565
+ });
571
566
  localeMatchIndex = entry.originalIndex;
572
567
  break;
573
568
  }
@@ -584,13 +579,10 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
584
579
  onRuleSourceMatch?.(redirect);
585
580
  const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
586
581
  if (!conditionParams) continue;
587
- localeMatch = {
588
- destination: substituteAndSanitizeDestination(redirect.destination, {
589
- [entry.paramName]: localePart,
590
- ...conditionParams
591
- }),
592
- permanent: redirect.permanent
593
- };
582
+ localeMatch = resolveRedirectMatch(redirect, {
583
+ [entry.paramName]: localePart,
584
+ ...conditionParams
585
+ });
594
586
  localeMatchIndex = entry.originalIndex;
595
587
  break;
596
588
  }
@@ -604,13 +596,10 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
604
596
  onRuleSourceMatch?.(redirect);
605
597
  const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
606
598
  if (!conditionParams) continue;
607
- return {
608
- destination: substituteAndSanitizeDestination(redirect.destination, {
609
- ...params,
610
- ...conditionParams
611
- }),
612
- permanent: redirect.permanent
613
- };
599
+ return resolveRedirectMatch(redirect, {
600
+ ...params,
601
+ ...conditionParams
602
+ });
614
603
  }
615
604
  }
616
605
  return localeMatch;
@@ -636,12 +625,24 @@ function matchRewrite(pathname, rewrites, ctx, basePathState = _BASEPATH_DEFAULT
636
625
  ...params,
637
626
  ...conditionParams
638
627
  };
639
- return substituteAndSanitizeRewriteDestination(rewrite.destination, rewriteParams);
628
+ return substituteAndSanitizeRewriteDestination(serializeRewriteDestinationTemplate(rewrite.destination), rewriteParams);
640
629
  }
641
630
  }
642
631
  return null;
643
632
  }
644
633
  /**
634
+ * Next.js prepareDestination URL-parses the destination template before it
635
+ * substitutes params, so a literal `é` routes as `%C3%A9` while substituted
636
+ * values stay verbatim: a captured `../admin` is never resolved to `/admin`.
637
+ *
638
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
639
+ */
640
+ function serializeRewriteDestinationTemplate(destination) {
641
+ if (!destination.startsWith("/") || destination.startsWith("//")) return destination;
642
+ const parsed = new URL(destination, "http://n");
643
+ return parsed.pathname + parsed.search + parsed.hash;
644
+ }
645
+ /**
645
646
  * Check whether a rewrite source can match a pathname without evaluating its
646
647
  * request-dependent `has` / `missing` conditions.
647
648
  *
@@ -654,47 +655,20 @@ function matchesRewriteSource(pathname, rewrite, basePathState = _BASEPATH_DEFAU
654
655
  return shouldEvaluateRule(rewrite.basePath, basePathState) && matchConfigPattern(pathname, rewrite.source) !== null;
655
656
  }
656
657
  /**
657
- * Substitute all matched route params into a redirect/rewrite destination.
658
- *
659
- * Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
660
- * (`:path*`, `:path+`) in a single pass. Unknown params are left intact.
661
- */
662
- function substituteDestinationParams(destination, params) {
663
- const keys = Object.keys(params);
664
- if (keys.length === 0) return destination;
665
- const sortedKeys = [...keys].sort((a, b) => b.length - a.length);
666
- const cacheKey = sortedKeys.join("\0");
667
- let paramRe = _compiledDestinationParamCache.get(cacheKey);
668
- if (!paramRe) {
669
- const paramAlternation = sortedKeys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
670
- paramRe = new RegExp(`:(${paramAlternation})([+*])?(?![A-Za-z0-9_])`, "g");
671
- _compiledDestinationParamCache.set(cacheKey, paramRe);
672
- }
673
- const replaceParams = (value, encodeParam) => value.replace(paramRe, (_token, key) => encodeParam(params[key]));
674
- const hashIndex = destination.indexOf("#");
675
- const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
676
- const hash = hashIndex === -1 ? "" : destination.slice(hashIndex);
677
- const queryIndex = beforeHash.indexOf("?");
678
- if (queryIndex !== -1) {
679
- const beforeQuery = beforeHash.slice(0, queryIndex);
680
- const query = beforeHash.slice(queryIndex + 1);
681
- return `${replaceParams(beforeQuery, (value) => value)}?${replaceParams(query, encodeDestinationQueryParamValue)}${replaceParams(hash, (value) => value)}`;
682
- }
683
- return replaceParams(destination, (value) => value);
684
- }
685
- function encodeDestinationQueryParamValue(value) {
686
- const params = new URLSearchParams();
687
- params.set("", value);
688
- return params.toString().slice(1);
689
- }
690
- /**
691
658
  * Substitute params into a redirect/rewrite destination and sanitize the
692
659
  * result. Used by every redirect/rewrite branch — the substitution can
693
660
  * introduce protocol-relative URLs (e.g. `//evil.com` from a decoded `%2F`
694
661
  * in a catch-all param), which sanitizeDestination collapses.
695
662
  */
696
- function substituteAndSanitizeDestination(destination, params) {
697
- return sanitizeDestination(substituteDestinationParams(destination, params));
663
+ function substituteAndSanitizeDestination(destination, params, kind) {
664
+ return sanitizeDestination(substituteDestinationParams(destination, params, kind));
665
+ }
666
+ function resolveRedirectMatch(redirect, params) {
667
+ return {
668
+ destination: substituteAndSanitizeDestination(redirect.destination, params, "redirect"),
669
+ permanent: redirect.permanent,
670
+ destinationQuery: substituteRedirectDestinationQuery(redirect.destination, params)
671
+ };
698
672
  }
699
673
  /**
700
674
  * Match Next.js's rewrite-specific prepareDestination behavior: source params
@@ -704,7 +678,7 @@ function substituteAndSanitizeDestination(destination, params) {
704
678
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
705
679
  */
706
680
  function substituteAndSanitizeRewriteDestination(destination, params) {
707
- const rewritten = substituteAndSanitizeDestination(destination, params);
681
+ const rewritten = substituteAndSanitizeDestination(destination, params, "rewrite");
708
682
  if (!shouldAppendRewriteParamsToQuery(destination, params)) return rewritten;
709
683
  const existingQueryKeys = getDestinationQueryKeys(destination);
710
684
  const paramsToAppend = [];
@@ -771,11 +745,12 @@ function appendQueryParams(url, params) {
771
745
  * users off-site.
772
746
  *
773
747
  * This function collapses any leading double (or more) slashes to a single
774
- * slash for non-external (relative) destinations.
748
+ * slash for non-external (relative) destinations, including tab/newline and
749
+ * leading C0 control characters that browsers ignore while parsing.
775
750
  */
776
751
  function sanitizeDestination(dest) {
777
752
  if (dest.startsWith("http://") || dest.startsWith("https://")) return dest;
778
- dest = dest.replace(/^[\\/]+/, "/");
753
+ dest = dest.replace(/^[\u0000-\u0020]*(?:[\\/][\t\n\r]*)+/, "/");
779
754
  return dest;
780
755
  }
781
756
  /**
@@ -795,13 +770,20 @@ function sanitizeDestination(dest) {
795
770
  * still treated as an RSC fetch. Dropping it breaks RSC fetch semantics
796
771
  * (issue #1529).
797
772
  *
798
- * Destination query params win — a request param is only carried over when
799
- * the destination does not already specify that key. Mirrors the merge
800
- * semantics in `proxyExternalRequest`. External destinations are returned
801
- * untouched (a config redirect to another origin should not leak the
802
- * original request's query).
773
+ * Ported from Next.js's `{ ...requestQuery, ...destinationQuery }` merge and
774
+ * `stringifyQuery`: request keys keep their order (integer-like keys first, as
775
+ * in a JS object), a destination key overrides the request value in place,
776
+ * and destination-only keys follow. Request keys and values are re-encoded;
777
+ * destination query text is emitted verbatim, except that, like Next.js,
778
+ * destination strings equal to a request key or value are re-encoded too.
779
+ * Pass the match's
780
+ * `destinationQuery` so substituted params keep their value boundaries;
781
+ * without it the destination's own query is split. External destinations are returned untouched (a config redirect to another
782
+ * origin should not leak the original request's query).
783
+ *
784
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
803
785
  */
804
- function preserveRedirectDestinationQuery(destination, requestSearch) {
786
+ function preserveRedirectDestinationQuery(destination, requestSearch, destinationQuery) {
805
787
  if (requestSearch === "" || requestSearch === "?" || isExternalUrl(destination)) return destination;
806
788
  const requestParams = new URLSearchParams(requestSearch);
807
789
  if ([...requestParams.keys()].length === 0) return destination;
@@ -810,13 +792,29 @@ function preserveRedirectDestinationQuery(destination, requestSearch) {
810
792
  const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
811
793
  const queryIndex = beforeHash.indexOf("?");
812
794
  const pathPart = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
813
- const destQuery = queryIndex === -1 ? "" : beforeHash.slice(queryIndex + 1);
814
- const merged = new URLSearchParams(destQuery);
815
- const destKeys = new Set(merged.keys());
816
- for (const [key, value] of requestParams) if (!destKeys.has(key)) merged.append(key, value);
817
- const mergedQuery = merged.toString();
795
+ const requestStrings = /* @__PURE__ */ new Set();
796
+ const requestParts = Object.create(null);
797
+ for (const [key, value] of requestParams) {
798
+ requestStrings.add(key);
799
+ requestStrings.add(value);
800
+ (requestParts[key] ??= []).push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
801
+ }
802
+ const encode = (text) => requestStrings.has(text) ? encodeURIComponent(text) : text;
803
+ const destinationParts = Object.create(null);
804
+ for (const [key, keyText, valueText] of destinationQuery ?? substituteRedirectDestinationQuery(destination, {})) (destinationParts[key] ??= []).push(valueText === null ? encode(keyText) : `${encode(keyText)}=${encode(valueText)}`);
805
+ const mergedQuery = Object.values({
806
+ ...requestParts,
807
+ ...destinationParts
808
+ }).flat().join("&");
818
809
  return mergedQuery === "" ? `${pathPart}${hash}` : `${pathPart}?${mergedQuery}${hash}`;
819
810
  }
811
+ function parseExternalRewriteUrl(externalUrl) {
812
+ try {
813
+ return new URL(externalUrl);
814
+ } catch {
815
+ return null;
816
+ }
817
+ }
820
818
  /**
821
819
  * Proxy an incoming request to an external URL and return the upstream response.
822
820
  *
@@ -828,7 +826,11 @@ function preserveRedirectDestinationQuery(destination, requestSearch) {
828
826
  */
829
827
  async function proxyExternalRequest(request, externalUrl) {
830
828
  const originalUrl = new URL(request.url);
831
- const targetUrl = new URL(externalUrl);
829
+ const targetUrl = parseExternalRewriteUrl(externalUrl);
830
+ if (!targetUrl) {
831
+ console.error("[vinext] Invalid external rewrite destination:", externalUrl);
832
+ return new Response("Internal Server Error", { status: 500 });
833
+ }
832
834
  const destinationKeys = new Set(targetUrl.searchParams.keys());
833
835
  for (const [key, value] of originalUrl.searchParams) if (!destinationKeys.has(key)) targetUrl.searchParams.append(key, value);
834
836
  const headers = new Headers(request.headers);
@@ -0,0 +1,47 @@
1
+ //#region src/config/destination-params.d.ts
2
+ /**
3
+ * Destination param substitution for next.config.js redirects and rewrites.
4
+ *
5
+ * Kept free of server-only imports so the Pages Router client can apply simple
6
+ * beforeFiles rewrites with exactly the same substitution as the server.
7
+ */
8
+ /**
9
+ * Substitute all matched route params into a redirect/rewrite destination.
10
+ *
11
+ * Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
12
+ * (`:path*`, `:path+`) in a single pass. Unknown params are left intact.
13
+ *
14
+ * Query substitution follows how Next.js consumes each destination kind:
15
+ *
16
+ * - A rewrite's query is parsed into an object before substitution, so the
17
+ * target sees each param verbatim as one value. vinext encodes the whole
18
+ * value so it round-trips back to that verbatim string.
19
+ * - A redirect's query is emitted as Location text with params inserted
20
+ * verbatim, so an already percent-encoded capture is decoded once by the
21
+ * client. vinext inserts redirect params verbatim too.
22
+ *
23
+ * A rewrite's absolute destination is proxied server-side, so params in its
24
+ * authority (`https://:tenant.example.com/`) are percent-encoded, keeping the
25
+ * `%XX` escapes a raw path capture already carries. A decoded `has` capture
26
+ * carrying `#`, `/`, `?`, `@` or `:` then makes the host invalid instead of
27
+ * moving the proxy to another host. Next.js substitutes the hostname verbatim
28
+ * and can be retargeted this way; vinext is stricter.
29
+ *
30
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
31
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
32
+ */
33
+ export declare function substituteDestinationParams(destination: string, params: Record<string, string>, kind: "redirect" | "rewrite"): string;
34
+ /**
35
+ * Split a redirect destination's query into parts before substituting params,
36
+ * as Next.js's prepareDestination parses the template query first. Each part
37
+ * is `[key, keyText, valueText]`: the texts are the part's template text with
38
+ * params substituted verbatim (`valueText` is `null` without an `=`), and
39
+ * `key` is the decoded key, used only to match request params. Keeping these
40
+ * boundaries means a param containing `&` stays one value when the request
41
+ * query is merged in.
42
+ *
43
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
44
+ */
45
+ export declare function substituteRedirectDestinationQuery(destination: string, params: Record<string, string>): RedirectDestinationQueryPart[];
46
+ export type RedirectDestinationQueryPart = [key: string, keyText: string, valueText: string | null];
47
+ //#endregion
@@ -0,0 +1,116 @@
1
+ //#region src/config/destination-params.ts
2
+ /**
3
+ * Destination param substitution for next.config.js redirects and rewrites.
4
+ *
5
+ * Kept free of server-only imports so the Pages Router client can apply simple
6
+ * beforeFiles rewrites with exactly the same substitution as the server.
7
+ */
8
+ /**
9
+ * Cache for destination substitution regexes in substituteDestinationParams.
10
+ *
11
+ * The regex depends only on the set of param keys captured from the matched
12
+ * source pattern. Caching by sorted key list avoids recompiling a new RegExp
13
+ * for repeated redirect/rewrite calls that use the same param shape.
14
+ */
15
+ const _compiledDestinationParamCache = /* @__PURE__ */ new Map();
16
+ /**
17
+ * Substitute all matched route params into a redirect/rewrite destination.
18
+ *
19
+ * Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
20
+ * (`:path*`, `:path+`) in a single pass. Unknown params are left intact.
21
+ *
22
+ * Query substitution follows how Next.js consumes each destination kind:
23
+ *
24
+ * - A rewrite's query is parsed into an object before substitution, so the
25
+ * target sees each param verbatim as one value. vinext encodes the whole
26
+ * value so it round-trips back to that verbatim string.
27
+ * - A redirect's query is emitted as Location text with params inserted
28
+ * verbatim, so an already percent-encoded capture is decoded once by the
29
+ * client. vinext inserts redirect params verbatim too.
30
+ *
31
+ * A rewrite's absolute destination is proxied server-side, so params in its
32
+ * authority (`https://:tenant.example.com/`) are percent-encoded, keeping the
33
+ * `%XX` escapes a raw path capture already carries. A decoded `has` capture
34
+ * carrying `#`, `/`, `?`, `@` or `:` then makes the host invalid instead of
35
+ * moving the proxy to another host. Next.js substitutes the hostname verbatim
36
+ * and can be retargeted this way; vinext is stricter.
37
+ *
38
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
39
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
40
+ */
41
+ function substituteDestinationParams(destination, params, kind) {
42
+ const paramRe = getDestinationParamRegex(params);
43
+ if (!paramRe) return destination;
44
+ const replaceParams = (value, encodeParam) => value.replace(paramRe, (_token, key) => encodeParam(params[key]));
45
+ if (kind === "redirect") return replaceParams(destination, (value) => value);
46
+ const hashIndex = destination.indexOf("#");
47
+ const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
48
+ const hash = hashIndex === -1 ? "" : destination.slice(hashIndex);
49
+ const queryIndex = beforeHash.indexOf("?");
50
+ const beforeQuery = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
51
+ const authorityEnd = getAbsoluteUrlAuthorityEnd(beforeQuery.replace(paramRe, (token) => "x".repeat(token.length)));
52
+ return `${`${replaceParams(beforeQuery.slice(0, authorityEnd), encodeAuthorityParam)}${replaceParams(beforeQuery.slice(authorityEnd), (value) => value)}`}${queryIndex === -1 ? "" : `?${replaceParams(beforeHash.slice(queryIndex + 1), encodeRewriteQueryParamValue)}`}${replaceParams(hash, (value) => value)}`;
53
+ }
54
+ function getAbsoluteUrlAuthorityEnd(value) {
55
+ const scheme = /^[a-z][a-z0-9+.-]*:[/\\\t\n\r]*/i.exec(value);
56
+ if (!scheme && /^[/\\]/.test(value)) return 0;
57
+ const start = scheme ? scheme[0].length : 0;
58
+ const pathStart = value.slice(start).search(/[/\\]/);
59
+ return pathStart === -1 ? value.length : start + pathStart;
60
+ }
61
+ /**
62
+ * Split a redirect destination's query into parts before substituting params,
63
+ * as Next.js's prepareDestination parses the template query first. Each part
64
+ * is `[key, keyText, valueText]`: the texts are the part's template text with
65
+ * params substituted verbatim (`valueText` is `null` without an `=`), and
66
+ * `key` is the decoded key, used only to match request params. Keeping these
67
+ * boundaries means a param containing `&` stays one value when the request
68
+ * query is merged in.
69
+ *
70
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
71
+ */
72
+ function substituteRedirectDestinationQuery(destination, params) {
73
+ const hashIndex = destination.indexOf("#");
74
+ const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
75
+ const queryIndex = beforeHash.indexOf("?");
76
+ if (queryIndex === -1) return [];
77
+ const paramRe = getDestinationParamRegex(params);
78
+ const substitute = (value) => paramRe ? value.replace(paramRe, (_token, key) => params[key]) : value;
79
+ return beforeHash.slice(queryIndex + 1).split("&").filter(Boolean).map((part) => {
80
+ const equalsIndex = part.indexOf("=");
81
+ const keyText = substitute(equalsIndex === -1 ? part : part.slice(0, equalsIndex));
82
+ const valueText = equalsIndex === -1 ? null : substitute(part.slice(equalsIndex + 1));
83
+ return [
84
+ decodeQueryKey(keyText),
85
+ keyText,
86
+ valueText
87
+ ];
88
+ });
89
+ }
90
+ function decodeQueryKey(keyText) {
91
+ const [entry] = new URLSearchParams(keyText.replace(/[&=]/g, (char) => encodeURIComponent(char)));
92
+ return entry?.[0] ?? "";
93
+ }
94
+ function getDestinationParamRegex(params) {
95
+ const keys = Object.keys(params);
96
+ if (keys.length === 0) return null;
97
+ const sortedKeys = [...keys].sort((a, b) => b.length - a.length);
98
+ const cacheKey = sortedKeys.join("\0");
99
+ let paramRe = _compiledDestinationParamCache.get(cacheKey);
100
+ if (!paramRe) {
101
+ const paramAlternation = sortedKeys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
102
+ paramRe = new RegExp(`:(${paramAlternation})([+*])?(?![A-Za-z0-9_])`, "g");
103
+ _compiledDestinationParamCache.set(cacheKey, paramRe);
104
+ }
105
+ return paramRe;
106
+ }
107
+ function encodeAuthorityParam(value) {
108
+ return value.replace(/%(?![0-9a-f]{2})|[^A-Za-z0-9._~%-]/giu, encodeURIComponent);
109
+ }
110
+ function encodeRewriteQueryParamValue(value) {
111
+ const params = new URLSearchParams();
112
+ params.set("", value);
113
+ return params.toString().slice(1);
114
+ }
115
+ //#endregion
116
+ export { substituteDestinationParams, substituteRedirectDestinationQuery };
@@ -111,6 +111,12 @@ export type NextConfig = {
111
111
  crossOrigin?: "anonymous" | "use-credentials";
112
112
  /** Whether to add trailing slashes */
113
113
  trailingSlash?: boolean;
114
+ /**
115
+ * Skip the automatic trailing-slash redirects and keep `<Link>`/router
116
+ * hrefs exactly as written.
117
+ * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/skipTrailingSlashRedirect
118
+ */
119
+ skipTrailingSlashRedirect?: boolean;
114
120
  /**
115
121
  * Compress responses from the Node production server. Defaults to true.
116
122
  * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/compress
@@ -317,6 +323,7 @@ export type ResolvedNextConfig = {
317
323
  */
318
324
  assetPrefix: string;
319
325
  trailingSlash: boolean;
326
+ skipTrailingSlashRedirect: boolean;
320
327
  /** Whether the Node production server compresses responses (`compress`, default true). */
321
328
  compress: boolean;
322
329
  skipProxyUrlNormalize: boolean;
@@ -373,6 +373,7 @@ function warnDeprecatedConfigOptions(config, root) {
373
373
  ["experimental.instrumentationHook", `\`experimental.instrumentationHook\` is no longer needed, because \`instrumentation.js\` is available by default. You can remove it from ${configFileName}.`]
374
374
  ];
375
375
  for (const [propertyPath, warning] of warnings) if (hasConfigProperty(config, propertyPath)) warnConfigOnce(warning);
376
+ if (readOptionalRecord(config.experimental)?.skipTrailingSlashRedirect != null) warnConfigOnce(`\`experimental.skipTrailingSlashRedirect\` has been moved to \`skipTrailingSlashRedirect\`. Please update your ${configFileName} file accordingly.`);
376
377
  }
377
378
  async function resolveNextConfigInput(config, phase = PHASE_DEVELOPMENT_SERVER) {
378
379
  return await resolveConfigValue(config, phase);
@@ -761,6 +762,7 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
761
762
  basePath: "",
762
763
  assetPrefix: "",
763
764
  trailingSlash: false,
765
+ skipTrailingSlashRedirect: false,
764
766
  compress: true,
765
767
  skipProxyUrlNormalize: false,
766
768
  typescript: {},
@@ -947,6 +949,7 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
947
949
  basePath: config.basePath ?? "",
948
950
  assetPrefix: normalizeAssetPrefix(config.assetPrefix),
949
951
  trailingSlash: config.trailingSlash ?? false,
952
+ skipTrailingSlashRedirect: (experimental?.skipTrailingSlashRedirect ?? config.skipTrailingSlashRedirect) === true,
950
953
  compress: config.compress !== false,
951
954
  skipProxyUrlNormalize: config.skipProxyUrlNormalize ?? config.skipMiddlewareUrlNormalize ?? false,
952
955
  typescript: typeof config.typescript?.tsconfigPath === "string" ? { tsconfigPath: config.typescript.tsconfigPath } : {},
@@ -93,6 +93,10 @@ type AppRouterConfig = {
93
93
  draftModeSecret?: string;
94
94
  /** Server-only token used to authorize build-time prerender endpoints. */
95
95
  prerenderSecret?: string;
96
+ /** next.config `skipTrailingSlashRedirect`. */
97
+ skipTrailingSlashRedirect?: boolean;
98
+ /** Dev-only key authenticating the hybrid Pages handler's forwarded middleware context. */
99
+ devForwardedMiddlewareContextKey?: string;
96
100
  };
97
101
  /** Generate the module-free App request stage used by multi-stage Worker outputs. */
98
102
  export declare function generateAppRequestRscEntry(appDir: string, routes: AppRoute[], middlewarePath?: string | null, metadataRoutes?: MetadataFileRoute[], _globalErrorPath?: string | null, basePath?: string, trailingSlash?: boolean, config?: AppRouterConfig, instrumentationPath?: string | null): string;