vinext 0.0.26 → 0.0.27

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 (196) hide show
  1. package/README.md +89 -85
  2. package/dist/build/static-export.d.ts.map +1 -1
  3. package/dist/build/static-export.js +3 -8
  4. package/dist/build/static-export.js.map +1 -1
  5. package/dist/check.d.ts.map +1 -1
  6. package/dist/check.js +152 -48
  7. package/dist/check.js.map +1 -1
  8. package/dist/cli.js +10 -11
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cloudflare/kv-cache-handler.d.ts +32 -1
  11. package/dist/cloudflare/kv-cache-handler.d.ts.map +1 -1
  12. package/dist/cloudflare/kv-cache-handler.js +47 -21
  13. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  14. package/dist/cloudflare/tpr.d.ts.map +1 -1
  15. package/dist/cloudflare/tpr.js +15 -4
  16. package/dist/cloudflare/tpr.js.map +1 -1
  17. package/dist/config/config-matchers.d.ts +27 -0
  18. package/dist/config/config-matchers.d.ts.map +1 -1
  19. package/dist/config/config-matchers.js +306 -60
  20. package/dist/config/config-matchers.js.map +1 -1
  21. package/dist/config/dotenv.d.ts.map +1 -1
  22. package/dist/config/dotenv.js +1 -6
  23. package/dist/config/dotenv.js.map +1 -1
  24. package/dist/config/next-config.d.ts +7 -0
  25. package/dist/config/next-config.d.ts.map +1 -1
  26. package/dist/config/next-config.js +44 -19
  27. package/dist/config/next-config.js.map +1 -1
  28. package/dist/deploy.d.ts.map +1 -1
  29. package/dist/deploy.js +36 -19
  30. package/dist/deploy.js.map +1 -1
  31. package/dist/entries/app-rsc-entry.d.ts.map +1 -1
  32. package/dist/entries/app-rsc-entry.js +89 -38
  33. package/dist/entries/app-rsc-entry.js.map +1 -1
  34. package/dist/entries/pages-client-entry.d.ts.map +1 -1
  35. package/dist/entries/pages-client-entry.js +5 -3
  36. package/dist/entries/pages-client-entry.js.map +1 -1
  37. package/dist/entries/pages-server-entry.d.ts.map +1 -1
  38. package/dist/entries/pages-server-entry.js +32 -10
  39. package/dist/entries/pages-server-entry.js.map +1 -1
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +204 -118
  43. package/dist/index.js.map +1 -1
  44. package/dist/init.d.ts.map +1 -1
  45. package/dist/init.js +6 -5
  46. package/dist/init.js.map +1 -1
  47. package/dist/routing/app-router.d.ts +2 -0
  48. package/dist/routing/app-router.d.ts.map +1 -1
  49. package/dist/routing/app-router.js +10 -18
  50. package/dist/routing/app-router.js.map +1 -1
  51. package/dist/routing/file-matcher.d.ts.map +1 -1
  52. package/dist/routing/file-matcher.js.map +1 -1
  53. package/dist/routing/pages-router.d.ts +2 -0
  54. package/dist/routing/pages-router.d.ts.map +1 -1
  55. package/dist/routing/pages-router.js +8 -5
  56. package/dist/routing/pages-router.js.map +1 -1
  57. package/dist/routing/utils.d.ts.map +1 -1
  58. package/dist/routing/utils.js.map +1 -1
  59. package/dist/server/api-handler.d.ts.map +1 -1
  60. package/dist/server/api-handler.js +7 -2
  61. package/dist/server/api-handler.js.map +1 -1
  62. package/dist/server/app-router-entry.d.ts +3 -2
  63. package/dist/server/app-router-entry.d.ts.map +1 -1
  64. package/dist/server/app-router-entry.js +8 -4
  65. package/dist/server/app-router-entry.js.map +1 -1
  66. package/dist/server/dev-module-runner.d.ts.map +1 -1
  67. package/dist/server/dev-module-runner.js +1 -1
  68. package/dist/server/dev-module-runner.js.map +1 -1
  69. package/dist/server/dev-origin-check.d.ts.map +1 -1
  70. package/dist/server/dev-origin-check.js.map +1 -1
  71. package/dist/server/dev-server.d.ts.map +1 -1
  72. package/dist/server/dev-server.js +30 -18
  73. package/dist/server/dev-server.js.map +1 -1
  74. package/dist/server/image-optimization.d.ts.map +1 -1
  75. package/dist/server/image-optimization.js.map +1 -1
  76. package/dist/server/instrumentation.js +1 -1
  77. package/dist/server/instrumentation.js.map +1 -1
  78. package/dist/server/isr-cache.d.ts +13 -1
  79. package/dist/server/isr-cache.d.ts.map +1 -1
  80. package/dist/server/isr-cache.js +10 -1
  81. package/dist/server/isr-cache.js.map +1 -1
  82. package/dist/server/metadata-routes.d.ts.map +1 -1
  83. package/dist/server/metadata-routes.js +6 -18
  84. package/dist/server/metadata-routes.js.map +1 -1
  85. package/dist/server/middleware-codegen.d.ts.map +1 -1
  86. package/dist/server/middleware-codegen.js +12 -10
  87. package/dist/server/middleware-codegen.js.map +1 -1
  88. package/dist/server/middleware-request-headers.d.ts +9 -0
  89. package/dist/server/middleware-request-headers.d.ts.map +1 -0
  90. package/dist/server/middleware-request-headers.js +77 -0
  91. package/dist/server/middleware-request-headers.js.map +1 -0
  92. package/dist/server/middleware.d.ts.map +1 -1
  93. package/dist/server/middleware.js +38 -19
  94. package/dist/server/middleware.js.map +1 -1
  95. package/dist/server/normalize-path.js.map +1 -1
  96. package/dist/server/prod-server.d.ts +1 -1
  97. package/dist/server/prod-server.d.ts.map +1 -1
  98. package/dist/server/prod-server.js +53 -38
  99. package/dist/server/prod-server.js.map +1 -1
  100. package/dist/server/request-pipeline.d.ts +2 -1
  101. package/dist/server/request-pipeline.d.ts.map +1 -1
  102. package/dist/server/request-pipeline.js +5 -7
  103. package/dist/server/request-pipeline.js.map +1 -1
  104. package/dist/shims/cache-runtime.d.ts.map +1 -1
  105. package/dist/shims/cache-runtime.js +21 -16
  106. package/dist/shims/cache-runtime.js.map +1 -1
  107. package/dist/shims/cache.d.ts.map +1 -1
  108. package/dist/shims/cache.js +18 -17
  109. package/dist/shims/cache.js.map +1 -1
  110. package/dist/shims/constants.d.ts.map +1 -1
  111. package/dist/shims/constants.js +1 -6
  112. package/dist/shims/constants.js.map +1 -1
  113. package/dist/shims/dynamic.d.ts.map +1 -1
  114. package/dist/shims/dynamic.js +1 -1
  115. package/dist/shims/dynamic.js.map +1 -1
  116. package/dist/shims/error-boundary.d.ts.map +1 -1
  117. package/dist/shims/error-boundary.js +2 -3
  118. package/dist/shims/error-boundary.js.map +1 -1
  119. package/dist/shims/error.d.ts.map +1 -1
  120. package/dist/shims/error.js +1 -3
  121. package/dist/shims/error.js.map +1 -1
  122. package/dist/shims/fetch-cache.d.ts.map +1 -1
  123. package/dist/shims/fetch-cache.js +53 -29
  124. package/dist/shims/fetch-cache.js.map +1 -1
  125. package/dist/shims/font-google-base.d.ts.map +1 -1
  126. package/dist/shims/font-google-base.js +16 -4
  127. package/dist/shims/font-google-base.js.map +1 -1
  128. package/dist/shims/font-google.d.ts +1 -1
  129. package/dist/shims/font-google.d.ts.map +1 -1
  130. package/dist/shims/font-google.generated.d.ts.map +1 -1
  131. package/dist/shims/font-google.generated.js +412 -206
  132. package/dist/shims/font-google.generated.js.map +1 -1
  133. package/dist/shims/font-google.js +1 -1
  134. package/dist/shims/font-google.js.map +1 -1
  135. package/dist/shims/font-local.d.ts.map +1 -1
  136. package/dist/shims/font-local.js +13 -3
  137. package/dist/shims/font-local.js.map +1 -1
  138. package/dist/shims/form.d.ts.map +1 -1
  139. package/dist/shims/form.js +2 -2
  140. package/dist/shims/form.js.map +1 -1
  141. package/dist/shims/head.d.ts.map +1 -1
  142. package/dist/shims/head.js +10 -8
  143. package/dist/shims/head.js.map +1 -1
  144. package/dist/shims/headers.d.ts +23 -5
  145. package/dist/shims/headers.d.ts.map +1 -1
  146. package/dist/shims/headers.js +97 -37
  147. package/dist/shims/headers.js.map +1 -1
  148. package/dist/shims/image.d.ts.map +1 -1
  149. package/dist/shims/image.js +35 -8
  150. package/dist/shims/image.js.map +1 -1
  151. package/dist/shims/legacy-image.d.ts.map +1 -1
  152. package/dist/shims/legacy-image.js +1 -1
  153. package/dist/shims/legacy-image.js.map +1 -1
  154. package/dist/shims/link.d.ts.map +1 -1
  155. package/dist/shims/link.js +29 -15
  156. package/dist/shims/link.js.map +1 -1
  157. package/dist/shims/metadata.d.ts +12 -2
  158. package/dist/shims/metadata.d.ts.map +1 -1
  159. package/dist/shims/metadata.js +10 -8
  160. package/dist/shims/metadata.js.map +1 -1
  161. package/dist/shims/navigation-state.d.ts.map +1 -1
  162. package/dist/shims/navigation-state.js +3 -2
  163. package/dist/shims/navigation-state.js.map +1 -1
  164. package/dist/shims/navigation.d.ts.map +1 -1
  165. package/dist/shims/navigation.js +26 -19
  166. package/dist/shims/navigation.js.map +1 -1
  167. package/dist/shims/request-context.d.ts +50 -0
  168. package/dist/shims/request-context.d.ts.map +1 -0
  169. package/dist/shims/request-context.js +59 -0
  170. package/dist/shims/request-context.js.map +1 -0
  171. package/dist/shims/router-state.d.ts.map +1 -1
  172. package/dist/shims/router-state.js +2 -1
  173. package/dist/shims/router-state.js.map +1 -1
  174. package/dist/shims/router.d.ts.map +1 -1
  175. package/dist/shims/router.js +18 -25
  176. package/dist/shims/router.js.map +1 -1
  177. package/dist/shims/script.d.ts.map +1 -1
  178. package/dist/shims/script.js.map +1 -1
  179. package/dist/shims/server.d.ts +13 -0
  180. package/dist/shims/server.d.ts.map +1 -1
  181. package/dist/shims/server.js +100 -34
  182. package/dist/shims/server.js.map +1 -1
  183. package/dist/shims/url-utils.d.ts.map +1 -1
  184. package/dist/shims/url-utils.js +1 -3
  185. package/dist/shims/url-utils.js.map +1 -1
  186. package/dist/utils/base-path.d.ts +17 -0
  187. package/dist/utils/base-path.d.ts.map +1 -0
  188. package/dist/utils/base-path.js +25 -0
  189. package/dist/utils/base-path.js.map +1 -0
  190. package/dist/utils/project.d.ts.map +1 -1
  191. package/dist/utils/project.js +2 -4
  192. package/dist/utils/project.js.map +1 -1
  193. package/dist/utils/query.d.ts.map +1 -1
  194. package/dist/utils/query.js +3 -1
  195. package/dist/utils/query.js.map +1 -1
  196. package/package.json +47 -33
