vinext 0.0.26 → 0.0.28

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 (227) hide show
  1. package/README.md +89 -85
  2. package/dist/build/static-export.d.ts +1 -1
  3. package/dist/build/static-export.d.ts.map +1 -1
  4. package/dist/build/static-export.js +5 -9
  5. package/dist/build/static-export.js.map +1 -1
  6. package/dist/check.d.ts.map +1 -1
  7. package/dist/check.js +152 -48
  8. package/dist/check.js.map +1 -1
  9. package/dist/cli.js +10 -11
  10. package/dist/cli.js.map +1 -1
  11. package/dist/cloudflare/kv-cache-handler.d.ts +43 -1
  12. package/dist/cloudflare/kv-cache-handler.d.ts.map +1 -1
  13. package/dist/cloudflare/kv-cache-handler.js +135 -44
  14. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  15. package/dist/cloudflare/tpr.d.ts.map +1 -1
  16. package/dist/cloudflare/tpr.js +15 -4
  17. package/dist/cloudflare/tpr.js.map +1 -1
  18. package/dist/config/config-matchers.d.ts +28 -0
  19. package/dist/config/config-matchers.d.ts.map +1 -1
  20. package/dist/config/config-matchers.js +353 -79
  21. package/dist/config/config-matchers.js.map +1 -1
  22. package/dist/config/dotenv.d.ts.map +1 -1
  23. package/dist/config/dotenv.js +1 -6
  24. package/dist/config/dotenv.js.map +1 -1
  25. package/dist/config/next-config.d.ts +7 -0
  26. package/dist/config/next-config.d.ts.map +1 -1
  27. package/dist/config/next-config.js +44 -19
  28. package/dist/config/next-config.js.map +1 -1
  29. package/dist/deploy.d.ts +1 -1
  30. package/dist/deploy.d.ts.map +1 -1
  31. package/dist/deploy.js +81 -48
  32. package/dist/deploy.js.map +1 -1
  33. package/dist/entries/app-rsc-entry.d.ts +3 -1
  34. package/dist/entries/app-rsc-entry.d.ts.map +1 -1
  35. package/dist/entries/app-rsc-entry.js +584 -113
  36. package/dist/entries/app-rsc-entry.js.map +1 -1
  37. package/dist/entries/pages-client-entry.d.ts.map +1 -1
  38. package/dist/entries/pages-client-entry.js +5 -3
  39. package/dist/entries/pages-client-entry.js.map +1 -1
  40. package/dist/entries/pages-server-entry.d.ts.map +1 -1
  41. package/dist/entries/pages-server-entry.js +100 -32
  42. package/dist/entries/pages-server-entry.js.map +1 -1
  43. package/dist/index.d.ts +24 -8
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +327 -154
  46. package/dist/index.js.map +1 -1
  47. package/dist/init.d.ts.map +1 -1
  48. package/dist/init.js +6 -5
  49. package/dist/init.js.map +1 -1
  50. package/dist/plugins/client-reference-dedup.d.ts +19 -0
  51. package/dist/plugins/client-reference-dedup.d.ts.map +1 -0
  52. package/dist/plugins/client-reference-dedup.js +96 -0
  53. package/dist/plugins/client-reference-dedup.js.map +1 -0
  54. package/dist/routing/app-router.d.ts +2 -0
  55. package/dist/routing/app-router.d.ts.map +1 -1
  56. package/dist/routing/app-router.js +70 -107
  57. package/dist/routing/app-router.js.map +1 -1
  58. package/dist/routing/file-matcher.d.ts.map +1 -1
  59. package/dist/routing/file-matcher.js.map +1 -1
  60. package/dist/routing/pages-router.d.ts +3 -1
  61. package/dist/routing/pages-router.d.ts.map +1 -1
  62. package/dist/routing/pages-router.js +33 -18
  63. package/dist/routing/pages-router.js.map +1 -1
  64. package/dist/routing/route-validation.d.ts +8 -0
  65. package/dist/routing/route-validation.d.ts.map +1 -0
  66. package/dist/routing/route-validation.js +124 -0
  67. package/dist/routing/route-validation.js.map +1 -0
  68. package/dist/routing/utils.d.ts.map +1 -1
  69. package/dist/routing/utils.js.map +1 -1
  70. package/dist/server/api-handler.d.ts.map +1 -1
  71. package/dist/server/api-handler.js +31 -9
  72. package/dist/server/api-handler.js.map +1 -1
  73. package/dist/server/app-router-entry.d.ts +3 -2
  74. package/dist/server/app-router-entry.d.ts.map +1 -1
  75. package/dist/server/app-router-entry.js +8 -4
  76. package/dist/server/app-router-entry.js.map +1 -1
  77. package/dist/server/dev-module-runner.d.ts.map +1 -1
  78. package/dist/server/dev-module-runner.js +1 -1
  79. package/dist/server/dev-module-runner.js.map +1 -1
  80. package/dist/server/dev-origin-check.d.ts.map +1 -1
  81. package/dist/server/dev-origin-check.js.map +1 -1
  82. package/dist/server/dev-server.d.ts.map +1 -1
  83. package/dist/server/dev-server.js +39 -21
  84. package/dist/server/dev-server.js.map +1 -1
  85. package/dist/server/image-optimization.d.ts.map +1 -1
  86. package/dist/server/image-optimization.js.map +1 -1
  87. package/dist/server/instrumentation.js +1 -1
  88. package/dist/server/instrumentation.js.map +1 -1
  89. package/dist/server/isr-cache.d.ts +5 -1
  90. package/dist/server/isr-cache.d.ts.map +1 -1
  91. package/dist/server/isr-cache.js +13 -3
  92. package/dist/server/isr-cache.js.map +1 -1
  93. package/dist/server/metadata-routes.d.ts +8 -2
  94. package/dist/server/metadata-routes.d.ts.map +1 -1
  95. package/dist/server/metadata-routes.js +78 -45
  96. package/dist/server/metadata-routes.js.map +1 -1
  97. package/dist/server/middleware-codegen.d.ts +1 -1
  98. package/dist/server/middleware-codegen.d.ts.map +1 -1
  99. package/dist/server/middleware-codegen.js +177 -22
  100. package/dist/server/middleware-codegen.js.map +1 -1
  101. package/dist/server/middleware-request-headers.d.ts +9 -0
  102. package/dist/server/middleware-request-headers.d.ts.map +1 -0
  103. package/dist/server/middleware-request-headers.js +77 -0
  104. package/dist/server/middleware-request-headers.js.map +1 -0
  105. package/dist/server/middleware.d.ts +9 -8
  106. package/dist/server/middleware.d.ts.map +1 -1
  107. package/dist/server/middleware.js +112 -32
  108. package/dist/server/middleware.js.map +1 -1
  109. package/dist/server/normalize-path.js.map +1 -1
  110. package/dist/server/prod-server.d.ts +1 -1
  111. package/dist/server/prod-server.d.ts.map +1 -1
  112. package/dist/server/prod-server.js +127 -82
  113. package/dist/server/prod-server.js.map +1 -1
  114. package/dist/server/request-pipeline.d.ts +2 -1
  115. package/dist/server/request-pipeline.d.ts.map +1 -1
  116. package/dist/server/request-pipeline.js +5 -7
  117. package/dist/server/request-pipeline.js.map +1 -1
  118. package/dist/shims/cache-runtime.d.ts.map +1 -1
  119. package/dist/shims/cache-runtime.js +21 -16
  120. package/dist/shims/cache-runtime.js.map +1 -1
  121. package/dist/shims/cache.d.ts +2 -0
  122. package/dist/shims/cache.d.ts.map +1 -1
  123. package/dist/shims/cache.js +38 -25
  124. package/dist/shims/cache.js.map +1 -1
  125. package/dist/shims/constants.d.ts.map +1 -1
  126. package/dist/shims/constants.js +1 -6
  127. package/dist/shims/constants.js.map +1 -1
  128. package/dist/shims/dynamic.d.ts.map +1 -1
  129. package/dist/shims/dynamic.js +1 -1
  130. package/dist/shims/dynamic.js.map +1 -1
  131. package/dist/shims/error-boundary.d.ts.map +1 -1
  132. package/dist/shims/error-boundary.js +2 -3
  133. package/dist/shims/error-boundary.js.map +1 -1
  134. package/dist/shims/error.d.ts.map +1 -1
  135. package/dist/shims/error.js +1 -3
  136. package/dist/shims/error.js.map +1 -1
  137. package/dist/shims/fetch-cache.d.ts.map +1 -1
  138. package/dist/shims/fetch-cache.js +57 -30
  139. package/dist/shims/fetch-cache.js.map +1 -1
  140. package/dist/shims/font-google-base.d.ts.map +1 -1
  141. package/dist/shims/font-google-base.js +16 -4
  142. package/dist/shims/font-google-base.js.map +1 -1
  143. package/dist/shims/font-google.d.ts +1 -1
  144. package/dist/shims/font-google.d.ts.map +1 -1
  145. package/dist/shims/font-google.generated.d.ts.map +1 -1
  146. package/dist/shims/font-google.generated.js +412 -206
  147. package/dist/shims/font-google.generated.js.map +1 -1
  148. package/dist/shims/font-google.js +1 -1
  149. package/dist/shims/font-google.js.map +1 -1
  150. package/dist/shims/font-local.d.ts.map +1 -1
  151. package/dist/shims/font-local.js +13 -3
  152. package/dist/shims/font-local.js.map +1 -1
  153. package/dist/shims/form.d.ts.map +1 -1
  154. package/dist/shims/form.js +105 -10
  155. package/dist/shims/form.js.map +1 -1
  156. package/dist/shims/head.d.ts.map +1 -1
  157. package/dist/shims/head.js +10 -8
  158. package/dist/shims/head.js.map +1 -1
  159. package/dist/shims/headers.d.ts +34 -8
  160. package/dist/shims/headers.d.ts.map +1 -1
  161. package/dist/shims/headers.js +268 -53
  162. package/dist/shims/headers.js.map +1 -1
  163. package/dist/shims/image.d.ts.map +1 -1
  164. package/dist/shims/image.js +35 -8
  165. package/dist/shims/image.js.map +1 -1
  166. package/dist/shims/internal/parse-cookie-header.d.ts +12 -0
  167. package/dist/shims/internal/parse-cookie-header.d.ts.map +1 -0
  168. package/dist/shims/internal/parse-cookie-header.js +32 -0
  169. package/dist/shims/internal/parse-cookie-header.js.map +1 -0
  170. package/dist/shims/legacy-image.d.ts.map +1 -1
  171. package/dist/shims/legacy-image.js +1 -1
  172. package/dist/shims/legacy-image.js.map +1 -1
  173. package/dist/shims/link.d.ts +2 -1
  174. package/dist/shims/link.d.ts.map +1 -1
  175. package/dist/shims/link.js +37 -17
  176. package/dist/shims/link.js.map +1 -1
  177. package/dist/shims/metadata.d.ts +12 -2
  178. package/dist/shims/metadata.d.ts.map +1 -1
  179. package/dist/shims/metadata.js +10 -8
  180. package/dist/shims/metadata.js.map +1 -1
  181. package/dist/shims/navigation-state.d.ts.map +1 -1
  182. package/dist/shims/navigation-state.js +3 -2
  183. package/dist/shims/navigation-state.js.map +1 -1
  184. package/dist/shims/navigation.d.ts +3 -7
  185. package/dist/shims/navigation.d.ts.map +1 -1
  186. package/dist/shims/navigation.js +46 -29
  187. package/dist/shims/navigation.js.map +1 -1
  188. package/dist/shims/readonly-url-search-params.d.ts +11 -0
  189. package/dist/shims/readonly-url-search-params.d.ts.map +1 -0
  190. package/dist/shims/readonly-url-search-params.js +24 -0
  191. package/dist/shims/readonly-url-search-params.js.map +1 -0
  192. package/dist/shims/request-context.d.ts +50 -0
  193. package/dist/shims/request-context.d.ts.map +1 -0
  194. package/dist/shims/request-context.js +59 -0
  195. package/dist/shims/request-context.js.map +1 -0
  196. package/dist/shims/router-state.d.ts.map +1 -1
  197. package/dist/shims/router-state.js +2 -1
  198. package/dist/shims/router-state.js.map +1 -1
  199. package/dist/shims/router.d.ts +4 -3
  200. package/dist/shims/router.d.ts.map +1 -1
  201. package/dist/shims/router.js +59 -53
  202. package/dist/shims/router.js.map +1 -1
  203. package/dist/shims/script.d.ts.map +1 -1
  204. package/dist/shims/script.js.map +1 -1
  205. package/dist/shims/server.d.ts +14 -1
  206. package/dist/shims/server.d.ts.map +1 -1
  207. package/dist/shims/server.js +107 -47
  208. package/dist/shims/server.js.map +1 -1
  209. package/dist/shims/url-utils.d.ts.map +1 -1
  210. package/dist/shims/url-utils.js +1 -3
  211. package/dist/shims/url-utils.js.map +1 -1
  212. package/dist/utils/base-path.d.ts +17 -0
  213. package/dist/utils/base-path.d.ts.map +1 -0
  214. package/dist/utils/base-path.js +25 -0
  215. package/dist/utils/base-path.js.map +1 -0
  216. package/dist/utils/manifest-paths.d.ts +4 -0
  217. package/dist/utils/manifest-paths.d.ts.map +1 -0
  218. package/dist/utils/manifest-paths.js +20 -0
  219. package/dist/utils/manifest-paths.js.map +1 -0
  220. package/dist/utils/project.d.ts.map +1 -1
  221. package/dist/utils/project.js +2 -4
  222. package/dist/utils/project.js.map +1 -1
  223. package/dist/utils/query.d.ts +9 -0
  224. package/dist/utils/query.d.ts.map +1 -1
  225. package/dist/utils/query.js +59 -7
  226. package/dist/utils/query.js.map +1 -1
  227. package/package.json +47 -33
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { loadEnv, parseAst } from "vite";
2
- import { pagesRouter, apiRouter, invalidateRouteCache, matchRoute } from "./routing/pages-router.js";
2
+ import { pagesRouter, apiRouter, invalidateRouteCache, matchRoute, } from "./routing/pages-router.js";
3
3
  import { generateServerEntry as _generateServerEntry } from "./entries/pages-server-entry.js";
