vinext 0.1.1 → 0.1.3

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 (266) hide show
  1. package/README.md +2 -5
  2. package/dist/build/client-build-config.d.ts +7 -1
  3. package/dist/build/client-build-config.js +9 -1
  4. package/dist/build/prerender.d.ts +9 -1
  5. package/dist/build/prerender.js +41 -12
  6. package/dist/build/run-prerender.d.ts +10 -2
  7. package/dist/build/run-prerender.js +15 -1
  8. package/dist/check.js +4 -3
  9. package/dist/client/app-nav-failure-handler.d.ts +8 -0
  10. package/dist/client/app-nav-failure-handler.js +44 -0
  11. package/dist/client/navigation-runtime.d.ts +3 -2
  12. package/dist/client/vinext-next-data.d.ts +18 -1
  13. package/dist/client/window-next.d.ts +8 -5
  14. package/dist/client/window-next.js +12 -1
  15. package/dist/cloudflare/src/cache/cdn-adapter.runtime.js +6 -1
  16. package/dist/config/config-matchers.d.ts +11 -4
  17. package/dist/config/config-matchers.js +88 -16
  18. package/dist/config/next-config.d.ts +59 -4
  19. package/dist/config/next-config.js +149 -48
  20. package/dist/deploy.d.ts +30 -11
  21. package/dist/deploy.js +189 -101
  22. package/dist/entries/app-browser-entry.d.ts +9 -3
  23. package/dist/entries/app-browser-entry.js +21 -3
  24. package/dist/entries/app-rsc-entry.d.ts +2 -0
  25. package/dist/entries/app-rsc-entry.js +71 -6
  26. package/dist/entries/app-rsc-manifest.js +2 -0
  27. package/dist/entries/app-ssr-entry.js +1 -1
  28. package/dist/entries/pages-client-entry.js +54 -9
  29. package/dist/entries/pages-server-entry.js +48 -11
  30. package/dist/index.d.ts +0 -2
  31. package/dist/index.js +285 -139
  32. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  33. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  34. package/dist/plugins/extensionless-dynamic-import.d.ts +6 -0
  35. package/dist/plugins/extensionless-dynamic-import.js +152 -0
  36. package/dist/plugins/og-assets.js +2 -2
  37. package/dist/plugins/optimize-imports.d.ts +10 -5
  38. package/dist/plugins/optimize-imports.js +27 -21
  39. package/dist/plugins/postcss.js +7 -7
  40. package/dist/plugins/sass.d.ts +53 -24
  41. package/dist/plugins/sass.js +249 -1
  42. package/dist/plugins/typeof-window.d.ts +14 -0
  43. package/dist/plugins/typeof-window.js +150 -0
  44. package/dist/plugins/wasm-module-import.d.ts +15 -0
  45. package/dist/plugins/wasm-module-import.js +50 -0
  46. package/dist/routing/app-route-graph.d.ts +25 -2
  47. package/dist/routing/app-route-graph.js +91 -22
  48. package/dist/routing/file-matcher.d.ts +10 -1
  49. package/dist/routing/file-matcher.js +23 -2
  50. package/dist/routing/pages-router.js +3 -3
  51. package/dist/routing/utils.d.ts +35 -6
  52. package/dist/routing/utils.js +59 -7
  53. package/dist/server/api-handler.d.ts +6 -1
  54. package/dist/server/api-handler.js +21 -15
  55. package/dist/server/app-browser-action-result.d.ts +19 -6
  56. package/dist/server/app-browser-action-result.js +19 -10
  57. package/dist/server/app-browser-entry.js +269 -297
  58. package/dist/server/app-browser-error.d.ts +10 -3
  59. package/dist/server/app-browser-error.js +47 -6
  60. package/dist/server/app-browser-history-controller.d.ts +104 -0
  61. package/dist/server/app-browser-history-controller.js +210 -0
  62. package/dist/server/app-browser-hydration.d.ts +2 -0
  63. package/dist/server/app-browser-hydration.js +1 -0
  64. package/dist/server/app-browser-navigation-controller.d.ts +7 -4
  65. package/dist/server/app-browser-navigation-controller.js +33 -9
  66. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  67. package/dist/server/app-browser-rsc-redirect.js +30 -8
  68. package/dist/server/app-browser-server-action-navigation.d.ts +6 -0
  69. package/dist/server/app-browser-server-action-navigation.js +9 -0
  70. package/dist/server/app-browser-state.js +4 -7
  71. package/dist/server/app-browser-stream.js +86 -43
  72. package/dist/server/app-browser-visible-commit.js +1 -1
  73. package/dist/server/app-elements-wire.d.ts +6 -1
  74. package/dist/server/app-elements-wire.js +14 -4
  75. package/dist/server/app-elements.d.ts +2 -2
  76. package/dist/server/app-elements.js +2 -2
  77. package/dist/server/app-fallback-renderer.d.ts +3 -1
  78. package/dist/server/app-fallback-renderer.js +6 -2
  79. package/dist/server/app-middleware.js +1 -0
  80. package/dist/server/app-optimistic-routing.js +24 -3
  81. package/dist/server/app-page-boundary-render.d.ts +3 -1
  82. package/dist/server/app-page-boundary-render.js +31 -16
  83. package/dist/server/app-page-cache-render.d.ts +53 -0
  84. package/dist/server/app-page-cache-render.js +91 -0
  85. package/dist/server/app-page-cache.d.ts +16 -2
  86. package/dist/server/app-page-cache.js +71 -8
  87. package/dist/server/app-page-dispatch.d.ts +34 -0
  88. package/dist/server/app-page-dispatch.js +167 -97
  89. package/dist/server/app-page-element-builder.d.ts +23 -2
  90. package/dist/server/app-page-element-builder.js +42 -10
  91. package/dist/server/app-page-execution.d.ts +7 -2
  92. package/dist/server/app-page-execution.js +53 -18
  93. package/dist/server/app-page-probe.d.ts +1 -0
  94. package/dist/server/app-page-probe.js +4 -0
  95. package/dist/server/app-page-render-observation.d.ts +3 -1
  96. package/dist/server/app-page-render-observation.js +17 -1
  97. package/dist/server/app-page-render.d.ts +13 -2
  98. package/dist/server/app-page-render.js +48 -17
  99. package/dist/server/app-page-request.d.ts +3 -0
  100. package/dist/server/app-page-request.js +5 -3
  101. package/dist/server/app-page-response.js +1 -1
  102. package/dist/server/app-page-route-wiring.d.ts +5 -1
  103. package/dist/server/app-page-route-wiring.js +21 -11
  104. package/dist/server/app-page-stream.d.ts +16 -9
  105. package/dist/server/app-page-stream.js +12 -9
  106. package/dist/server/app-pages-bridge.d.ts +18 -0
  107. package/dist/server/app-pages-bridge.js +22 -5
  108. package/dist/server/app-ppr-fallback-shell-render.d.ts +17 -0
  109. package/dist/server/app-ppr-fallback-shell-render.js +26 -0
  110. package/dist/server/app-ppr-fallback-shell.d.ts +13 -1
  111. package/dist/server/app-ppr-fallback-shell.js +8 -1
  112. package/dist/server/app-route-handler-dispatch.js +9 -2
  113. package/dist/server/app-route-handler-policy.d.ts +1 -0
  114. package/dist/server/app-route-handler-response.js +11 -10
  115. package/dist/server/app-route-handler-runtime.js +12 -1
  116. package/dist/server/app-router-entry.js +5 -0
  117. package/dist/server/app-rsc-cache-busting.js +2 -0
  118. package/dist/server/app-rsc-handler.d.ts +25 -0
  119. package/dist/server/app-rsc-handler.js +153 -53
  120. package/dist/server/app-rsc-response-finalizer.js +1 -1
  121. package/dist/server/app-rsc-route-matching.d.ts +3 -0
  122. package/dist/server/app-rsc-route-matching.js +2 -0
  123. package/dist/server/app-segment-config.d.ts +9 -1
  124. package/dist/server/app-segment-config.js +12 -3
  125. package/dist/server/app-server-action-execution.d.ts +12 -0
  126. package/dist/server/app-server-action-execution.js +47 -15
  127. package/dist/server/app-ssr-entry.d.ts +2 -0
  128. package/dist/server/app-ssr-entry.js +81 -8
  129. package/dist/server/app-ssr-stream.js +9 -1
  130. package/dist/server/cache-control.js +4 -0
  131. package/dist/server/dev-lockfile.js +2 -1
  132. package/dist/server/dev-server.d.ts +2 -2
  133. package/dist/server/dev-server.js +287 -63
  134. package/dist/server/headers.d.ts +8 -1
  135. package/dist/server/headers.js +8 -1
  136. package/dist/server/hybrid-route-priority.d.ts +22 -0
  137. package/dist/server/hybrid-route-priority.js +33 -0
  138. package/dist/server/image-optimization.d.ts +18 -9
  139. package/dist/server/image-optimization.js +37 -23
  140. package/dist/server/implicit-tags.d.ts +2 -1
  141. package/dist/server/implicit-tags.js +4 -1
  142. package/dist/server/instrumentation-runtime.d.ts +6 -0
  143. package/dist/server/instrumentation-runtime.js +8 -0
  144. package/dist/server/isr-decision.d.ts +79 -0
  145. package/dist/server/isr-decision.js +70 -0
  146. package/dist/server/metadata-route-response.js +5 -3
  147. package/dist/server/middleware-runtime.d.ts +13 -0
  148. package/dist/server/middleware-runtime.js +11 -7
  149. package/dist/server/middleware.js +1 -0
  150. package/dist/server/navigation-planner.d.ts +186 -22
  151. package/dist/server/navigation-planner.js +302 -0
  152. package/dist/server/navigation-trace.d.ts +18 -1
  153. package/dist/server/navigation-trace.js +18 -1
  154. package/dist/server/normalize-path.d.ts +0 -8
  155. package/dist/server/normalize-path.js +3 -1
  156. package/dist/server/otel-tracer-extension.d.ts +45 -0
  157. package/dist/server/otel-tracer-extension.js +89 -0
  158. package/dist/server/pages-api-route.d.ts +20 -3
  159. package/dist/server/pages-api-route.js +19 -3
  160. package/dist/server/pages-asset-tags.d.ts +16 -4
  161. package/dist/server/pages-asset-tags.js +22 -12
  162. package/dist/server/pages-data-route.d.ts +8 -1
  163. package/dist/server/pages-data-route.js +16 -3
  164. package/dist/server/pages-get-initial-props.d.ts +54 -4
  165. package/dist/server/pages-get-initial-props.js +43 -1
  166. package/dist/server/pages-node-compat.d.ts +3 -11
  167. package/dist/server/pages-node-compat.js +175 -122
  168. package/dist/server/pages-page-data.d.ts +39 -2
  169. package/dist/server/pages-page-data.js +261 -46
  170. package/dist/server/pages-page-handler.d.ts +5 -2
  171. package/dist/server/pages-page-handler.js +78 -25
  172. package/dist/server/pages-page-response.d.ts +47 -2
  173. package/dist/server/pages-page-response.js +73 -9
  174. package/dist/server/pages-readiness.d.ts +1 -1
  175. package/dist/server/pages-request-pipeline.d.ts +16 -1
  176. package/dist/server/pages-request-pipeline.js +96 -38
  177. package/dist/server/pregenerated-concrete-paths.d.ts +1 -17
  178. package/dist/server/pregenerated-concrete-paths.js +2 -19
  179. package/dist/server/prerender-manifest.d.ts +33 -0
  180. package/dist/server/prerender-manifest.js +54 -0
  181. package/dist/server/prerender-route-params.d.ts +1 -2
  182. package/dist/server/prod-server.d.ts +39 -1
  183. package/dist/server/prod-server.js +107 -37
  184. package/dist/server/request-pipeline.d.ts +3 -15
  185. package/dist/server/request-pipeline.js +58 -47
  186. package/dist/server/rsc-stream-hints.d.ts +5 -1
  187. package/dist/server/rsc-stream-hints.js +6 -1
  188. package/dist/server/seed-cache.js +10 -18
  189. package/dist/shims/app-router-scroll-state.d.ts +3 -1
  190. package/dist/shims/app-router-scroll-state.js +14 -2
  191. package/dist/shims/app-router-scroll.d.ts +3 -0
  192. package/dist/shims/app-router-scroll.js +28 -18
  193. package/dist/shims/cache-runtime.js +12 -4
  194. package/dist/shims/cache.d.ts +1 -0
  195. package/dist/shims/cache.js +1 -1
  196. package/dist/shims/cdn-cache.d.ts +5 -5
  197. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  198. package/dist/shims/dynamic-preload-chunks.js +79 -0
  199. package/dist/shims/dynamic.d.ts +4 -0
  200. package/dist/shims/dynamic.js +4 -2
  201. package/dist/shims/error-boundary.d.ts +6 -4
  202. package/dist/shims/error-boundary.js +7 -0
  203. package/dist/shims/error.js +38 -11
  204. package/dist/shims/error.react-server.d.ts +9 -0
  205. package/dist/shims/error.react-server.js +6 -0
  206. package/dist/shims/fetch-cache.d.ts +11 -1
  207. package/dist/shims/fetch-cache.js +55 -20
  208. package/dist/shims/hash-scroll.js +6 -1
  209. package/dist/shims/head.js +6 -1
  210. package/dist/shims/headers.d.ts +16 -2
  211. package/dist/shims/headers.js +66 -5
  212. package/dist/shims/image-config.js +7 -1
  213. package/dist/shims/internal/als-registry.js +28 -1
  214. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  215. package/dist/shims/internal/app-route-detection.js +18 -23
  216. package/dist/shims/internal/app-router-context.d.ts +5 -0
  217. package/dist/shims/internal/hybrid-client-route-owner.d.ts +31 -0
  218. package/dist/shims/internal/hybrid-client-route-owner.js +143 -0
  219. package/dist/shims/internal/navigation-untracked.d.ts +35 -0
  220. package/dist/shims/internal/navigation-untracked.js +55 -0
  221. package/dist/shims/internal/pages-data-target.d.ts +7 -2
  222. package/dist/shims/internal/pages-data-target.js +17 -8
  223. package/dist/shims/internal/pages-router-accessor.d.ts +19 -0
  224. package/dist/shims/internal/pages-router-accessor.js +13 -0
  225. package/dist/shims/internal/router-context.d.ts +2 -1
  226. package/dist/shims/internal/router-context.js +3 -1
  227. package/dist/shims/link.js +12 -5
  228. package/dist/shims/metadata.d.ts +6 -2
  229. package/dist/shims/metadata.js +32 -14
  230. package/dist/shims/navigation.d.ts +14 -17
  231. package/dist/shims/navigation.js +93 -46
  232. package/dist/shims/ppr-fallback-shell.d.ts +5 -1
  233. package/dist/shims/ppr-fallback-shell.js +28 -7
  234. package/dist/shims/router.d.ts +13 -2
  235. package/dist/shims/router.js +434 -116
  236. package/dist/shims/script-nonce-context.d.ts +1 -1
  237. package/dist/shims/script-nonce-context.js +11 -3
  238. package/dist/shims/server.d.ts +33 -2
  239. package/dist/shims/server.js +75 -18
  240. package/dist/shims/slot.js +1 -1
  241. package/dist/shims/unified-request-context.js +2 -0
  242. package/dist/typegen.js +1 -0
  243. package/dist/utils/built-asset-url.d.ts +4 -0
  244. package/dist/utils/built-asset-url.js +11 -0
  245. package/dist/utils/client-build-manifest.js +15 -5
  246. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  247. package/dist/utils/client-runtime-metadata.js +63 -0
  248. package/dist/utils/commonjs-loader.d.ts +16 -0
  249. package/dist/utils/commonjs-loader.js +100 -0
  250. package/dist/utils/deployment-id.d.ts +8 -0
  251. package/dist/utils/deployment-id.js +22 -0
  252. package/dist/utils/hash.d.ts +17 -1
  253. package/dist/utils/hash.js +36 -1
  254. package/dist/utils/html-limited-bots.d.ts +18 -1
  255. package/dist/utils/html-limited-bots.js +23 -1
  256. package/dist/utils/lazy-chunks.d.ts +27 -1
  257. package/dist/utils/lazy-chunks.js +65 -1
  258. package/dist/utils/manifest-paths.d.ts +20 -2
  259. package/dist/utils/manifest-paths.js +38 -3
  260. package/dist/utils/parse-cookie.d.ts +13 -0
  261. package/dist/utils/parse-cookie.js +52 -0
  262. package/dist/utils/path.d.ts +8 -1
  263. package/dist/utils/path.js +13 -1
  264. package/package.json +2 -2
  265. package/dist/shims/internal/parse-cookie-header.d.ts +0 -14
  266. package/dist/shims/internal/parse-cookie-header.js +0 -30