@@ -10,7 +10,7 @@
10
10
  import fs from "node:fs";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { generateDevOriginCheckCode } from "../server/dev-origin-check.js";
13
- import { generateSafeRegExpCode, generateMiddlewareMatcherCode, generateNormalizePathCode } from "../server/middleware-codegen.js";
13
+ import { generateSafeRegExpCode, generateMiddlewareMatcherCode, generateNormalizePathCode, } from "../server/middleware-codegen.js";
14
14
  import { isProxyFile } from "../server/middleware.js";
15
15
  // Pre-computed absolute paths for generated-code imports. The virtual RSC
16
16
  // entry can't use relative imports (it has no real file location), so we
@@ -96,7 +96,7 @@ export function generateRscEntry(appDir, routes, middlewarePath, metadataRoutes,
96
96
  const routeEntries = routes.map((route) => {
97
97
  const layoutVars = route.layouts.map((l) => getImportVar(l));
98
98
  const templateVars = route.templates.map((t) => getImportVar(t));
99
- const notFoundVars = (route.notFoundPaths || []).map((nf) => nf ? getImportVar(nf) : "null");
99
+ const notFoundVars = (route.notFoundPaths || []).map((nf) => (nf ? getImportVar(nf) : "null"));
100
100
  const slotEntries = route.parallelSlots.map((slot) => {
101
101
  const interceptEntries = slot.interceptingRoutes.map((ir) => {
102
102
  return ` {
@@ -121,6 +121,7 @@ ${interceptEntries.join(",\n")}
121
121
  const layoutErrorVars = (route.layoutErrorPaths || []).map((ep) => ep ? getImportVar(ep) : "null");
122
122
  return ` {
123
123
  pattern: ${JSON.stringify(route.pattern)},
124
+ patternParts: ${JSON.stringify(route.patternParts)},
124
125
  isDynamic: ${route.isDynamic},
125
126
  params: ${JSON.stringify(route.params)},
126
127
  page: ${route.pagePath ? getImportVar(route.pagePath) : "null"},
@@ -143,18 +144,12 @@ ${slotEntries.join(",\n")}
143
144
  });
144
145
  // Find root not-found/forbidden/unauthorized pages and root layouts for global error handling
145
146
  const rootRoute = routes.find((r) => r.pattern === "/");
146
- const rootNotFoundVar = rootRoute?.notFoundPath
147
- ? getImportVar(rootRoute.notFoundPath)
148
- : null;
149
- const rootForbiddenVar = rootRoute?.forbiddenPath
150
- ? getImportVar(rootRoute.forbiddenPath)
151
- : null;
147
+ const rootNotFoundVar = rootRoute?.notFoundPath ? getImportVar(rootRoute.notFoundPath) : null;
148
+ const rootForbiddenVar = rootRoute?.forbiddenPath ? getImportVar(rootRoute.forbiddenPath) : null;
152
149
  const rootUnauthorizedVar = rootRoute?.unauthorizedPath
153
150
  ? getImportVar(rootRoute.unauthorizedPath)
154
151
  : null;
155
- const rootLayoutVars = rootRoute
156
- ? rootRoute.layouts.map((l) => getImportVar(l))
157
- : [];
152
+ const rootLayoutVars = rootRoute ? rootRoute.layouts.map((l) => getImportVar(l)) : [];
158
153
  // Global error boundary (app/global-error.tsx)
159
154
  const globalErrorVar = globalErrorPath ? getImportVar(globalErrorPath) : null;
160
155
  // Build metadata route handling
@@ -214,7 +209,7 @@ ${middlewarePath ? `import * as middlewareModule from ${JSON.stringify(middlewar
214
209
  ${instrumentationPath ? `import * as _instrumentation from ${JSON.stringify(instrumentationPath.replace(/\\/g, "/"))};` : ""}
215
210
  ${effectiveMetaRoutes.length > 0 ? `import { sitemapToXml, robotsToText, manifestToJson } from ${JSON.stringify(fileURLToPath(new URL("../server/metadata-routes.js", import.meta.url)).replace(/\\/g, "/"))};` : ""}
216
211
  import { requestContextFromRequest, matchRedirect, matchRewrite, matchHeaders, isExternalUrl, proxyExternalRequest, sanitizeDestination } from ${JSON.stringify(configMatchersPath)};
217
- import { validateCsrfOrigin, validateImageUrl, guardProtocolRelativeUrl, stripBasePath, normalizeTrailingSlash, processMiddlewareHeaders } from ${JSON.stringify(requestPipelinePath)};
212
+ import { validateCsrfOrigin, validateImageUrl, guardProtocolRelativeUrl, hasBasePath, stripBasePath, normalizeTrailingSlash, processMiddlewareHeaders } from ${JSON.stringify(requestPipelinePath)};
218
213
  import { _consumeRequestScopedCacheLife, _runWithCacheState } from "next/cache";
219
214
  import { runWithFetchCache } from "vinext/fetch-cache";
220
215
  import { runWithPrivateCache as _runWithPrivateCache } from "vinext/cache-runtime";
@@ -431,7 +426,8 @@ function createRscOnErrorHandler(request, pathname, routePath) {
431
426
 
432
427
  ${imports.join("\n")}
433
428
 
434
- ${instrumentationPath ? `// Run instrumentation register() exactly once, lazily on the first request.
429
+ ${instrumentationPath
430
+ ? `// Run instrumentation register() exactly once, lazily on the first request.
435
431
  // Previously this was a top-level await, which blocked the entire module graph
436
432
  // from finishing initialization until register() resolved — adding that latency
437
433
  // to every cold start. Moving it here preserves the "runs before any request is
@@ -460,7 +456,8 @@ async function __ensureInstrumentation() {
460
456
  __instrumentationInitialized = true;
461
457
  })();
462
458
  return __instrumentationInitPromise;
463
- }` : ""}
459
+ }`
460
+ : ""}
464
461
 
465
462
  const routes = [
466
463
  ${routeEntries.join(",\n")}
@@ -556,7 +553,8 @@ async function renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, req
556
553
  element = createElement(LayoutSegmentProvider, { childSegments: _cs }, element);
557
554
  }
558
555
  }
559
- ${globalErrorVar ? `
556
+ ${globalErrorVar
557
+ ? `
560
558
  const _GlobalErrorComponent = ${globalErrorVar}.default;
561
559
  if (_GlobalErrorComponent) {
562
560
  element = createElement(ErrorBoundary, {
@@ -564,7 +562,8 @@ async function renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, req
564
562
  children: element,
565
563
  });
566
564
  }
567
- ` : ""}
565
+ `
566
+ : ""}
568
567
  const _pathname = new URL(request.url).pathname;
569
568
  const onRenderError = createRscOnErrorHandler(
570
569
  request,
@@ -644,12 +643,14 @@ async function renderErrorBoundaryPage(route, error, isRscRequest, request, matc
644
643
  }
645
644
  }
646
645
  }
647
- ${globalErrorVar ? `
646
+ ${globalErrorVar
647
+ ? `
648
648
  if (!ErrorComponent) {
649
649
  ErrorComponent = ${globalErrorVar}?.default ?? null;
650
650
  _isGlobalError = !!ErrorComponent;
651
651
  }
652
- ` : ""}
652
+ `
653
+ : ""}
653
654
  if (!ErrorComponent) return null;
654
655
 
655
656
  const rawError = error instanceof Error ? error : new Error(String(error));
@@ -687,7 +688,8 @@ async function renderErrorBoundaryPage(route, error, isRscRequest, request, matc
687
688
  element = createElement(LayoutSegmentProvider, { childSegments: _ecs }, element);
688
689
  }
689
690
  }
690
- ${globalErrorVar ? `
691
+ ${globalErrorVar
692
+ ? `
691
693
  const _ErrGlobalComponent = ${globalErrorVar}.default;
692
694
  if (_ErrGlobalComponent) {
693
695
  element = createElement(ErrorBoundary, {
@@ -695,7 +697,8 @@ async function renderErrorBoundaryPage(route, error, isRscRequest, request, matc
695
697
  children: element,
696
698
  });
697
699
  }
698
- ` : ""}
700
+ `
701
+ : ""}
699
702
  } else {
700
703
  // For HTML (full page load) responses, wrap with layouts only.
701
704
  const _errParamsHtml = matchedParams ?? route?.params ?? {};
@@ -757,16 +760,15 @@ function matchRoute(url, routes) {
757
760
  // NOTE: Do NOT decodeURIComponent here. The caller is responsible for decoding
758
761
  // the pathname exactly once at the request entry point. Decoding again here
759
762
  // would cause inconsistent path matching between middleware and routing.
763
+ const urlParts = normalizedUrl.split("/").filter(Boolean);
760
764
  for (const route of routes) {
761
- const params = matchPattern(normalizedUrl, route.pattern);
765
+ const params = matchPattern(urlParts, route.patternParts);
762
766
  if (params !== null) return { route, params };
763
767
  }
764
768
  return null;
765
769
  }
766
770
 
767
- function matchPattern(url, pattern) {
768
- const urlParts = url.split("/").filter(Boolean);
769
- const patternParts = pattern.split("/").filter(Boolean);
771
+ function matchPattern(urlParts, patternParts) {
770
772
  const params = Object.create(null);
771
773
  for (let i = 0; i < patternParts.length; i++) {
772
774
  const pp = patternParts[i];
@@ -806,6 +808,7 @@ for (let ri = 0; ri < routes.length; ri++) {
806
808
  sourceRouteIndex: ri,
807
809
  slotName,
808
810
  targetPattern: intercept.targetPattern,
811
+ targetPatternParts: intercept.targetPattern.split("/").filter(Boolean),
809
812
  page: intercept.page,
810
813
  params: intercept.params,
811
814
  });
@@ -818,8 +821,9 @@ for (let ri = 0; ri < routes.length; ri++) {
818
821
  * Returns the match info or null.
819
822
  */
820
823
  function findIntercept(pathname) {
824
+ const urlParts = pathname.split("/").filter(Boolean);
821
825
  for (const entry of interceptLookup) {
822
- const params = matchPattern(pathname, entry.targetPattern);
826
+ const params = matchPattern(urlParts, entry.targetPatternParts);
823
827
  if (params !== null) {
824
828
  return { ...entry, matchedParams: params };
825
829
  }
@@ -1105,7 +1109,8 @@ async function buildPageElement(route, params, opts, searchParams) {
1105
1109
  // For HTML requests (initial page load), the ErrorBoundary catches during SSR
1106
1110
  // but produces double <html>/<body> (root layout + global-error). The request
1107
1111
  // handler detects this via the rscOnError flag and re-renders without layouts.
1108
- ${globalErrorVar ? `
1112
+ ${globalErrorVar
1113
+ ? `
1109
1114
  const GlobalErrorComponent = ${globalErrorVar}.default;
1110
1115
  if (GlobalErrorComponent) {
1111
1116
  element = createElement(ErrorBoundary, {
@@ -1113,7 +1118,8 @@ async function buildPageElement(route, params, opts, searchParams) {
1113
1118
  children: element,
1114
1119
  });
1115
1120
  }
1116
- ` : ""}
1121
+ `
1122
+ : ""}
1117
1123
 
1118
1124
  return element;
1119
1125
  }
@@ -1231,10 +1237,12 @@ async function __readFormDataWithLimit(request, maxBytes) {
1231
1237
  }
1232
1238
 
1233
1239
  export default async function handler(request) {
1234
- ${instrumentationPath ? `// Ensure instrumentation.register() has run before handling the first request.
1240
+ ${instrumentationPath
1241
+ ? `// Ensure instrumentation.register() has run before handling the first request.
1235
1242
  // This is a no-op after the first call (guarded by __instrumentationInitialized).
1236
1243
  await __ensureInstrumentation();
1237
- ` : ""}
1244
+ `
1245
+ : ""}
1238
1246
  // Wrap the entire request in nested AsyncLocalStorage.run() scopes to ensure
1239
1247
  // per-request isolation for all state modules. Each runWith*() creates an
1240
1248
  // ALS scope that propagates through all async continuations (including RSC
@@ -1316,10 +1324,12 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1316
1324
  }
1317
1325
  let pathname = __normalizePath(decodedUrlPathname);
1318
1326
 
1319
- ${bp ? `
1327
+ ${bp
1328
+ ? `
1320
1329
  // Strip basePath prefix
1321
1330
  pathname = stripBasePath(pathname, __basePath);
1322
- ` : ""}
1331
+ `
1332
+ : ""}
1323
1333
 
1324
1334
  // Trailing slash normalization (redirect to canonical form)
1325
1335
  const __tsRedirect = normalizeTrailingSlash(pathname, __basePath, __trailingSlash, url.search);
@@ -1334,7 +1344,9 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1334
1344
  const __redir = matchRedirect(__redirPathname, __configRedirects, __reqCtx);
1335
1345
  if (__redir) {
1336
1346
  const __redirDest = sanitizeDestination(
1337
- __basePath && !__redir.destination.startsWith(__basePath)
1347
+ __basePath &&
1348
+ !isExternalUrl(__redir.destination) &&
1349
+ !hasBasePath(__redir.destination, __basePath)
1338
1350
  ? __basePath + __redir.destination
1339
1351
  : __redir.destination
1340
1352
  );
@@ -1352,7 +1364,8 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1352
1364
  // _mwCtx (per-request container) so handler() can merge them into
1353
1365
  // every response path without module-level state that races on Workers.
1354
1366
 
1355
- ${middlewarePath ? `
1367
+ ${middlewarePath
1368
+ ? `
1356
1369
  // Run proxy/middleware if present and path matches.
1357
1370
  // Validate exports match the file type (proxy.ts vs middleware.ts), matching Next.js behavior.
1358
1371
  // https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/proxy-missing-export/proxy-missing-export.test.ts
@@ -1434,7 +1447,8 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1434
1447
  applyMiddlewareRequestHeaders(_mwCtx.headers);
1435
1448
  processMiddlewareHeaders(_mwCtx.headers);
1436
1449
  }
1437
- ` : ""}
1450
+ `
1451
+ : ""}
1438
1452
 
1439
1453
  // Build post-middleware request context for afterFiles/fallback rewrites.
1440
1454
  // These run after middleware in the App Router execution order and should
@@ -1467,6 +1481,34 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1467
1481
 
1468
1482
  // Handle metadata routes (sitemap.xml, robots.txt, manifest.webmanifest, etc.)
1469
1483
  for (const metaRoute of metadataRoutes) {
1484
+ // generateSitemaps() support — paginated sitemaps at /{prefix}/sitemap/{id}.xml
1485
+ // When a sitemap module exports generateSitemaps, the base URL (e.g. /products/sitemap.xml)
1486
+ // is no longer served. Instead, individual sitemaps are served at /products/sitemap/{id}.xml.
1487
+ if (
1488
+ metaRoute.type === "sitemap" &&
1489
+ metaRoute.isDynamic &&
1490
+ typeof metaRoute.module.generateSitemaps === "function"
1491
+ ) {
1492
+ const sitemapPrefix = metaRoute.servedUrl.slice(0, -4); // strip ".xml"
1493
+ // Match exactly /{prefix}/{id}.xml — one segment only (no slashes in id)
1494
+ if (cleanPathname.startsWith(sitemapPrefix + "/") && cleanPathname.endsWith(".xml")) {
1495
+ const rawId = cleanPathname.slice(sitemapPrefix.length + 1, -4);
1496
+ if (rawId.includes("/")) continue; // multi-segment — not a paginated sitemap
1497
+ const sitemaps = await metaRoute.module.generateSitemaps();
1498
+ const matched = sitemaps.find(function(s) { return String(s.id) === rawId; });
1499
+ if (!matched) return new Response("Not Found", { status: 404 });
1500
+ // Pass the original typed id from generateSitemaps() so numeric IDs stay numeric.
1501
+ // TODO: wrap with makeThenableParams-style Promise when upgrading to Next.js 16
1502
+ // full-Promise param semantics (id becomes Promise<string> in v16).
1503
+ const result = await metaRoute.module.default({ id: matched.id });
1504
+ if (result instanceof Response) return result;
1505
+ return new Response(sitemapToXml(result), {
1506
+ headers: { "Content-Type": metaRoute.contentType },
1507
+ });
1508
+ }
1509
+ // Skip — the base servedUrl is not served when generateSitemaps exists
1510
+ continue;
1511
+ }
1470
1512
  if (cleanPathname === metaRoute.servedUrl) {
1471
1513
  if (metaRoute.isDynamic) {
1472
1514
  // Dynamic metadata route — call the default export and serialize
@@ -1760,10 +1802,17 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1760
1802
  if (typeof handlerFn === "function") {
1761
1803
  try {
1762
1804
  const response = await handlerFn(request, { params });
1805
+ const dynamicUsedInHandler = consumeDynamicUsage();
1763
1806
 
1764
1807
  // Apply Cache-Control from route segment config (export const revalidate = N).
1765
- // Next.js sets s-maxage on GET route handlers with a numeric revalidate value.
1766
- if (revalidateSeconds !== null && (method === "GET" || isAutoHead) && !response.headers.has("cache-control")) {
1808
+ // Runtime request APIs like headers() / cookies() make GET handlers dynamic,
1809
+ // so only attach ISR headers when the handler stayed static.
1810
+ if (
1811
+ revalidateSeconds !== null &&
1812
+ !dynamicUsedInHandler &&
1813
+ (method === "GET" || isAutoHead) &&
1814
+ !response.headers.has("cache-control")
1815
+ ) {
1767
1816
  response.headers.set("cache-control", "s-maxage=" + revalidateSeconds + ", stale-while-revalidate");
1768
1817
  }
1769
1818
 
@@ -2271,7 +2320,8 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2271
2320
  // the HTML output has double <html>/<body> (root layout + global-error.tsx).
2272
2321
  // Discard it and re-render using renderErrorBoundaryPage which skips layouts
2273
2322
  // when the error falls through to global-error.tsx.
2274
- ${globalErrorVar ? `
2323
+ ${globalErrorVar
2324
+ ? `
2275
2325
  if (_rscErrorForRerender && !isRscRequest) {
2276
2326
  const _hasLocalBoundary = !!(route?.error?.default) || !!(route?.errors && route.errors.some(function(e) { return e?.default; }));
2277
2327
  if (!_hasLocalBoundary) {
@@ -2279,7 +2329,8 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2279
2329
  if (cleanResp) return cleanResp;
2280
2330
  }
2281
2331
  }
2282
- ` : ""}
2332
+ `
2333
+ : ""}
2283
2334
 
2284
2335
  // Check for draftMode Set-Cookie header (from draftMode().enable()/disable())
2285
2336
  const draftCookie = getDraftModeCookieHeader();