4
4
  import { generateClientEntry as _generateClientEntry } from "./entries/pages-client-entry.js";
5
5
  import { appRouter, invalidateAppRouteCache } from "./routing/app-router.js";
@@ -19,8 +19,12 @@ import { PHASE_PRODUCTION_BUILD, PHASE_DEVELOPMENT_SERVER } from "./shims/consta
19
19
  import { validateDevRequest } from "./server/dev-origin-check.js";
20
20
  import { isExternalUrl, proxyExternalRequest, matchHeaders, matchRedirect, matchRewrite, requestContextFromRequest, sanitizeDestination, } from "./config/config-matchers.js";
21
21
  import { scanMetadataFiles } from "./server/metadata-routes.js";
22
+ import { buildRequestHeadersFromMiddlewareResponse } from "./server/middleware-request-headers.js";
22
23
  import { detectPackageManager } from "./utils/project.js";
24
+ import { manifestFileWithBase, manifestFilesWithBase, normalizeManifestFile, } from "./utils/manifest-paths.js";
25
+ import { hasBasePath } from "./utils/base-path.js";
23
26
  import { asyncHooksStubPlugin } from "./plugins/async-hooks-stub.js";
27
+ import { clientReferenceDedupPlugin } from "./plugins/client-reference-dedup.js";
24
28
  import { hasWranglerConfig, formatMissingCloudflarePluginError } from "./deploy.js";
25
29
  import tsconfigPaths from "vite-tsconfig-paths";
26
30
  import react from "@vitejs/plugin-react";