@@ -60,6 +60,13 @@ const RSC_ACTION_HEADER = "x-rsc-action";
60
60
  const NEXT_ACTION_HEADER = "next-action";
61
61
  /** Next.js action-not-found indicator (value "1"). */
62
62
  const NEXTJS_ACTION_NOT_FOUND_HEADER = "x-nextjs-action-not-found";
63
+ /**
64
+ * Deployment ID header used by the Pages Router for deployment-skew
65
+ * protection. Set on every `/_next/data/` response so the client can detect
66
+ * when a new deployment has been rolled out and trigger a hard navigation.
67
+ * Mirrors `NEXT_NAV_DEPLOYMENT_ID_HEADER` from Next.js `lib/constants.ts`.
68
+ */
69
+ const NEXTJS_DEPLOYMENT_ID_HEADER = "x-nextjs-deployment-id";
63
70
  /** Forwarded action marker — set when a request has already been forwarded between workers. */
64
71
  const ACTION_FORWARDED_HEADER = "x-action-forwarded";
65
72
  /** Indicates revalidation occurred — value is JSON kind (1 = path/tag, 2 = dynamic-only). */
@@ -122,4 +129,4 @@ const INTERNAL_HEADERS = [
122
129
  /** Vinext-only internal headers stripped alongside Next.js protocol internals. */
123
130
  const VINEXT_INTERNAL_HEADERS = [VINEXT_PRERENDER_ROUTE_PARAMS_HEADER];
124
131
  //#endregion
125
- export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
132
+ export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
@@ -0,0 +1,22 @@
1
+ //#region src/server/hybrid-route-priority.d.ts
2
+ type HybridRoutePriorityRoute = {
3
+ isDynamic: boolean;
4
+ pattern: string;
5
+ sourcePath?: string | null;
6
+ };
7
+ declare function validateHybridRouteConflicts(pagesRoutes: readonly HybridRoutePriorityRoute[], appRoutes: readonly HybridRoutePriorityRoute[]): void;
8
+ /**
9
+ * Return whether a matched Pages Router route should own the request instead
10
+ * of a matched App Router route.
11
+ *
12
+ * Next.js registers Pages providers before App providers, then sorts all
13
+ * dynamic route pathnames together in DefaultRouteMatcherManager. Vinext keeps
14
+ * separate route tries for each router, so the hybrid boundary needs to apply
15
+ * that same cross-router ordering after both routers have produced their best
16
+ * local match. The decision itself lives in
17
+ * `routing/utils.ts#compareHybridRoutePatterns` so the server and client
18
+ * always reach the same answer.
19
+ */
20
+ declare function pagesRouteHasPriorityOverAppRoute(pagesRoute: HybridRoutePriorityRoute, appRoute: HybridRoutePriorityRoute | null): boolean;
21
+ //#endregion
22
+ export { HybridRoutePriorityRoute, pagesRouteHasPriorityOverAppRoute, validateHybridRouteConflicts };
@@ -0,0 +1,33 @@
1
+ import { compareHybridRoutePatterns } from "../routing/utils.js";
2
+ import { validateRoutePatterns } from "../routing/route-validation.js";
3
+ //#region src/server/hybrid-route-priority.ts
4
+ function validateHybridRouteConflicts(pagesRoutes, appRoutes) {
5
+ const pagesByPattern = new Map(pagesRoutes.map((route) => [route.pattern, route]));
6
+ const conflicts = appRoutes.flatMap((appRoute) => {
7
+ const pagesRoute = pagesByPattern.get(appRoute.pattern);
8
+ return pagesRoute === void 0 ? [] : [[pagesRoute, appRoute]];
9
+ });
10
+ if (conflicts.length > 0) {
11
+ const message = `Conflicting app and page file${conflicts.length === 1 ? " was" : "s were"} found, please remove the conflicting files to continue:`;
12
+ throw new Error(`${message}\n${conflicts.map(([pagesRoute, appRoute]) => ` "${pagesRoute.sourcePath ?? pagesRoute.pattern}" - "${appRoute.sourcePath ?? appRoute.pattern}"`).join("\n")}`);
13
+ }
14
+ validateRoutePatterns([...pagesRoutes.map((route) => route.pattern), ...appRoutes.map((route) => route.pattern)]);
15
+ }
16
+ /**
17
+ * Return whether a matched Pages Router route should own the request instead
18
+ * of a matched App Router route.
19
+ *
20
+ * Next.js registers Pages providers before App providers, then sorts all
21
+ * dynamic route pathnames together in DefaultRouteMatcherManager. Vinext keeps
22
+ * separate route tries for each router, so the hybrid boundary needs to apply
23
+ * that same cross-router ordering after both routers have produced their best
24
+ * local match. The decision itself lives in
25
+ * `routing/utils.ts#compareHybridRoutePatterns` so the server and client
26
+ * always reach the same answer.
27
+ */
28
+ function pagesRouteHasPriorityOverAppRoute(pagesRoute, appRoute) {
29
+ if (appRoute === null) return true;
30
+ return compareHybridRoutePatterns(pagesRoute.pattern, pagesRoute.isDynamic, appRoute.pattern, appRoute.isDynamic) === "pages";
31
+ }
32
+ //#endregion
33
+ export { pagesRouteHasPriorityOverAppRoute, validateHybridRouteConflicts };
@@ -33,7 +33,15 @@ declare function isImageOptimizationPath(pathname: string): boolean;
33
33
  * Controls SVG handling and security headers for the image endpoint.
34
34
  */
35
35
  type ImageConfig = {
36
- /** Allow SVG through the image optimization endpoint. Default: false. */dangerouslyAllowSVG?: boolean;
36
+ /** Allowed device widths. Defaults to Next.js device sizes. */deviceSizes?: number[]; /** Allowed fixed-image widths. Defaults to Next.js image sizes. */
37
+ imageSizes?: number[];
38
+ /**
39
+ * Allowed output qualities. When unset, any quality from 1-100 is permitted
40
+ * (matches Next.js: an unset `images.qualities` is not restricted to a single
41
+ * value). When set, only the listed qualities are accepted.
42
+ */
43
+ qualities?: number[]; /** Allow SVG through the image optimization endpoint. Default: false. */
44
+ dangerouslyAllowSVG?: boolean;
37
45
  /**
38
46
  * Allow image optimization for hostnames that resolve to private IP addresses.
39
47
  * Default: false.
@@ -54,18 +62,19 @@ type ImageConfig = {
54
62
  */
55
63
  declare const DEFAULT_DEVICE_SIZES: number[];
56
64
  declare const DEFAULT_IMAGE_SIZES: number[];
65
+ type ParseImageParamsOptions = {
66
+ isDev?: boolean;
67
+ };
68
+ declare function resolveDevImageRedirect(requestUrl: URL, allowedWidths?: number[], allowedQualities?: number[], options?: ParseImageParamsOptions): string | null;
57
69
  /**
58
70
  * Parse and validate image optimization query parameters.
59
71
  * Returns null if the request is malformed.
60
72
  *
61
- * When `allowedWidths` is provided, the width must be 0 (no resize) or
62
- * exactly match one of the allowed values. This matches Next.js behavior
63
- * where only configured deviceSizes and imageSizes are accepted.
64
- *
65
- * When `allowedWidths` is not provided, any width from 0 to ABSOLUTE_MAX_WIDTH
66
- * is accepted (backwards-compatible fallback).
73
+ * Ported from Next.js:
74
+ * test/integration/image-optimizer/test/index.test.ts
75
+ * https://github.com/vercel/next.js/blob/canary/test/integration/image-optimizer/test/index.test.ts
67
76
  */
68
- declare function parseImageParams(url: URL, allowedWidths?: number[]): {
77
+ declare function parseImageParams(url: URL, allowedWidths?: number[], allowedQualities?: number[], options?: ParseImageParamsOptions): {
69
78
  imageUrl: string;
70
79
  width: number;
71
80
  quality: number;
@@ -113,4 +122,4 @@ type ImageHandlers = {
113
122
  */
114
123
  declare function handleImageOptimization(request: Request, handlers: ImageHandlers, allowedWidths?: number[], imageConfig?: ImageConfig): Promise<Response>;
115
124
  //#endregion
116
- export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, ImageConfig, ImageHandlers, VINEXT_IMAGE_OPTIMIZATION_PATH, handleImageOptimization, isImageOptimizationPath, isSafeImageContentType, negotiateImageFormat, parseImageParams };
125
+ export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, ImageConfig, ImageHandlers, ParseImageParamsOptions, VINEXT_IMAGE_OPTIMIZATION_PATH, handleImageOptimization, isImageOptimizationPath, isSafeImageContentType, negotiateImageFormat, parseImageParams, resolveDevImageRedirect };
@@ -56,32 +56,46 @@ const DEFAULT_IMAGE_SIZES = [
56
56
  256,
57
57
  384
58
58
  ];
59
- /**
60
- * Absolute maximum image width. Even if custom deviceSizes/imageSizes are
61
- * configured, widths above this are always rejected. This prevents resource
62
- * exhaustion from absurdly large resize requests.
63
- */
64
- const ABSOLUTE_MAX_WIDTH = 3840;
59
+ const DEV_BLUR_MAX_WIDTH = 8;
60
+ const DEV_BLUR_QUALITY = 70;
61
+ function resolveDevImageRedirect(requestUrl, allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES], allowedQualities, options = { isDev: true }) {
62
+ const params = parseImageParams(requestUrl, allowedWidths, allowedQualities, options);
63
+ if (!params) return null;
64
+ if (params.imageUrl.startsWith("/@") || params.imageUrl.startsWith("/__vite") || params.imageUrl.startsWith("/node_modules")) return null;
65
+ const resolved = new URL(params.imageUrl, requestUrl.origin);
66
+ if (resolved.origin !== requestUrl.origin) return null;
67
+ return resolved.pathname + resolved.search;
68
+ }
65
69
  /**
66
70
  * Parse and validate image optimization query parameters.
67
71
  * Returns null if the request is malformed.
68
72
  *
69
- * When `allowedWidths` is provided, the width must be 0 (no resize) or
70
- * exactly match one of the allowed values. This matches Next.js behavior
71
- * where only configured deviceSizes and imageSizes are accepted.
72
- *
73
- * When `allowedWidths` is not provided, any width from 0 to ABSOLUTE_MAX_WIDTH
74
- * is accepted (backwards-compatible fallback).
73
+ * Ported from Next.js:
74
+ * test/integration/image-optimizer/test/index.test.ts
75
+ * https://github.com/vercel/next.js/blob/canary/test/integration/image-optimizer/test/index.test.ts
75
76
  */
76
- function parseImageParams(url, allowedWidths) {
77
+ function parseImageParams(url, allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES], allowedQualities, options = {}) {
78
+ const allowedParamNames = new Set([
79
+ "url",
80
+ "w",
81
+ "q"
82
+ ]);
83
+ for (const name of url.searchParams.keys()) if (!allowedParamNames.has(name) || url.searchParams.getAll(name).length !== 1) return null;
77
84
  const imageUrl = url.searchParams.get("url");
78
85
  if (!imageUrl) return null;
79
- const w = parseInt(url.searchParams.get("w") || "0", 10);
80
- const q = parseInt(url.searchParams.get("q") || "75", 10);
81
- if (Number.isNaN(w) || w < 0) return null;
82
- if (w > ABSOLUTE_MAX_WIDTH) return null;
83
- if (allowedWidths && w !== 0 && !allowedWidths.includes(w)) return null;
84
- if (Number.isNaN(q) || q < 1 || q > 100) return null;
86
+ if (imageUrl.length > 3072) return null;
87
+ const widthParam = url.searchParams.get("w");
88
+ const qualityParam = url.searchParams.get("q");
89
+ if (!widthParam || !/^[0-9]+$/.test(widthParam)) return null;
90
+ if (!qualityParam || !/^[0-9]+$/.test(qualityParam)) return null;
91
+ const width = Number.parseInt(widthParam, 10);
92
+ const quality = Number.parseInt(qualityParam, 10);
93
+ if (String(width) !== widthParam || String(quality) !== qualityParam) return null;
94
+ const isDevBlurWidth = options.isDev && width <= DEV_BLUR_MAX_WIDTH;
95
+ const isDevBlurQuality = options.isDev && quality === DEV_BLUR_QUALITY;
96
+ if (width <= 0 || !allowedWidths.includes(width) && !isDevBlurWidth) return null;
97
+ if (quality < 1 || quality > 100) return null;
98
+ if (allowedQualities && !allowedQualities.includes(quality) && !isDevBlurQuality) return null;
85
99
  const normalizedUrl = imageUrl.replaceAll("\\", "/");
86
100
  if (!normalizedUrl.startsWith("/") || normalizedUrl.startsWith("//")) return null;
87
101
  try {
@@ -92,8 +106,8 @@ function parseImageParams(url, allowedWidths) {
92
106
  }
93
107
  return {
94
108
  imageUrl: normalizedUrl,
95
- width: w,
96
- quality: q
109
+ width,
110
+ quality
97
111
  };
98
112
  }
99
113
  /**
@@ -174,7 +188,7 @@ function createPassthroughImageResponse(source, config) {
174
188
  * cache headers.
175
189
  */
176
190
  async function handleImageOptimization(request, handlers, allowedWidths, imageConfig) {
177
- const params = parseImageParams(new URL(request.url), allowedWidths);
191
+ const params = parseImageParams(new URL(request.url), allowedWidths, imageConfig?.qualities);
178
192
  if (!params) return badRequestResponse();
179
193
  const { imageUrl, width, quality } = params;
180
194
  const source = await handlers.fetchAsset(imageUrl, request);
@@ -212,4 +226,4 @@ async function handleImageOptimization(request, handlers, allowedWidths, imageCo
212
226
  }
213
227
  }
214
228
  //#endregion
215
- export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, VINEXT_IMAGE_OPTIMIZATION_PATH, handleImageOptimization, isImageOptimizationPath, isSafeImageContentType, negotiateImageFormat, parseImageParams };
229
+ export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, VINEXT_IMAGE_OPTIMIZATION_PATH, handleImageOptimization, isImageOptimizationPath, isSafeImageContentType, negotiateImageFormat, parseImageParams, resolveDevImageRedirect };
@@ -1,5 +1,6 @@
1
1
  //#region src/server/implicit-tags.d.ts
2
2
  type AppCacheLeafKind = "page" | "route";
3
+ declare function buildAppPageTags(cleanPathname: string, extraTags: string[], routeSegments: readonly string[]): string[];
3
4
  declare function buildPageCacheTags(pathname: string, extraTags: string[], routeSegments: string[], leafKind: AppCacheLeafKind): string[];
4
5
  //#endregion
5
- export { buildPageCacheTags };
6
+ export { buildAppPageTags, buildPageCacheTags };
@@ -29,6 +29,9 @@ function appendDerivedTags(tags, routePath) {
29
29
  appendUnique(tags, `${NEXT_CACHE_IMPLICIT_TAG_ID}${currentPathname}`);
30
30
  }
31
31
  }
32
+ function buildAppPageTags(cleanPathname, extraTags, routeSegments) {
33
+ return buildPageCacheTags(cleanPathname, extraTags, [...routeSegments], "page");
34
+ }
32
35
  function buildPageCacheTags(pathname, extraTags, routeSegments, leafKind) {
33
36
  const tags = [pathname, `${NEXT_CACHE_IMPLICIT_TAG_ID}${pathname}`];
34
37
  if (pathname === "/") appendUnique(tags, `${NEXT_CACHE_IMPLICIT_TAG_ID}/index`);
@@ -38,4 +41,4 @@ function buildPageCacheTags(pathname, extraTags, routeSegments, leafKind) {
38
41
  return tags.map(encodeCacheTag);
39
42
  }
40
43
  //#endregion
41
- export { buildPageCacheTags };
44
+ export { buildAppPageTags, buildPageCacheTags };
@@ -34,6 +34,12 @@
34
34
  * Ensure the instrumentation module's `register()` and `onRequestError`
35
35
  * hooks have been applied exactly once.
36
36
  *
37
+ * After `register()` runs, we extend the OTel tracer provider so that spans
38
+ * created inside Cache Component renders (warmup / fallback-resume phases) use
39
+ * a fresh OTel context rather than inheriting the prerender work unit store.
40
+ * This mirrors Next.js's `afterRegistration()` call in
41
+ * `instrumentation-node-extensions.ts`.
42
+ *
37
43
  * @param instrumentationModule - The imported `instrumentation.ts` module.
38
44
  * Passed as an argument so the generated entry can import it normally
39
45
  * without this helper needing to know the module path.
@@ -1,3 +1,4 @@
1
+ import { extendTracerProviderForCacheComponents } from "./otel-tracer-extension.js";
1
2
  //#region src/server/instrumentation-runtime.ts
2
3
  let initialized = false;
3
4
  let initPromise = null;
@@ -8,6 +9,12 @@ function isOnRequestErrorHandler(value) {
8
9
  * Ensure the instrumentation module's `register()` and `onRequestError`
9
10
  * hooks have been applied exactly once.
10
11
  *
12
+ * After `register()` runs, we extend the OTel tracer provider so that spans
13
+ * created inside Cache Component renders (warmup / fallback-resume phases) use
14
+ * a fresh OTel context rather than inheriting the prerender work unit store.
15
+ * This mirrors Next.js's `afterRegistration()` call in
16
+ * `instrumentation-node-extensions.ts`.
17
+ *
11
18
  * @param instrumentationModule - The imported `instrumentation.ts` module.
12
19
  * Passed as an argument so the generated entry can import it normally
13
20
  * without this helper needing to know the module path.
@@ -18,6 +25,7 @@ async function ensureInstrumentationRegistered(instrumentationModule) {
18
25
  if (initPromise) return initPromise;
19
26
  initPromise = (async () => {
20
27
  if (typeof instrumentationModule.register === "function") await instrumentationModule.register();
28
+ extendTracerProviderForCacheComponents();
21
29
  if (isOnRequestErrorHandler(instrumentationModule.onRequestError)) globalThis.__VINEXT_onRequestErrorHandler__ = instrumentationModule.onRequestError;
22
30
  initialized = true;
23
31
  })();
@@ -0,0 +1,79 @@
1
+ import { CacheControlMetadata } from "../shims/cache.js";
2
+ import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL } from "./cache-control.js";
3
+
4
+ //#region src/server/isr-decision.d.ts
5
+ type IsrDisposition = "HIT" | "STALE" | "MISS";
6
+ type IsrDecision = {
7
+ disposition: IsrDisposition; /** True when the caller must schedule a background regeneration. */
8
+ scheduleRegeneration: boolean; /** The `Cache-Control` string to stamp on the response. */
9
+ cacheControl: string;
10
+ };
11
+ /**
12
+ * Per-router special-case policies for `Cache-Control`.
13
+ *
14
+ * - `"app-page"` / `"pages"`: `buildCachedRevalidateCacheControl` for HIT/STALE.
15
+ * - `"app-route"`: same, but `revalidateSeconds=0` forces `NEVER_CACHE_CONTROL`
16
+ * and `revalidateSeconds=Infinity` forces `STATIC_CACHE_CONTROL`.
17
+ * - `"dev"`: like `"pages"`, but `revalidate=0`/`Infinity` guards are absent
18
+ * (dev never caches when revalidate=0 and never has Infinity entries in practice).
19
+ */
20
+ type IsrPolicyKind = "app-page" | "app-route" | "pages" | "dev";
21
+ type DecideIsrOptions = {
22
+ /**
23
+ * The cache state. Content guards (kind-mismatch, empty body,
24
+ * query-variant-unproven) must have already passed before passing
25
+ * `"HIT"` or `"STALE"` here.
26
+ */
27
+ cacheState: "HIT" | "STALE" | "MISS"; /** Which router is making the decision. */
28
+ kind: IsrPolicyKind;
29
+ /**
30
+ * The route's configured revalidate window in seconds. Used as the fallback
31
+ * when `cacheControlMeta` is absent.
32
+ *
33
+ * For `"dev"` call sites this is the only source of the revalidate value —
34
+ * dev never has metadata attached to a cache entry.
35
+ */
36
+ revalidateSeconds: number;
37
+ /**
38
+ * The expire ceiling (seconds from epoch) read from the route config.
39
+ * Absent when the route pre-dates expire metadata support.
40
+ */
41
+ expireSeconds?: number;
42
+ /**
43
+ * Optional per-entry metadata written alongside the cache value.
44
+ * When present its `revalidate`/`expire` fields override the route defaults,
45
+ * exactly as the call sites do today with `cacheControl?.revalidate ?? revalidateSeconds`.
46
+ */
47
+ cacheControlMeta?: CacheControlMetadata;
48
+ };
49
+ /**
50
+ * Derive the `Cache-Control` string for an ISR response.
51
+ *
52
+ * Content guards (kind mismatch, query-variant-unproven, empty body) are the
53
+ * caller's responsibility and must happen *before* this call. `cacheState`
54
+ * must only be `"HIT"` or `"STALE"` when those guards have already passed.
55
+ */
56
+ declare function decideIsr(options: DecideIsrOptions): IsrDecision;
57
+ /**
58
+ * Build the `Cache-Control` string for a fresh MISS response whose ISR policy
59
+ * is known (i.e. revalidate is set and > 0). Uses the unbounded SWR form when
60
+ * no expire ceiling is available, exactly as `buildRevalidateCacheControl` does.
61
+ *
62
+ * Separate from `decideIsr` because a MISS doesn't read a cache entry and
63
+ * therefore never has `cacheControlMeta`. `expireSeconds` here is the route
64
+ * config ceiling passed directly from the caller (not a per-entry fallback).
65
+ */
66
+ declare function buildMissIsrCacheControl(revalidateSeconds: number, expireSeconds?: number): string;
67
+ /**
68
+ * Build the `Cache-Control` string for a fresh (MISS) app-route response.
69
+ *
70
+ * Applies the same `revalidateSeconds=0`→NEVER and `Infinity`→STATIC gates
71
+ * that `decideIsr` uses for app-route cached responses. `expireSeconds` is
72
+ * the route config ceiling passed directly (not per-entry metadata fallback).
73
+ *
74
+ * Used by `applyRouteHandlerRevalidateHeader` which operates on a fresh
75
+ * response that has no per-entry cache metadata.
76
+ */
77
+ declare function buildAppRouteMissIsrCacheControl(revalidateSeconds: number, expireSeconds?: number): string;
78
+ //#endregion
79
+ export { NEVER_CACHE_CONTROL as ISR_NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL as ISR_NO_STORE_CACHE_CONTROL, buildAppRouteMissIsrCacheControl, buildMissIsrCacheControl, decideIsr };
@@ -0,0 +1,70 @@
1
+ import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, buildCachedRevalidateCacheControl, buildRevalidateCacheControl } from "./cache-control.js";
2
+ //#region src/server/isr-decision.ts
3
+ /** Resolve effective revalidate/expire, preferring per-entry metadata. */
4
+ function resolveRevalidate(options) {
5
+ return {
6
+ effectiveRevalidate: options.cacheControlMeta?.revalidate ?? options.revalidateSeconds,
7
+ effectiveExpire: options.cacheControlMeta === void 0 ? void 0 : options.cacheControlMeta.expire ?? options.expireSeconds
8
+ };
9
+ }
10
+ function buildCacheControl(disposition, kind, revalidate, expire) {
11
+ if (kind === "app-route") {
12
+ if (revalidate === 0) return NEVER_CACHE_CONTROL;
13
+ if (revalidate === Infinity) return STATIC_CACHE_CONTROL;
14
+ }
15
+ return buildCachedRevalidateCacheControl(disposition, revalidate, expire);
16
+ }
17
+ /**
18
+ * Derive the `Cache-Control` string for an ISR response.
19
+ *
20
+ * Content guards (kind mismatch, query-variant-unproven, empty body) are the
21
+ * caller's responsibility and must happen *before* this call. `cacheState`
22
+ * must only be `"HIT"` or `"STALE"` when those guards have already passed.
23
+ */
24
+ function decideIsr(options) {
25
+ if (options.cacheState === "MISS") return {
26
+ disposition: "MISS",
27
+ scheduleRegeneration: false,
28
+ cacheControl: ""
29
+ };
30
+ const { effectiveRevalidate, effectiveExpire } = resolveRevalidate(options);
31
+ if (options.cacheState === "HIT") return {
32
+ disposition: "HIT",
33
+ scheduleRegeneration: false,
34
+ cacheControl: buildCacheControl("HIT", options.kind, effectiveRevalidate, effectiveExpire)
35
+ };
36
+ return {
37
+ disposition: "STALE",
38
+ scheduleRegeneration: true,
39
+ cacheControl: buildCacheControl("STALE", options.kind, effectiveRevalidate, effectiveExpire)
40
+ };
41
+ }
42
+ /**
43
+ * Build the `Cache-Control` string for a fresh MISS response whose ISR policy
44
+ * is known (i.e. revalidate is set and > 0). Uses the unbounded SWR form when
45
+ * no expire ceiling is available, exactly as `buildRevalidateCacheControl` does.
46
+ *
47
+ * Separate from `decideIsr` because a MISS doesn't read a cache entry and
48
+ * therefore never has `cacheControlMeta`. `expireSeconds` here is the route
49
+ * config ceiling passed directly from the caller (not a per-entry fallback).
50
+ */
51
+ function buildMissIsrCacheControl(revalidateSeconds, expireSeconds) {
52
+ return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
53
+ }
54
+ /**
55
+ * Build the `Cache-Control` string for a fresh (MISS) app-route response.
56
+ *
57
+ * Applies the same `revalidateSeconds=0`→NEVER and `Infinity`→STATIC gates
58
+ * that `decideIsr` uses for app-route cached responses. `expireSeconds` is
59
+ * the route config ceiling passed directly (not per-entry metadata fallback).
60
+ *
61
+ * Used by `applyRouteHandlerRevalidateHeader` which operates on a fresh
62
+ * response that has no per-entry cache metadata.
63
+ */
64
+ function buildAppRouteMissIsrCacheControl(revalidateSeconds, expireSeconds) {
65
+ if (revalidateSeconds === 0) return NEVER_CACHE_CONTROL;
66
+ if (revalidateSeconds === Infinity) return STATIC_CACHE_CONTROL;
67
+ return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
68
+ }
69
+ //#endregion
70
+ export { NEVER_CACHE_CONTROL as ISR_NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL as ISR_NO_STORE_CACHE_CONTROL, buildAppRouteMissIsrCacheControl, buildMissIsrCacheControl, decideIsr };
@@ -53,9 +53,9 @@ function getMetadataRouteFunctions(route) {
53
53
  routeFunctionCache.set(route, functions);
54
54
  return functions;
55
55
  }
56
- function matchMetadataRoute(route, cleanPathname, functions) {
56
+ function matchMetadataRoute(route, cleanPathname, functions, getUrlParts) {
57
57
  if (route.patternParts) {
58
- const urlParts = cleanPathname.split("/").filter(Boolean);
58
+ const urlParts = getUrlParts();
59
59
  if (functions.hasGeneratedImageMetadata && urlParts.length > 0) {
60
60
  const params = matchMetadataRoutePattern(urlParts.slice(0, -1), route.patternParts);
61
61
  if (params) return {
@@ -184,6 +184,8 @@ function serveStaticMetadataRoute(route) {
184
184
  }
185
185
  }
186
186
  async function handleMetadataRouteRequest(options) {
187
+ let urlParts;
188
+ const getUrlParts = () => urlParts ??= options.cleanPathname.split("/").filter(Boolean);
187
189
  for (const route of options.metadataRoutes) {
188
190
  const functions = getMetadataRouteFunctions(route);
189
191
  if (route.type === "sitemap" && route.isDynamic) {
@@ -193,7 +195,7 @@ async function handleMetadataRouteRequest(options) {
193
195
  continue;
194
196
  }
195
197
  }
196
- const match = matchMetadataRoute(route, options.cleanPathname, functions);
198
+ const match = matchMetadataRoute(route, options.cleanPathname, functions, getUrlParts);
197
199
  if (!match) continue;
198
200
  return route.isDynamic ? callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route);
199
201
  }
@@ -19,6 +19,19 @@ type MiddlewareHandler = (request: NextRequest, event: NextFetchEvent) => Respon
19
19
  type ExecuteMiddlewareOptions = {
20
20
  basePath?: string;
21
21
  filePath?: string;
22
+ /**
23
+ * Whether the incoming request was inside the configured basePath. Drives
24
+ * the `nextUrl.basePath` the middleware observes: in-basePath requests are
25
+ * re-prefixed so NextURL reports the configured basePath, while
26
+ * out-of-basePath ("absolute path") requests stay un-prefixed so middleware
27
+ * sees `nextUrl.basePath === ""` (Next.js `getNextPathnameInfo` semantics —
28
+ * see test/e2e/middleware-base-path "should execute from absolute paths").
29
+ * When omitted it is derived from the request URL, which is correct for the
30
+ * Pages prod/deploy adapters because they pass the original (un-stripped)
31
+ * URL. Callers that pass an already-stripped URL (dev server, App Router)
32
+ * must set this explicitly.
33
+ */
34
+ hadBasePath?: boolean;
22
35
  i18nConfig?: NextI18nConfig | null;
23
36
  includeErrorDetails?: boolean;
24
37
  /**
@@ -1,4 +1,5 @@
1
1
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
2
+ import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
2
3
  import "./server-globals.js";
3
4
  import { getRequestExecutionContext, runWithExecutionContext } from "../shims/request-context.js";
4
5
  import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
@@ -99,12 +100,13 @@ function resolveMiddlewarePathname(request) {
99
100
  return badRequestResponse();
100
101
  }
101
102
  }
102
- function createNextRequest(request, normalizedPathname, i18nConfig, basePath, trailingSlash) {
103
+ function createNextRequest(request, normalizedPathname, i18nConfig, basePath, trailingSlash, hadBasePath) {
103
104
  const url = new URL(request.url);
104
105
  let mwRequest = request.body && !request.bodyUsed ? request.clone() : request;
105
- if (normalizedPathname !== url.pathname) {
106
+ const mwPathname = basePath && hadBasePath ? addBasePathToPathname(normalizedPathname, basePath) : normalizedPathname;
107
+ if (mwPathname !== url.pathname) {
106
108
  const mwUrl = new URL(url);
107
- mwUrl.pathname = normalizedPathname;
109
+ mwUrl.pathname = mwPathname;
108
110
  mwRequest = new Request(mwUrl, mwRequest);
109
111
  }
110
112
  const nextConfig = basePath || i18nConfig || trailingSlash ? {
@@ -124,9 +126,11 @@ async function executeMiddleware(options) {
124
126
  continue: false,
125
127
  response: normalizedPathname
126
128
  };
127
- if (!matchesMiddleware(normalizedPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
128
- const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash);
129
- const fetchEvent = new NextFetchEvent({ page: normalizedPathname });
129
+ const hadBasePath = options.hadBasePath ?? (!options.basePath || hasBasePath(new URL(options.request.url).pathname, options.basePath));
130
+ const matchPathname = options.basePath ? stripBasePath(normalizedPathname, options.basePath) : normalizedPathname;
131
+ if (!matchesMiddleware(matchPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
132
+ const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath);
133
+ const fetchEvent = new NextFetchEvent({ page: matchPathname });
130
134
  let response;
131
135
  try {
132
136
  response = await middlewareFn(nextRequest, fetchEvent);
@@ -195,7 +199,7 @@ async function executeMiddleware(options) {
195
199
  try {
196
200
  const rewriteParsed = new URL(rewriteUrl, options.request.url);
197
201
  const requestOrigin = new URL(options.request.url).origin;
198
- if (rewriteParsed.origin === requestOrigin) rewritePath = rewriteParsed.pathname + rewriteParsed.search;
202
+ if (rewriteParsed.origin === requestOrigin) rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
199
203
  else rewritePath = rewriteParsed.href;
200
204
  } catch {
201
205
  rewritePath = rewriteUrl;
@@ -85,6 +85,7 @@ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePa
85
85
  return runGeneratedMiddleware({
86
86
  basePath,
87
87
  filePath: middlewarePath,
88
+ hadBasePath: true,
88
89
  i18nConfig,
89
90
  includeErrorDetails: process.env.NODE_ENV !== "production",
90
91
  isDataRequest,