@@ -66,7 +70,11 @@ async function fetchAndCacheFont(cssUrl, family, cacheDir) {
66
70
  while ((urlMatch = urlRe.exec(css)) !== null) {
67
71
  const fontUrl = urlMatch[1];
68
72
  if (!urls.has(fontUrl)) {
69
- const ext = fontUrl.includes(".woff2") ? ".woff2" : fontUrl.includes(".woff") ? ".woff" : ".ttf";
73
+ const ext = fontUrl.includes(".woff2")
74
+ ? ".woff2"
75
+ : fontUrl.includes(".woff")
76
+ ? ".woff"
77
+ : ".ttf";
70
78
  const fileHash = createHash("md5").update(fontUrl).digest("hex").slice(0, 8);
71
79
  urls.set(fontUrl, `${family.toLowerCase().replace(/\s+/g, "-")}-${fileHash}${ext}`);
72
80
  }
@@ -133,7 +141,9 @@ function extractStaticValue(node) {
133
141
  return node.value;
134
142
  case "UnaryExpression":
135
143
  // Handle negative numbers: -1, -3.14
136
- if (node.operator === "-" && node.argument?.type === "Literal" && typeof node.argument.value === "number") {
144
+ if (node.operator === "-" &&
145
+ node.argument?.type === "Literal" &&
146
+ typeof node.argument.value === "number") {
137
147
  return -node.argument.value;
138
148
  }
139
149
  return undefined;
@@ -244,7 +254,9 @@ async function resolvePostcssStringPlugins(projectRoot) {
244
254
  // Load the config file
245
255
  let config;
246
256
  try {
247
- if (configPath.endsWith(".json") || configPath.endsWith(".yaml") || configPath.endsWith(".yml")) {
257
+ if (configPath.endsWith(".json") ||
258
+ configPath.endsWith(".yaml") ||
259
+ configPath.endsWith(".yml")) {
248
260
  // JSON/YAML configs use object form — postcss-load-config handles these fine
249
261
  return undefined;
250
262
  }
@@ -267,8 +279,7 @@ async function resolvePostcssStringPlugins(projectRoot) {
267
279
  // (either bare strings or tuple form ["plugin-name", { options }])
268
280
  if (!config || !Array.isArray(config.plugins))
269
281
  return undefined;
270
- const hasStringPlugins = config.plugins.some((p) => typeof p === "string" ||
271
- (Array.isArray(p) && typeof p[0] === "string"));
282
+ const hasStringPlugins = config.plugins.some((p) => typeof p === "string" || (Array.isArray(p) && typeof p[0] === "string"));
272
283
  if (!hasStringPlugins)
273
284
  return undefined;
274
285
  // Resolve string plugin names to actual plugin functions
@@ -366,9 +377,7 @@ function clientManualChunks(id) {
366
377
  const pkg = getPackageName(id);
367
378
  if (!pkg)
368
379
  return undefined;
369
- if (pkg === "react" ||
370
- pkg === "react-dom" ||
371
- pkg === "scheduler") {
380
+ if (pkg === "react" || pkg === "react-dom" || pkg === "scheduler") {
372
381
  return "framework";
373
382
  }
374
383
  // Let Rollup handle all other vendor code via its default
@@ -495,6 +504,57 @@ function computeLazyChunks(buildManifest) {
495
504
  }
496
505
  return lazyChunks;
497
506
  }
507
+ function normalizeManifestModuleId(moduleId, root) {
508
+ const normalizedId = moduleId.replace(/\\/g, "/");
509
+ const isWindowsAbsolute = /^[a-zA-Z]:[\\/]/.test(moduleId) || moduleId.startsWith("\\\\");
510
+ if (isWindowsAbsolute) {
511
+ const relativeId = path.win32.relative(root, moduleId).replace(/\\/g, "/");
512
+ if (!relativeId || relativeId.startsWith("../"))
513
+ return normalizedId;
514
+ return relativeId;
515
+ }
516
+ if (!path.isAbsolute(moduleId))
517
+ return normalizedId;
518
+ const relativeId = path.relative(root, moduleId).replace(/\\/g, "/");
519
+ if (!relativeId || relativeId.startsWith("../"))
520
+ return normalizedId;
521
+ return relativeId;
522
+ }
523
+ function augmentSsrManifestFromBundle(ssrManifest, bundle, root, base = "/") {
524
+ const nextManifest = Object.fromEntries(Object.entries(ssrManifest).map(([key, files]) => [
525
+ key,
526
+ new Set(files.map((file) => normalizeManifestFile(file))),
527
+ ]));
528
+ for (const item of Object.values(bundle)) {
529
+ if (item.type !== "chunk")
530
+ continue;
531
+ const chunk = item;
532
+ const files = new Set();
533
+ files.add(manifestFileWithBase(chunk.fileName, base));
534
+ for (const importedFile of chunk.imports ?? []) {
535
+ files.add(manifestFileWithBase(importedFile, base));
536
+ }
537
+ for (const cssFile of chunk.viteMetadata?.importedCss ?? []) {
538
+ files.add(manifestFileWithBase(cssFile, base));
539
+ }
540
+ for (const assetFile of chunk.viteMetadata?.importedAssets ?? []) {
541
+ files.add(manifestFileWithBase(assetFile, base));
542
+ }
543
+ for (const moduleId of Object.keys(chunk.modules ?? {})) {
544
+ const key = normalizeManifestModuleId(moduleId, root);
545
+ if (key.startsWith("node_modules/") || key.includes("/node_modules/"))
546
+ continue;
547
+ if (key.startsWith("\0"))
548
+ continue;
549
+ if (!nextManifest[key])
550
+ nextManifest[key] = new Set();
551
+ for (const file of files) {
552
+ nextManifest[key].add(file);
553
+ }
554
+ }
555
+ }
556
+ return Object.fromEntries(Object.entries(nextManifest).map(([key, files]) => [key, [...files]]));
557
+ }
498
558
  export default function vinext(options = {}) {
499
559
  let root;
500
560
  let pagesDir;
@@ -566,11 +626,12 @@ export default function vinext(options = {}) {
566
626
  if (earlyAppDirExists && autoRsc) {
567
627
  if (!resolvedRscPath) {
568
628
  throw new Error("vinext: App Router detected but @vitejs/plugin-rsc is not installed.\n" +
569
- "Run: " + detectPackageManager(process.cwd()) + " @vitejs/plugin-rsc");
629
+ "Run: " +
630
+ detectPackageManager(process.cwd()) +
631
+ " @vitejs/plugin-rsc");
570
632
  }
571
633
  const rscImport = import(pathToFileURL(resolvedRscPath).href);
572
- rscPluginPromise = rscImport
573
- .then((mod) => {
634
+ rscPluginPromise = rscImport.then((mod) => {
574
635
  const rsc = mod.default;
575
636
  return rsc({
576
637
  entries: {
@@ -585,9 +646,7 @@ export default function vinext(options = {}) {
585
646
  // Shared state for the MDX proxy plugin. Populated during config() if MDX
586
647
  // files are detected and @mdx-js/rollup is installed.
587
648
  let mdxDelegate = null;
588
- const reactPlugin = options.react === false
589
- ? false
590
- : react(options.react === true ? undefined : options.react);
649
+ const reactPlugin = options.react === false ? false : react(options.react === true ? undefined : options.react);
591
650
  const plugins = [
592
651
  // Resolve tsconfig paths/baseUrl aliases so real-world Next.js repos
593
652
  // that use @/*, #/*, or baseUrl imports work out of the box.
@@ -689,7 +748,9 @@ export default function vinext(options = {}) {
689
748
  // server-side validation. Matches Next.js behavior: only configured
690
749
  // sizes are accepted by the image optimization endpoint.
691
750
  {
692
- const deviceSizes = nextConfig.images?.deviceSizes ?? [640, 750, 828, 1080, 1200, 1920, 2048, 3840];
751
+ const deviceSizes = nextConfig.images?.deviceSizes ?? [
752
+ 640, 750, 828, 1080, 1200, 1920, 2048, 3840,
753
+ ];
693
754
  const imageSizes = nextConfig.images?.imageSizes ?? [16, 32, 48, 64, 96, 128, 256, 384];
694
755
  defines["process.env.__VINEXT_IMAGE_DEVICE_SIZES"] = JSON.stringify(JSON.stringify(deviceSizes));
695
756
  defines["process.env.__VINEXT_IMAGE_SIZES"] = JSON.stringify(JSON.stringify(imageSizes));
@@ -701,6 +762,11 @@ export default function vinext(options = {}) {
701
762
  // __prerender_bypass cookie is consistent across all server
702
763
  // instances (e.g. multiple Cloudflare Workers isolates).
703
764
  defines["process.env.__VINEXT_DRAFT_SECRET"] = JSON.stringify(crypto.randomUUID());
765
+ // Build ID — resolved from next.config generateBuildId() or random UUID.
766
+ // Exposed so server entries and the next/server shim can inject it.
767
+ // Also used to namespace ISR cache keys so old cached entries from a
768
+ // previous deploy are never served by the new one.
769
+ defines["process.env.__VINEXT_BUILD_ID"] = JSON.stringify(nextConfig.buildId);
704
770
  // Build the shim alias map — used by both resolve.alias and resolveId
705
771
  // (resolveId handles .js extension variants for libraries like nuqs)
706
772
  nextShimMap = {
@@ -770,8 +836,14 @@ export default function vinext(options = {}) {
770
836
  }
771
837
  }
772
838
  flattenPlugins(config.plugins ?? []);
773
- hasCloudflarePlugin = pluginsFlat.some((p) => p && typeof p === "object" && typeof p.name === "string" && (p.name === "vite-plugin-cloudflare" || p.name.startsWith("vite-plugin-cloudflare:")));
774
- hasNitroPlugin = pluginsFlat.some((p) => p && typeof p === "object" && typeof p.name === "string" && (p.name === "nitro" || p.name.startsWith("nitro:")));
839
+ hasCloudflarePlugin = pluginsFlat.some((p) => p &&
840
+ typeof p === "object" &&
841
+ typeof p.name === "string" &&
842
+ (p.name === "vite-plugin-cloudflare" || p.name.startsWith("vite-plugin-cloudflare:")));
843
+ hasNitroPlugin = pluginsFlat.some((p) => p &&
844
+ typeof p === "object" &&
845
+ typeof p.name === "string" &&
846
+ (p.name === "nitro" || p.name.startsWith("nitro:")));
775
847
  // Resolve PostCSS string plugin names that Vite can't handle.
776
848
  // Next.js projects commonly use array-form plugins like
777
849
  // `plugins: ["@tailwindcss/postcss"]` which postcss-load-config
@@ -785,9 +857,12 @@ export default function vinext(options = {}) {
785
857
  }
786
858
  // Auto-inject @mdx-js/rollup when MDX files exist and no MDX plugin is
787
859
  // already configured. Applies remark/rehype plugins from next.config.
788
- const hasMdxPlugin = pluginsFlat.some((p) => p && typeof p === "object" && typeof p.name === "string" &&
860
+ const hasMdxPlugin = pluginsFlat.some((p) => p &&
861
+ typeof p === "object" &&
862
+ typeof p.name === "string" &&
789
863
  (p.name === "@mdx-js/rollup" || p.name === "mdx"));
790
- if (!hasMdxPlugin && hasMdxFiles(root, hasAppDir ? appDir : null, hasPagesDir ? pagesDir : null)) {
864
+ if (!hasMdxPlugin &&
865
+ hasMdxFiles(root, hasAppDir ? appDir : null, hasPagesDir ? pagesDir : null)) {
791
866
  try {
792
867
  const mdxRollup = await import("@mdx-js/rollup");
793
868
  const mdxFactory = mdxRollup.default ?? mdxRollup;
@@ -811,7 +886,9 @@ export default function vinext(options = {}) {
811
886
  catch {
812
887
  // @mdx-js/rollup not installed — warn but don't fail
813
888
  console.warn("[vinext] MDX files detected but @mdx-js/rollup is not installed. " +
814
- "Install it with: " + detectPackageManager(process.cwd()) + " @mdx-js/rollup");
889
+ "Install it with: " +
890
+ detectPackageManager(process.cwd()) +
891
+ " @mdx-js/rollup");
815
892
  }
816
893
  }
817
894
  // Detect if this is a standalone SSR build (set by `vite build --ssr`
@@ -893,12 +970,14 @@ export default function vinext(options = {}) {
893
970
  // Skip when targeting bundled runtimes (Cloudflare/Nitro bundle everything).
894
971
  // This also resolves extensionless-import issues in packages like
895
972
  // `validator` (see #189) by routing them through Vite's resolver.
896
- ...(hasCloudflarePlugin || hasNitroPlugin ? {} : {
897
- ssr: {
898
- external: ["react", "react-dom", "react-dom/server"],
899
- noExternal: true,
900
- },
901
- }),
973
+ ...(hasCloudflarePlugin || hasNitroPlugin
974
+ ? {}
975
+ : {
976
+ ssr: {
977
+ external: ["react", "react-dom", "react-dom/server"],
978
+ noExternal: true,
979
+ },
980
+ }),
902
981
  resolve: {
903
982
  alias: nextShimMap,
904
983
  // Dedupe React packages to prevent dual-instance errors.
@@ -906,12 +985,7 @@ export default function vinext(options = {}) {
906
985
  // brings its own React copy, multiple React instances can load,
907
986
  // causing cryptic "Invalid hook call" errors. This is a no-op
908
987
  // when only one copy exists.
909
- dedupe: [
910
- "react",
911
- "react-dom",
912
- "react/jsx-runtime",
913
- "react/jsx-dev-runtime",
914
- ],
988
+ dedupe: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
915
989
  },
916
990
  // Exclude vinext from dependency optimization so esbuild doesn't
917
991
  // scan dist files containing virtual module imports (virtual:vinext-*)
@@ -954,34 +1028,31 @@ export default function vinext(options = {}) {
954
1028
  // "Invalid hook call" from duplicate React instances).
955
1029
  // The entries must be relative to the project root.
956
1030
  const relAppDir = path.relative(root, appDir);
957
- const appEntries = [
958
- `${relAppDir}/**/*.{tsx,ts,jsx,js}`,
959
- ];
1031
+ const appEntries = [`${relAppDir}/**/*.{tsx,ts,jsx,js}`];
960
1032
  viteConfig.environments = {
961
1033
  rsc: {
962
- ...(hasCloudflarePlugin || hasNitroPlugin ? {} : {
963
- resolve: {
964
- // Externalize native/heavy packages so the RSC environment
965
- // loads them natively via Node rather than through Vite's
966
- // ESM module evaluator (which can't handle native addons).
967
- // Note: Do NOT externalize react/react-dom here they must
968
- // be bundled with the "react-server" condition for RSC.
969
- // Skip when targeting bundled runtimes (Cloudflare/Nitro).
970
- external: userSsrExternal === true ? true : [
971
- "satori",
972
- "@resvg/resvg-js",
973
- "yoga-wasm-web",
974
- ...userSsrExternal,
975
- ],
976
- // Force all node_modules through Vite's transform pipeline
977
- // so non-JS imports (CSS, images) don't hit Node's native
978
- // ESM loader. Matches Next.js behavior of bundling everything.
979
- // Packages in `external` above take precedence per Vite rules.
980
- // When user sets `ssr.external: true`, skip noExternal since
981
- // everything is already externalized.
982
- ...(userSsrExternal === true ? {} : { noExternal: true }),
983
- },
984
- }),
1034
+ ...(hasCloudflarePlugin || hasNitroPlugin
1035
+ ? {}
1036
+ : {
1037
+ resolve: {
1038
+ // Externalize native/heavy packages so the RSC environment
1039
+ // loads them natively via Node rather than through Vite's
1040
+ // ESM module evaluator (which can't handle native addons).
1041
+ // Note: Do NOT externalize react/react-dom here — they must
1042
+ // be bundled with the "react-server" condition for RSC.
1043
+ // Skip when targeting bundled runtimes (Cloudflare/Nitro).
1044
+ external: userSsrExternal === true
1045
+ ? true
1046
+ : ["satori", "@resvg/resvg-js", "yoga-wasm-web", ...userSsrExternal],
1047
+ // Force all node_modules through Vite's transform pipeline
1048
+ // so non-JS imports (CSS, images) don't hit Node's native
1049
+ // ESM loader. Matches Next.js behavior of bundling everything.
1050
+ // Packages in `external` above take precedence per Vite rules.
1051
+ // When user sets `ssr.external: true`, skip noExternal since
1052
+ // everything is already externalized.
1053
+ ...(userSsrExternal === true ? {} : { noExternal: true }),
1054
+ },
1055
+ }),
985
1056
  optimizeDeps: {
986
1057
  exclude: ["vinext", "@vercel/og"],
987
1058
  entries: appEntries,
@@ -994,17 +1065,19 @@ export default function vinext(options = {}) {
994
1065
  },
995
1066
  },
996
1067
  ssr: {
997
- ...(hasCloudflarePlugin || hasNitroPlugin ? {} : {
998
- resolve: {
999
- external: userSsrExternal === true ? true : [...userSsrExternal],
1000
- // Force all node_modules through Vite's transform pipeline
1001
- // so non-JS imports (CSS, images) don't hit Node's native
1002
- // ESM loader. Matches Next.js behavior of bundling everything.
1003
- // When user sets `ssr.external: true`, skip noExternal since
1004
- // everything is already externalized.
1005
- ...(userSsrExternal === true ? {} : { noExternal: true }),
1006
- },
1007
- }),
1068
+ ...(hasCloudflarePlugin || hasNitroPlugin
1069
+ ? {}
1070
+ : {
1071
+ resolve: {
1072
+ external: userSsrExternal === true ? true : [...userSsrExternal],
1073
+ // Force all node_modules through Vite's transform pipeline
1074
+ // so non-JS imports (CSS, images) don't hit Node's native
1075
+ // ESM loader. Matches Next.js behavior of bundling everything.
1076
+ // When user sets `ssr.external: true`, skip noExternal since
1077
+ // everything is already externalized.
1078
+ ...(userSsrExternal === true ? {} : { noExternal: true }),
1079
+ },
1080
+ }),
1008
1081
  optimizeDeps: {
1009
1082
  exclude: ["vinext", "@vercel/og"],
1010
1083
  entries: appEntries,
@@ -1137,10 +1210,12 @@ export default function vinext(options = {}) {
1137
1210
  return RESOLVED_SERVER_ENTRY;
1138
1211
  if (cleanId === VIRTUAL_CLIENT_ENTRY)
1139
1212
  return RESOLVED_CLIENT_ENTRY;
1140
- if (cleanId.endsWith("/" + VIRTUAL_SERVER_ENTRY) || cleanId.endsWith("\\" + VIRTUAL_SERVER_ENTRY)) {
1213
+ if (cleanId.endsWith("/" + VIRTUAL_SERVER_ENTRY) ||
1214
+ cleanId.endsWith("\\" + VIRTUAL_SERVER_ENTRY)) {
1141
1215
  return RESOLVED_SERVER_ENTRY;
1142
1216
  }
1143
- if (cleanId.endsWith("/" + VIRTUAL_CLIENT_ENTRY) || cleanId.endsWith("\\" + VIRTUAL_CLIENT_ENTRY)) {
1217
+ if (cleanId.endsWith("/" + VIRTUAL_CLIENT_ENTRY) ||
1218
+ cleanId.endsWith("\\" + VIRTUAL_CLIENT_ENTRY)) {
1144
1219
  return RESOLVED_CLIENT_ENTRY;
1145
1220
  }
1146
1221
  // App Router virtual modules
@@ -1150,13 +1225,16 @@ export default function vinext(options = {}) {
1150
1225
  return RESOLVED_APP_SSR_ENTRY;
1151
1226
  if (cleanId === VIRTUAL_APP_BROWSER_ENTRY)
1152
1227
  return RESOLVED_APP_BROWSER_ENTRY;
1153
- if (cleanId.endsWith("/" + VIRTUAL_RSC_ENTRY) || cleanId.endsWith("\\" + VIRTUAL_RSC_ENTRY)) {
1228
+ if (cleanId.endsWith("/" + VIRTUAL_RSC_ENTRY) ||
1229
+ cleanId.endsWith("\\" + VIRTUAL_RSC_ENTRY)) {
1154
1230
  return RESOLVED_RSC_ENTRY;
1155
1231
  }
1156
- if (cleanId.endsWith("/" + VIRTUAL_APP_SSR_ENTRY) || cleanId.endsWith("\\" + VIRTUAL_APP_SSR_ENTRY)) {
1232
+ if (cleanId.endsWith("/" + VIRTUAL_APP_SSR_ENTRY) ||
1233
+ cleanId.endsWith("\\" + VIRTUAL_APP_SSR_ENTRY)) {
1157
1234
  return RESOLVED_APP_SSR_ENTRY;
1158
1235
  }
1159
- if (cleanId.endsWith("/" + VIRTUAL_APP_BROWSER_ENTRY) || cleanId.endsWith("\\" + VIRTUAL_APP_BROWSER_ENTRY)) {
1236
+ if (cleanId.endsWith("/" + VIRTUAL_APP_BROWSER_ENTRY) ||
1237
+ cleanId.endsWith("\\" + VIRTUAL_APP_BROWSER_ENTRY)) {
1160
1238
  return RESOLVED_APP_BROWSER_ENTRY;
1161
1239
  }
1162
1240
  },
@@ -1182,6 +1260,7 @@ export default function vinext(options = {}) {
1182
1260
  allowedOrigins: nextConfig?.serverActionsAllowedOrigins,
1183
1261
  allowedDevOrigins: nextConfig?.allowedDevOrigins,
1184
1262
  bodySizeLimit: nextConfig?.serverActionsBodySizeLimit,
1263
+ i18n: nextConfig?.i18n,
1185
1264
  }, instrumentationPath);
1186
1265
  }
1187
1266
  if (id === RESOLVED_APP_SSR_ENTRY && hasAppDir) {
@@ -1194,6 +1273,8 @@ export default function vinext(options = {}) {
1194
1273
  },
1195
1274
  // Stub node:async_hooks in client builds — see src/plugins/async-hooks-stub.ts
1196
1275
  asyncHooksStubPlugin,
1276
+ // Dedup client references from RSC proxy modules — see src/plugins/client-reference-dedup.ts
1277
+ clientReferenceDedupPlugin(),
1197
1278
  // Proxy plugin for @mdx-js/rollup. The real MDX plugin is created lazily
1198
1279
  // during vinext:config's config() (when MDX files are detected), but
1199
1280
  // plugins returned from config() hooks run too late in the pipeline —
@@ -1448,9 +1529,14 @@ export default function vinext(options = {}) {
1448
1529
  // loads the module. The gap between them is exactly the Vite
1449
1530
  // compile/transform cost.
1450
1531
  function _parseTiming(raw) {
1451
- const [handlerStart, inHandlerCompileMs, renderMs] = String(raw).split(",").map((v) => Number(v));
1452
- if (!Number.isNaN(handlerStart) && !Number.isNaN(inHandlerCompileMs) && inHandlerCompileMs !== -1) {
1453
- _compileMs = Math.max(0, Math.round(handlerStart - _reqStart)) + inHandlerCompileMs;
1532
+ const [handlerStart, inHandlerCompileMs, renderMs] = String(raw)
1533
+ .split(",")
1534
+ .map((v) => Number(v));
1535
+ if (!Number.isNaN(handlerStart) &&
1536
+ !Number.isNaN(inHandlerCompileMs) &&
1537
+ inHandlerCompileMs !== -1) {
1538
+ _compileMs =
1539
+ Math.max(0, Math.round(handlerStart - _reqStart)) + inHandlerCompileMs;
1454
1540
  }
1455
1541
  if (!Number.isNaN(renderMs) && renderMs !== -1) {
1456
1542
  _renderMs = renderMs;
@@ -1496,7 +1582,9 @@ export default function vinext(options = {}) {
1496
1582
  // matching what Next.js shows for soft navigations.
1497
1583
  const resolvedRenderMs = _renderMs !== undefined
1498
1584
  ? _renderMs
1499
- : (_compileMs !== undefined ? Math.max(0, Math.round(totalMs - _compileMs)) : undefined);
1585
+ : _compileMs !== undefined
1586
+ ? Math.max(0, Math.round(totalMs - _compileMs))
1587
+ : undefined;
1500
1588
  logRequest({
1501
1589
  method: req.method ?? "GET",
1502
1590
  url: logUrl,
@@ -1633,7 +1721,10 @@ export default function vinext(options = {}) {
1633
1721
  }
1634
1722
  // Normalize trailing slash based on next.config.js trailingSlash setting.
1635
1723
  // Redirect to the canonical form if needed.
1636
- if (nextConfig && pathname !== "/" && pathname !== "/api" && !pathname.startsWith("/api/")) {
1724
+ if (nextConfig &&
1725
+ pathname !== "/" &&
1726
+ pathname !== "/api" &&
1727
+ !pathname.startsWith("/api/")) {
1637
1728
  const hasTrailing = pathname.endsWith("/");
1638
1729
  if (nextConfig.trailingSlash && !hasTrailing) {
1639
1730
  // trailingSlash: true — redirect /about → /about/
@@ -1652,25 +1743,61 @@ export default function vinext(options = {}) {
1652
1743
  return;
1653
1744
  }
1654
1745
  }
1746
+ // When @cloudflare/vite-plugin is present, delegate the entire
1747
+ // Pages Router request pipeline to the Worker/miniflare side.
1748
+ // That keeps middleware, headers, redirects, rewrites, API
1749
+ // routes, and rendering in one place instead of mutating the
1750
+ // host request and forwarding post-middleware state downstream.
1751
+ if (hasCloudflarePlugin)
1752
+ return next();
1753
+ // Snapshot of req.headers before middleware runs. Used for both
1754
+ // preMiddlewareReqCtx and the middleware Request itself. Intentionally
1755
+ // captured once here — applyRequestHeadersToNodeRequest() mutates
1756
+ // req.headers later, but by then this Headers object is no longer read.
1757
+ const nodeRequestHeaders = new Headers(Object.fromEntries(Object.entries(req.headers)
1758
+ .filter(([, v]) => v !== undefined)
1759
+ .map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : String(v)])));
1760
+ const requestOrigin = `http://${req.headers.host || "localhost"}`;
1761
+ const preMiddlewareReqUrl = new URL(url, requestOrigin);
1762
+ const preMiddlewareReqCtx = requestContextFromRequest(new Request(preMiddlewareReqUrl, { headers: nodeRequestHeaders }));
1763
+ // Config redirects run before middleware, but still match against
1764
+ // the original normalized pathname and request headers/cookies.
1765
+ if (nextConfig?.redirects.length) {
1766
+ const redirected = applyRedirects(pathname, res, nextConfig.redirects, preMiddlewareReqCtx, nextConfig.basePath ?? "");
1767
+ if (redirected)
1768
+ return;
1769
+ }
1770
+ const applyRequestHeadersToNodeRequest = (nextRequestHeaders) => {
1771
+ for (const key of Object.keys(req.headers)) {
1772
+ delete req.headers[key];
1773
+ }
1774
+ for (const [key, value] of nextRequestHeaders) {
1775
+ req.headers[key] = value;
1776
+ }
1777
+ };
1778
+ let middlewareRequestHeaders = null;
1655
1779
  // Run middleware.ts if present
1656
1780
  if (middlewarePath) {
1657
1781
  // Only trust X-Forwarded-Proto when behind a trusted proxy
1658
- const devTrustProxy = process.env.VINEXT_TRUST_PROXY === "1" || (process.env.VINEXT_TRUSTED_HOSTS ?? "").split(",").some(h => h.trim());
1782
+ const devTrustProxy = process.env.VINEXT_TRUST_PROXY === "1" ||
1783
+ (process.env.VINEXT_TRUSTED_HOSTS ?? "").split(",").some((h) => h.trim());
1659
1784
  const rawProto = devTrustProxy
1660
- ? String(req.headers["x-forwarded-proto"] || "").split(",")[0].trim()
1785
+ ? String(req.headers["x-forwarded-proto"] || "")
1786
+ .split(",")[0]
1787
+ .trim()
1661
1788
  : "";
1662
1789
  const mwProto = rawProto === "https" || rawProto === "http" ? rawProto : "http";
1663
1790
  const origin = `${mwProto}://${req.headers.host || "localhost"}`;
1664
1791
  const middlewareRequest = new Request(new URL(url, origin), {
1665
1792
  method: req.method,
1666
- headers: Object.fromEntries(Object.entries(req.headers)
1667
- .filter(([, v]) => v !== undefined)
1668
- .map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : String(v)])),
1793
+ headers: nodeRequestHeaders,
1669
1794
  });
1670
- const result = await runMiddleware(getPagesRunner(), middlewarePath, middlewareRequest);
1795
+ const result = await runMiddleware(getPagesRunner(), middlewarePath, middlewareRequest, nextConfig?.i18n);
1671
1796
  if (!result.continue) {
1672
1797
  if (result.redirectUrl) {
1673
- const redirectHeaders = { Location: result.redirectUrl };
1798
+ const redirectHeaders = {
1799
+ Location: result.redirectUrl,
1800
+ };
1674
1801
  if (result.responseHeaders) {
1675
1802
  for (const [key, value] of result.responseHeaders) {
1676
1803
  const existing = redirectHeaders[key];
@@ -1704,13 +1831,21 @@ export default function vinext(options = {}) {
1704
1831
  // config has/missing conditions and downstream handlers
1705
1832
  // see middleware-modified cookies and headers.
1706
1833
  if (result.responseHeaders) {
1707
- const mwReqPrefix = "x-middleware-request-";
1708
- for (const [key, value] of result.responseHeaders) {
1709
- if (key.startsWith(mwReqPrefix)) {
1710
- const realName = key.slice(mwReqPrefix.length);
1711
- req.headers[realName] = value;
1834
+ const currentRequestHeaders = new Headers();
1835
+ for (const [key, value] of Object.entries(req.headers)) {
1836
+ if (Array.isArray(value)) {
1837
+ currentRequestHeaders.set(key, value.join(", "));
1838
+ }
1839
+ else if (value !== undefined) {
1840
+ currentRequestHeaders.set(key, value);
1712
1841
  }
1713
- else if (!key.startsWith("x-middleware-")) {
1842
+ }
1843
+ middlewareRequestHeaders = buildRequestHeadersFromMiddlewareResponse(currentRequestHeaders, result.responseHeaders);
1844
+ if (middlewareRequestHeaders && !hasAppDir) {
1845
+ applyRequestHeadersToNodeRequest(middlewareRequestHeaders);
1846
+ }
1847
+ for (const [key, value] of result.responseHeaders) {
1848
+ if (!key.startsWith("x-middleware-")) {
1714
1849
  res.appendHeader(key, value);
1715
1850
  }
1716
1851
  }
@@ -1732,52 +1867,25 @@ export default function vinext(options = {}) {
1732
1867
  req.__vinextRewriteStatus = result.rewriteStatus;
1733
1868
  }
1734
1869
  }
1735
- // ── Cloudflare Workers dev mode ────────────────────────────
1736
- // When @cloudflare/vite-plugin is present, ALL rendering runs
1737
- // inside the miniflare Worker subprocess — both App Router (via
1738
- // virtual:vinext-rsc-entry) and Pages Router (via
1739
- // virtual:vinext-server-entry → renderPage/handleApiRoute).
1740
- //
1741
- // The Worker entry already handles config redirects, rewrites,
1742
- // headers, and all routing internally. Running them here too
1743
- // would duplicate that logic and produce incorrect behaviour
1744
- // (double redirects, headers set on the wrong object, etc.).
1745
- //
1746
- // Middleware.ts is the only thing that belongs in the host connect
1747
- // handler — it has already run above. Any terminal middleware
1748
- // result (redirect, block response) has already been sent.
1749
- // Any rewrite has been written back to req.url above so the
1750
- // Cloudflare plugin's handler sees the correct path.
1751
- //
1752
- // Call next() to hand off to the Cloudflare plugin's connect
1753
- // handler, which dispatches the request to miniflare.
1754
- if (hasCloudflarePlugin)
1755
- return next();
1756
1870
  // Build request context once for has/missing condition checks
1757
- // across headers, redirects, and rewrites.
1871
+ // for config rules that execute after middleware (rewrites).
1758
1872
  // Convert Node.js IncomingMessage headers to a Web Request for
1759
1873
  // requestContextFromRequest(), which uses the standard Web API.
1760
- const reqUrl = new URL(url, `http://${req.headers.host || "localhost"}`);
1761
- const reqCtxHeaders = new Headers(Object.fromEntries(Object.entries(req.headers)
1762
- .filter(([, v]) => v !== undefined)
1763
- .map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : String(v)])));
1874
+ const reqUrl = new URL(url, requestOrigin);
1875
+ const reqCtxHeaders = middlewareRequestHeaders ?? nodeRequestHeaders;
1764
1876
  const reqCtx = requestContextFromRequest(new Request(reqUrl, { headers: reqCtxHeaders }));
1765
1877
  // Apply custom headers from next.config.js
1878
+ // Header matching still uses the original normalized pathname and
1879
+ // pre-middleware request state; middleware response headers win
1880
+ // later because they are already on the outgoing response.
1766
1881
  if (nextConfig?.headers.length) {
1767
- applyHeaders(pathname, res, nextConfig.headers, reqCtx);
1768
- }
1769
- // Apply redirects from next.config.js
1770
- if (nextConfig?.redirects.length) {
1771
- const redirected = applyRedirects(pathname, res, nextConfig.redirects, reqCtx);
1772
- if (redirected)
1773
- return;
1882
+ applyHeaders(pathname, res, nextConfig.headers, preMiddlewareReqCtx);
1774
1883
  }
1775
1884
  // Apply rewrites from next.config.js (beforeFiles)
1776
1885
  let resolvedUrl = url;
1777
1886
  if (nextConfig?.rewrites.beforeFiles.length) {
1778
1887
  resolvedUrl =
1779
- applyRewrites(pathname, nextConfig.rewrites.beforeFiles, reqCtx) ??
1780
- url;
1888
+ applyRewrites(pathname, nextConfig.rewrites.beforeFiles, reqCtx) ?? url;
1781
1889
  }
1782
1890
  // External rewrite from beforeFiles — proxy to external URL
1783
1891
  if (isExternalUrl(resolvedUrl)) {
@@ -1786,9 +1894,12 @@ export default function vinext(options = {}) {
1786
1894
  }
1787
1895
  // Handle API routes first (pages/api/*)
1788
1896
  const resolvedPathname = resolvedUrl.split("?")[0];
1789
- if (resolvedPathname.startsWith("/api/") ||
1790
- resolvedPathname === "/api") {
1897
+ if (resolvedPathname.startsWith("/api/") || resolvedPathname === "/api") {
1791
1898
  const apiRoutes = await apiRouter(pagesDir, nextConfig?.pageExtensions, fileMatcher);
1899
+ const apiMatch = matchRoute(resolvedUrl, apiRoutes);
1900
+ if (apiMatch && middlewareRequestHeaders) {
1901
+ applyRequestHeadersToNodeRequest(middlewareRequestHeaders);
1902
+ }
1792
1903
  const handled = await handleApiRoute(server, req, res, resolvedUrl, apiRoutes);
1793
1904
  if (handled)
1794
1905
  return;
@@ -1820,6 +1931,9 @@ export default function vinext(options = {}) {
1820
1931
  // Try rendering the resolved URL
1821
1932
  const match = matchRoute(resolvedUrl.split("?")[0], routes);
1822
1933
  if (match) {
1934
+ if (middlewareRequestHeaders) {
1935
+ applyRequestHeadersToNodeRequest(middlewareRequestHeaders);
1936
+ }
1823
1937
  await handler(req, res, resolvedUrl, mwStatus);
1824
1938
  return;
1825
1939
  }
@@ -1832,6 +1946,13 @@ export default function vinext(options = {}) {
1832
1946
  await proxyExternalRewriteNode(req, res, fallbackRewrite);
1833
1947
  return;
1834
1948
  }
1949
+ const fallbackMatch = matchRoute(fallbackRewrite.split("?")[0], routes);
1950
+ if (!fallbackMatch && hasAppDir) {
1951
+ return next();
1952
+ }
1953
+ if (middlewareRequestHeaders) {
1954
+ applyRequestHeadersToNodeRequest(middlewareRequestHeaders);
1955
+ }
1835
1956
  await handler(req, res, fallbackRewrite, mwStatus);
1836
1957
  return;
1837
1958
  }
@@ -2032,7 +2153,10 @@ export default function vinext(options = {}) {
2032
2153
  const importMatch = code.match(importRe);
2033
2154
  if (!importMatch)
2034
2155
  return null;
2035
- const importedNames = new Set(importMatch[1].split(",").map((s) => s.trim()).filter(Boolean));
2156
+ const importedNames = new Set(importMatch[1]
2157
+ .split(",")
2158
+ .map((s) => s.trim())
2159
+ .filter(Boolean));
2036
2160
  const s = new MagicString(code);
2037
2161
  let hasChanges = false;
2038
2162
  const cacheDir = this._cacheDir;
@@ -2058,10 +2182,14 @@ export default function vinext(options = {}) {
2058
2182
  }
2059
2183
  // Build the Google Fonts CSS URL
2060
2184
  const weights = options.weight
2061
- ? Array.isArray(options.weight) ? options.weight : [options.weight]
2185
+ ? Array.isArray(options.weight)
2186
+ ? options.weight
2187
+ : [options.weight]
2062
2188
  : [];
2063
2189
  const styles = options.style
2064
- ? Array.isArray(options.style) ? options.style : [options.style]
2190
+ ? Array.isArray(options.style)
2191
+ ? options.style
2192
+ : [options.style]
2065
2193
  : [];
2066
2194
  const display = options.display ?? "swap";
2067
2195
  let spec = family.replace(/\s+/g, "+");
@@ -2217,7 +2345,9 @@ export default function vinext(options = {}) {
2217
2345
  return null;
2218
2346
  if (!resolvedRscTransformsPath) {
2219
2347
  throw new Error("vinext: 'use cache' requires @vitejs/plugin-rsc to be installed.\n" +
2220
- "Run: " + detectPackageManager(process.cwd()) + " @vitejs/plugin-rsc");
2348
+ "Run: " +
2349
+ detectPackageManager(process.cwd()) +
2350
+ " @vitejs/plugin-rsc");
2221
2351
  }
2222
2352
  const { transformWrapExport, transformHoistInlineDirective } = await import(pathToFileURL(resolvedRscTransformsPath).href);
2223
2353
  const ast = parseAst(code);
@@ -2232,7 +2362,9 @@ export default function vinext(options = {}) {
2232
2362
  // (they're leaf components). Layout/template defaults are excluded
2233
2363
  // because they receive {children} from the framework.
2234
2364
  const directiveValue = cacheDirective.expression.value;
2235
- const variant = directiveValue === "use cache" ? "" : directiveValue.replace("use cache:", "").replace("use cache: ", "").trim();
2365
+ const variant = directiveValue === "use cache"
2366
+ ? ""
2367
+ : directiveValue.replace("use cache:", "").replace("use cache: ", "").trim();
2236
2368
  // Only skip default export wrapping for layouts and templates —
2237
2369
  // they receive {children} from the framework which requires
2238
2370
  // temporary reference handling that registerCachedFunction doesn't
@@ -2284,7 +2416,9 @@ export default function vinext(options = {}) {
2284
2416
  directive: /^use cache(:\s*\w+)?$/,
2285
2417
  runtime: (value, name, meta) => {
2286
2418
  const directiveMatch = meta.directiveMatch[0];
2287
- const variant = directiveMatch === "use cache" ? "" : directiveMatch.replace("use cache:", "").replace("use cache: ", "").trim();
2419
+ const variant = directiveMatch === "use cache"
2420
+ ? ""
2421
+ : directiveMatch.replace("use cache:", "").replace("use cache: ", "").trim();
2288
2422
  return `(await import(${JSON.stringify(runtimeModuleUrl2)})).registerCachedFunction(${value}, ${JSON.stringify(id + ":" + name)}, ${JSON.stringify(variant)})`;
2289
2423
  },
2290
2424
  rejectNonAsyncFunction: false,
@@ -2429,11 +2563,9 @@ export default function vinext(options = {}) {
2429
2563
  const content = fs.readFileSync(indexPath, "utf-8");
2430
2564
  // The font is inlined as base64 by vinext:og-inline-fetch-assets, so only
2431
2565
  // the WASM needs to be present as a file alongside the bundle.
2432
- const ogAssets = [
2433
- "resvg.wasm",
2434
- ];
2566
+ const ogAssets = ["resvg.wasm"];
2435
2567
  // Only copy if the bundle actually references these files
2436
- const referencedAssets = ogAssets.filter(asset => content.includes(asset));
2568
+ const referencedAssets = ogAssets.filter((asset) => content.includes(asset));
2437
2569
  if (referencedAssets.length === 0)
2438
2570
  return;
2439
2571
  // Find @vercel/og in node_modules
@@ -2482,6 +2614,38 @@ export default function vinext(options = {}) {
2482
2614
  },
2483
2615
  },
2484
2616
  },
2617
+ // Vite can emit empty SSR manifest entries for modules that Rollup inlines
2618
+ // into another chunk. Pages Router looks up assets by page module path at
2619
+ // runtime, so rebuild those mappings from the emitted client bundle.
2620
+ {
2621
+ name: "vinext:ssr-manifest-backfill",
2622
+ apply: "build",
2623
+ enforce: "post",
2624
+ writeBundle: {
2625
+ sequential: true,
2626
+ order: "post",
2627
+ handler(options, bundle) {
2628
+ const outDir = options.dir;
2629
+ if (!outDir)
2630
+ return;
2631
+ const viteDir = path.join(outDir, ".vite");
2632
+ const ssrManifestPath = path.join(viteDir, "ssr-manifest.json");
2633
+ if (!fs.existsSync(ssrManifestPath))
2634
+ return;
2635
+ try {
2636
+ const ssrManifest = JSON.parse(fs.readFileSync(ssrManifestPath, "utf-8"));
2637
+ const buildRoot = this.environment?.config.root ?? process.cwd();
2638
+ const buildBase = this.environment?.config.base ?? "/";
2639
+ const augmentedManifest = augmentSsrManifestFromBundle(ssrManifest, bundle, buildRoot, buildBase);
2640
+ fs.writeFileSync(ssrManifestPath, JSON.stringify(augmentedManifest, null, 2));
2641
+ }
2642
+ catch (err) {
2643
+ // Leave Vite's manifest untouched if parsing fails.
2644
+ console.warn("[vinext] Failed to augment SSR manifest:", err);
2645
+ }
2646
+ },
2647
+ },
2648
+ },
2485
2649
  // Cloudflare Workers production build integration:
2486
2650
  // After all environments are built, compute lazy chunks from the client
2487
2651
  // build manifest and inject globals into the worker entry.
@@ -2513,6 +2677,7 @@ export default function vinext(options = {}) {
2513
2677
  if (!fs.existsSync(distDir))
2514
2678
  return;
2515
2679
  const clientDir = path.resolve(buildRoot, "dist", "client");
2680
+ const clientBase = envConfig.base ?? "/";
2516
2681
  // Read build manifest and compute lazy chunks (only reachable via
2517
2682
  // dynamic imports). This runs for BOTH App Router and Pages Router.
2518
2683
  // clientEntryFile is only used by the Pages Router path below —
@@ -2525,15 +2690,17 @@ export default function vinext(options = {}) {
2525
2690
  const buildManifest = JSON.parse(fs.readFileSync(buildManifestPath, "utf-8"));
2526
2691
  for (const [, value] of Object.entries(buildManifest)) {
2527
2692
  if (value && value.isEntry && value.file) {
2528
- clientEntryFile = value.file;
2693
+ clientEntryFile = manifestFileWithBase(value.file, clientBase);
2529
2694
  break;
2530
2695
  }
2531
2696
  }
2532
- const lazy = computeLazyChunks(buildManifest);
2697
+ const lazy = manifestFilesWithBase(computeLazyChunks(buildManifest), clientBase);
2533
2698
  if (lazy.length > 0)
2534
2699
  lazyChunksData = lazy;
2535
2700
  }
2536
- catch { /* ignore parse errors */ }
2701
+ catch {
2702
+ /* ignore parse errors */
2703
+ }
2537
2704
  }
2538
2705
  // Read SSR manifest for per-page CSS/JS injection
2539
2706
  let ssrManifestData = null;
@@ -2542,7 +2709,9 @@ export default function vinext(options = {}) {
2542
2709
  try {
2543
2710
  ssrManifestData = JSON.parse(fs.readFileSync(ssrManifestPath, "utf-8"));
2544
2711
  }
2545
- catch { /* ignore parse errors */ }
2712
+ catch {
2713
+ /* ignore parse errors */
2714
+ }
2546
2715
  }
2547
2716
  if (hasAppDir) {
2548
2717
  // App Router: the RSC plugin handles __VINEXT_CLIENT_ENTRY__
@@ -2589,9 +2758,10 @@ export default function vinext(options = {}) {
2589
2758
  const assetsDir = path.join(clientDir, "assets");
2590
2759
  if (fs.existsSync(assetsDir)) {
2591
2760
  const files = fs.readdirSync(assetsDir);
2592
- const entry = files.find((f) => (f.includes("vinext-client-entry") || f.includes("vinext-app-browser-entry")) && f.endsWith(".js"));
2761
+ const entry = files.find((f) => (f.includes("vinext-client-entry") || f.includes("vinext-app-browser-entry")) &&
2762
+ f.endsWith(".js"));
2593
2763
  if (entry)
2594
- clientEntryFile = "assets/" + entry;
2764
+ clientEntryFile = manifestFileWithBase("assets/" + entry, clientBase);
2595
2765
  }
2596
2766
  }
2597
2767
  // Prepend globals to worker entry
@@ -2722,7 +2892,7 @@ export { matchConfigPattern } from "./config/config-matchers.js";
2722
2892
  */
2723
2893
  function stripServerExports(code) {
2724
2894
  const SERVER_EXPORTS = new Set(["getServerSideProps", "getStaticProps", "getStaticPaths"]);
2725
- if (![...SERVER_EXPORTS].some(name => code.includes(name)))
2895
+ if (![...SERVER_EXPORTS].some((name) => code.includes(name)))
2726
2896
  return null;
2727
2897
  let ast;
2728
2898
  try {
@@ -2773,11 +2943,13 @@ function stripServerExports(code) {
2773
2943
  // Build replacement: keep non-server specifiers, add stubs for stripped ones
2774
2944
  const parts = [];
2775
2945
  if (kept.length > 0) {
2776
- const keptStr = kept.map((sp) => {
2946
+ const keptStr = kept
2947
+ .map((sp) => {
2777
2948
  const local = sp.local.name;
2778
2949
  const exported = sp.exported?.name ?? sp.exported?.value;
2779
2950
  return local === exported ? local : `${local} as ${exported}`;
2780
- }).join(", ");
2951
+ })
2952
+ .join(", ");
2781
2953
  parts.push(`export { ${keptStr} };`);
2782
2954
  }
2783
2955
  for (const name of stripped) {
@@ -2796,11 +2968,13 @@ function stripServerExports(code) {
2796
2968
  * Apply redirect rules from next.config.js.
2797
2969
  * Returns true if a redirect was applied.
2798
2970
  */
2799
- function applyRedirects(pathname, res, redirects, ctx) {
2971
+ function applyRedirects(pathname, res, redirects, ctx, basePath = "") {
2800
2972
  const result = matchRedirect(pathname, redirects, ctx);
2801
2973
  if (result) {
2802
2974
  // Sanitize to prevent open redirect via protocol-relative URLs
2803
- const dest = sanitizeDestination(result.destination);
2975
+ const dest = sanitizeDestination(basePath && !isExternalUrl(result.destination) && !hasBasePath(result.destination, basePath)
2976
+ ? basePath + result.destination
2977
+ : result.destination);
2804
2978
  res.writeHead(result.permanent ? 308 : 307, { Location: dest });
2805
2979
  res.end();
2806
2980
  return true;
@@ -2838,9 +3012,7 @@ async function proxyExternalRewriteNode(req, res, externalUrl) {
2838
3012
  proxyResponse.headers.forEach((value, key) => {
2839
3013
  const existing = nodeHeaders[key];
2840
3014
  if (existing !== undefined) {
2841
- nodeHeaders[key] = Array.isArray(existing)
2842
- ? [...existing, value]
2843
- : [existing, value];
3015
+ nodeHeaders[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
2844
3016
  }
2845
3017
  else {
2846
3018
  nodeHeaders[key] = value;
@@ -2968,6 +3140,7 @@ function scanDirForMdx(dir) {
2968
3140
  export { staticExportPages, staticExportApp } from "./build/static-export.js";
2969
3141
  // Exported for CLI and testing
2970
3142
  export { clientManualChunks, clientOutputConfig, clientTreeshakeConfig, computeLazyChunks };
3143
+ export { augmentSsrManifestFromBundle as _augmentSsrManifestFromBundle };
2971
3144
  export { resolvePostcssStringPlugins as _resolvePostcssStringPlugins };
2972
3145
  export { parseStaticObjectLiteral as _parseStaticObjectLiteral };
2973
3146
  export { stripServerExports as _stripServerExports };