vinext 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +22 -10
  2. package/dist/build/client-build-config.d.ts +10 -0
  3. package/dist/build/client-build-config.js +18 -2
  4. package/dist/build/output-root-externals.d.ts +21 -0
  5. package/dist/build/output-root-externals.js +40 -0
  6. package/dist/build/pages-client-assets-module.d.ts +8 -0
  7. package/dist/build/pages-client-assets-module.js +21 -1
  8. package/dist/build/prerender-paths.js +6 -4
  9. package/dist/build/prerender.js +1 -1
  10. package/dist/build/run-prerender.js +1 -1
  11. package/dist/build/ssr-manifest.d.ts +2 -0
  12. package/dist/build/ssr-manifest.js +6 -2
  13. package/dist/check.js +12 -12
  14. package/dist/cli-dev-config.js +3 -3
  15. package/dist/cli.js +3 -2
  16. package/dist/client/client-redirect-matcher.d.ts +14 -0
  17. package/dist/client/client-redirect-matcher.js +37 -0
  18. package/dist/client/client-redirects.d.ts +27 -0
  19. package/dist/client/client-redirects.js +28 -0
  20. package/dist/client/client-rewrites.d.ts +2 -1
  21. package/dist/client/client-rewrites.js +1 -1
  22. package/dist/config/config-matchers.d.ts +26 -11
  23. package/dist/config/config-matchers.js +90 -88
  24. package/dist/config/destination-params.d.ts +47 -0
  25. package/dist/config/destination-params.js +116 -0
  26. package/dist/config/next-config.d.ts +7 -0
  27. package/dist/config/next-config.js +3 -0
  28. package/dist/entries/app-rsc-entry.d.ts +4 -0
  29. package/dist/entries/app-rsc-entry.js +20 -6
  30. package/dist/entries/pages-client-entry.js +3 -1
  31. package/dist/entries/pages-server-entry.js +13 -37
  32. package/dist/index.d.ts +3 -1
  33. package/dist/index.js +246 -136
  34. package/dist/init-cloudflare.d.ts +9 -0
  35. package/dist/init-cloudflare.js +107 -31
  36. package/dist/init-platform.d.ts +4 -0
  37. package/dist/init-platform.js +27 -2
  38. package/dist/init.d.ts +2 -1
  39. package/dist/init.js +153 -9
  40. package/dist/plugins/ast-scope.d.ts +7 -0
  41. package/dist/plugins/ast-scope.js +59 -3
  42. package/dist/plugins/ast-utils.d.ts +53 -2
  43. package/dist/plugins/ast-utils.js +90 -2
  44. package/dist/plugins/cloudflare-dev-module-urls.d.ts +41 -0
  45. package/dist/plugins/cloudflare-dev-module-urls.js +271 -0
  46. package/dist/plugins/commonjs-esm-facade.js +2 -2
  47. package/dist/plugins/css-module-imports.js +13 -20
  48. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  49. package/dist/plugins/dynamic-preload-metadata.js +11 -11
  50. package/dist/plugins/extensionless-dynamic-import.js +2 -3
  51. package/dist/plugins/fonts.d.ts +1 -1
  52. package/dist/plugins/fonts.js +3 -3
  53. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  54. package/dist/plugins/ignore-dynamic-requests.js +5 -7
  55. package/dist/plugins/import-meta-url.js +3 -4
  56. package/dist/plugins/instrumentation-client.js +3 -2
  57. package/dist/plugins/middleware-export-validation.js +2 -3
  58. package/dist/plugins/middleware-server-only.d.ts +5 -4
  59. package/dist/plugins/middleware-server-only.js +8 -5
  60. package/dist/plugins/og-harfbuzz.js +2 -3
  61. package/dist/plugins/optimize-imports.js +3 -4
  62. package/dist/plugins/pages-node-externals.js +3 -4
  63. package/dist/plugins/remove-console.js +2 -18
  64. package/dist/plugins/require-condition-resolution.js +3 -3
  65. package/dist/plugins/require-context.js +2 -3
  66. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  67. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  68. package/dist/plugins/strip-server-exports.js +265 -102
  69. package/dist/plugins/styled-jsx.js +2 -3
  70. package/dist/plugins/transform-result.d.ts +9 -5
  71. package/dist/plugins/transitive-externals.js +1 -1
  72. package/dist/plugins/typeof-window.d.ts +2 -2
  73. package/dist/plugins/use-cache-callable.d.ts +21 -2
  74. package/dist/plugins/use-cache-callable.js +84 -35
  75. package/dist/plugins/worker-image-imports.js +30 -33
  76. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  77. package/dist/plugins/worker-server-only-imports.js +31 -0
  78. package/dist/plugins/worker-use-server.d.ts +25 -0
  79. package/dist/plugins/worker-use-server.js +153 -0
  80. package/dist/server/api-handler.d.ts +1 -0
  81. package/dist/server/api-handler.js +3 -2
  82. package/dist/server/app-action-forwarding.js +2 -2
  83. package/dist/server/app-browser-action-result.d.ts +1 -1
  84. package/dist/server/app-browser-action-result.js +9 -2
  85. package/dist/server/app-browser-entry.js +3 -3
  86. package/dist/server/app-browser-interception-context.js +1 -1
  87. package/dist/server/app-browser-navigation-controller.js +1 -1
  88. package/dist/server/app-browser-prefetch-response.js +2 -2
  89. package/dist/server/app-browser-server-action-client.js +1 -1
  90. package/dist/server/app-browser-state.js +1 -1
  91. package/dist/server/app-browser-supplemental-refresh.js +1 -1
  92. package/dist/server/app-middleware.d.ts +11 -0
  93. package/dist/server/app-middleware.js +17 -2
  94. package/dist/server/app-optimistic-routing.js +1 -1
  95. package/dist/server/app-page-cache.d.ts +1 -1
  96. package/dist/server/app-page-cache.js +1 -1
  97. package/dist/server/app-page-dispatch.d.ts +3 -1
  98. package/dist/server/app-page-dispatch.js +5 -4
  99. package/dist/server/app-page-ppr-runtime.js +1 -1
  100. package/dist/server/app-page-render-observation.js +1 -1
  101. package/dist/server/app-page-render.js +1 -1
  102. package/dist/server/app-page-request.js +53 -15
  103. package/dist/server/app-page-route-wiring.js +2 -2
  104. package/dist/server/app-pages-bridge.d.ts +1 -1
  105. package/dist/server/app-pages-bridge.js +3 -4
  106. package/dist/server/app-prerender-endpoints.js +9 -6
  107. package/dist/server/app-render-abort-error.d.ts +19 -0
  108. package/dist/server/app-render-abort-error.js +31 -1
  109. package/dist/server/app-request-stage-dispatch.js +6 -2
  110. package/dist/server/app-request-stage-independent-entry.js +29 -9
  111. package/dist/server/app-response-stage-entry.js +11 -3
  112. package/dist/server/app-route-handler-cache.d.ts +1 -0
  113. package/dist/server/app-route-handler-cache.js +1 -0
  114. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  115. package/dist/server/app-route-handler-dispatch.js +5 -2
  116. package/dist/server/app-route-handler-execution.d.ts +1 -0
  117. package/dist/server/app-route-handler-execution.js +1 -0
  118. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  119. package/dist/server/app-route-handler-runtime.js +46 -25
  120. package/dist/server/app-router-entry.js +29 -10
  121. package/dist/server/app-rsc-cache-busting.d.ts +16 -0
  122. package/dist/server/app-rsc-cache-busting.js +30 -13
  123. package/dist/server/app-rsc-errors.js +20 -16
  124. package/dist/server/app-rsc-handler.d.ts +7 -0
  125. package/dist/server/app-rsc-handler.js +89 -39
  126. package/dist/server/app-rsc-request-normalization.js +3 -3
  127. package/dist/server/app-rsc-response-stage.js +5 -3
  128. package/dist/server/app-segment-config.d.ts +2 -1
  129. package/dist/server/app-server-action-execution.d.ts +2 -1
  130. package/dist/server/app-server-action-execution.js +62 -23
  131. package/dist/server/app-ssr-entry.js +7 -5
  132. package/dist/server/app-visited-response-cache.js +2 -2
  133. package/dist/server/app-worker-stages.d.ts +5 -0
  134. package/dist/server/app-worker-stages.js +1 -1
  135. package/dist/server/cacheability-request.js +2 -2
  136. package/dist/server/client-build-files.d.ts +16 -0
  137. package/dist/server/client-build-files.js +36 -0
  138. package/dist/server/client-reuse-manifest.js +1 -1
  139. package/dist/server/config-headers.js +1 -1
  140. package/dist/server/defer-until-stream-consumed.d.ts +2 -1
  141. package/dist/server/defer-until-stream-consumed.js +3 -1
  142. package/dist/server/dev-server.js +39 -28
  143. package/dist/server/dev-stack-sourcemap.js +18 -23
  144. package/dist/server/document-initial-head.d.ts +0 -11
  145. package/dist/server/document-initial-head.js +1 -1
  146. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  147. package/dist/server/forwarded-middleware-context.js +22 -0
  148. package/dist/server/headers.d.ts +4 -1
  149. package/dist/server/headers.js +4 -1
  150. package/dist/server/http-conditional.d.ts +11 -0
  151. package/dist/server/http-conditional.js +10 -1
  152. package/dist/server/http-error-responses.d.ts +1 -1
  153. package/dist/server/image-optimization.d.ts +38 -0
  154. package/dist/server/image-optimization.js +94 -4
  155. package/dist/server/isr-cache.d.ts +10 -0
  156. package/dist/server/isr-cache.js +41 -6
  157. package/dist/server/metadata-route-response.js +11 -10
  158. package/dist/server/middleware-matcher.d.ts +2 -0
  159. package/dist/server/middleware-matcher.js +23 -2
  160. package/dist/server/middleware-path-to-regexp.js +1 -1
  161. package/dist/server/middleware-runtime.d.ts +13 -0
  162. package/dist/server/middleware-runtime.js +38 -25
  163. package/dist/server/middleware.d.ts +1 -1
  164. package/dist/server/middleware.js +3 -1
  165. package/dist/server/multi-stage.d.ts +11 -0
  166. package/dist/server/navigation-planner.js +1 -1
  167. package/dist/server/normalize-path.d.ts +0 -13
  168. package/dist/server/normalize-path.js +1 -34
  169. package/dist/server/open-redirect.d.ts +11 -4
  170. package/dist/server/open-redirect.js +11 -4
  171. package/dist/server/pages-api-route.d.ts +3 -2
  172. package/dist/server/pages-api-route.js +6 -5
  173. package/dist/server/pages-cache-tag.d.ts +12 -0
  174. package/dist/server/pages-cache-tag.js +19 -0
  175. package/dist/server/pages-client-assets.d.ts +2 -0
  176. package/dist/server/pages-data-route.d.ts +10 -0
  177. package/dist/server/pages-data-route.js +21 -3
  178. package/dist/server/pages-document-asset-props.d.ts +5 -1
  179. package/dist/server/pages-document-asset-props.js +13 -5
  180. package/dist/server/pages-document-splice.d.ts +19 -0
  181. package/dist/server/pages-document-splice.js +55 -0
  182. package/dist/server/pages-i18n.d.ts +12 -1
  183. package/dist/server/pages-i18n.js +18 -4
  184. package/dist/server/pages-node-compat.d.ts +16 -5
  185. package/dist/server/pages-node-compat.js +20 -12
  186. package/dist/server/pages-page-data.d.ts +32 -24
  187. package/dist/server/pages-page-data.js +51 -121
  188. package/dist/server/pages-page-handler.d.ts +0 -6
  189. package/dist/server/pages-page-handler.js +189 -127
  190. package/dist/server/pages-page-response.d.ts +12 -6
  191. package/dist/server/pages-page-response.js +42 -35
  192. package/dist/server/pages-request-pipeline.d.ts +10 -2
  193. package/dist/server/pages-request-pipeline.js +41 -22
  194. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  195. package/dist/server/pages-request-stage-entry.js +75 -16
  196. package/dist/server/pages-response-stage-entry.js +4 -0
  197. package/dist/server/pages-revalidate.js +3 -3
  198. package/dist/server/pages-router-entry.js +5 -4
  199. package/dist/server/prerender-manifest.d.ts +3 -3
  200. package/dist/server/prerender-manifest.js +4 -3
  201. package/dist/server/prerender-work-unit-setup.js +4 -0
  202. package/dist/server/prod-server.d.ts +12 -0
  203. package/dist/server/prod-server.js +204 -74
  204. package/dist/server/proper-error.d.ts +4 -0
  205. package/dist/server/proper-error.js +31 -0
  206. package/dist/server/request-pipeline.d.ts +4 -1
  207. package/dist/server/request-pipeline.js +3 -2
  208. package/dist/server/revalidation-request.d.ts +1 -0
  209. package/dist/server/revalidation-request.js +1 -1
  210. package/dist/server/rsc-stream-hints.d.ts +1 -1
  211. package/dist/server/rsc-stream-hints.js +52 -5
  212. package/dist/server/seed-cache.d.ts +1 -0
  213. package/dist/server/seed-cache.js +5 -4
  214. package/dist/server/server-reference-loader.d.ts +4 -0
  215. package/dist/server/server-reference-loader.js +29 -0
  216. package/dist/server/socket-error-backstop.d.ts +9 -1
  217. package/dist/server/socket-error-backstop.js +46 -17
  218. package/dist/server/static-file-signal.d.ts +15 -4
  219. package/dist/server/static-file-signal.js +46 -12
  220. package/dist/server/worker-image-optimization.d.ts +34 -0
  221. package/dist/server/worker-image-optimization.js +99 -0
  222. package/dist/server/worker-stages.d.ts +1 -1
  223. package/dist/server/worker-stages.js +2 -2
  224. package/dist/server/worker-utils.d.ts +8 -3
  225. package/dist/server/worker-utils.js +12 -7
  226. package/dist/shims/app-router-scroll.js +4 -1
  227. package/dist/shims/app-router-tree-context.d.ts +5 -0
  228. package/dist/shims/app-router-tree-context.js +27 -0
  229. package/dist/shims/cache-callable-runtime.d.ts +4 -2
  230. package/dist/shims/cache-callable-runtime.js +21 -12
  231. package/dist/shims/cache-handler.js +1 -9
  232. package/dist/shims/cache-runtime.d.ts +6 -1
  233. package/dist/shims/cache-runtime.js +2 -1
  234. package/dist/shims/cache.d.ts +2 -2
  235. package/dist/shims/cache.js +40 -13
  236. package/dist/shims/cdn-cache.d.ts +8 -0
  237. package/dist/shims/cdn-cache.js +17 -1
  238. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  239. package/dist/shims/disable-smooth-scroll.js +36 -0
  240. package/dist/shims/document-placeholders.d.ts +19 -0
  241. package/dist/shims/document-placeholders.js +45 -0
  242. package/dist/shims/document.js +21 -5
  243. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  244. package/dist/shims/dynamic-preload-chunks.js +13 -9
  245. package/dist/shims/dynamic.d.ts +1 -1
  246. package/dist/shims/dynamic.js +60 -8
  247. package/dist/shims/fetch-cache-state.d.ts +66 -0
  248. package/dist/shims/fetch-cache-state.js +92 -0
  249. package/dist/shims/fetch-cache.d.ts +3 -67
  250. package/dist/shims/fetch-cache.js +160 -125
  251. package/dist/shims/head.d.ts +10 -6
  252. package/dist/shims/head.js +16 -14
  253. package/dist/shims/headers.js +2 -2
  254. package/dist/shims/image.js +2 -1
  255. package/dist/shims/internal/hybrid-client-route-owner-direct.js +1 -1
  256. package/dist/shims/internal/hybrid-client-route-owner.js +1 -0
  257. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  258. package/dist/shims/internal/work-store-scope.js +24 -0
  259. package/dist/shims/link-prefetch.js +5 -4
  260. package/dist/shims/link.js +2 -2
  261. package/dist/shims/navigation.js +34 -9
  262. package/dist/shims/request-context.d.ts +2 -0
  263. package/dist/shims/request-state-types.d.ts +2 -1
  264. package/dist/shims/router.js +21 -32
  265. package/dist/shims/server.d.ts +27 -4
  266. package/dist/shims/server.js +65 -9
  267. package/dist/shims/unified-request-context.d.ts +1 -1
  268. package/dist/shims/url-utils.d.ts +9 -0
  269. package/dist/shims/url-utils.js +15 -4
  270. package/dist/utils/client-runtime-metadata.js +1 -1
  271. package/dist/utils/domain-locale.js +1 -1
  272. package/dist/utils/hash.d.ts +7 -0
  273. package/dist/utils/hash.js +11 -1
  274. package/dist/utils/package-name.d.ts +6 -0
  275. package/dist/utils/package-name.js +12 -1
  276. package/dist/utils/plugin-rsc-version.d.ts +6 -0
  277. package/dist/utils/plugin-rsc-version.js +41 -0
  278. package/dist/utils/prerender-output-paths.js +15 -4
  279. package/dist/utils/public-file-path.d.ts +10 -2
  280. package/dist/utils/public-file-path.js +13 -9
  281. package/dist/utils/public-routes.js +2 -2
  282. package/dist/utils/react-version.d.ts +10 -0
  283. package/dist/utils/react-version.js +39 -16
  284. package/package.json +11 -7
  285. package/dist/build/nitro-route-rules.d.ts +0 -47
  286. package/dist/build/nitro-route-rules.js +0 -79
  287. package/dist/plugins/cache-flight-codec.d.ts +0 -19
  288. package/dist/plugins/cache-flight-codec.js +0 -68
package/README.md CHANGED
@@ -147,6 +147,9 @@ Other options: `--port <port>` (default: 3001), `--skip-check`, `--force`.
147
147
  Cloudflare init uses `cf` and `cloudflare.config.ts` by default. Both `vinext init`
148
148
  and `create-vinext-app` accept `--legacy-wrangler-cloudflare-init` to retain the
149
149
  legacy Wrangler setup. Existing Wrangler configs are not automatically migrated.
150
+ `vinext init --name <name>` sets the Worker name in the generated config instead of
151
+ deriving it from `package.json`. An existing config keeps its own Worker name, and init
152
+ prints a warning.
150
153
 
151
154
  If your `next.config.*` sets `output: "standalone"`, `vite build` emits a self-hosting bundle at `dist/standalone/`. Start it with:
152
155
 
@@ -282,7 +285,7 @@ You can, with caution. vinext has known compatibility gaps and has not yet been
282
285
  Yes. Next.js supports [self-hosting](https://nextjs.org/docs/app/building-your-application/deploying#self-hosting) on Node.js servers, Docker containers, and static exports. If you're happy with the Next.js toolchain and just want to run it somewhere other than Vercel, self-hosting is the simplest path.
283
286
 
284
287
  **How are you verifying this works?**
285
- The test suite has over 1,700 Vitest tests and 380 Playwright E2E tests. This includes tests ported directly from the [Next.js test suite](https://github.com/vercel/next.js/tree/canary/test) and [OpenNext's Cloudflare conformance suite](https://github.com/opennextjs/opennextjs-cloudflare), covering routing, SSR, RSC, server actions, caching, metadata, middleware, streaming, and more. Vercel's [App Router Playground](https://github.com/vercel/next-app-router-playground) also runs on vinext as an integration test. See the [Tests](#tests) section and `tests/nextjs-compat/TRACKING.md` for details.
288
+ The test suite has over 1,700 Vitest tests and 380 Playwright E2E tests. This includes tests ported directly from the [Next.js test suite](https://github.com/vercel/next.js/tree/canary/test) and [OpenNext's Cloudflare conformance suite](https://github.com/opennextjs/opennextjs-cloudflare), covering routing, SSR, RSC, server actions, caching, metadata, middleware, streaming, and more. Vercel's [App Router Playground](https://github.com/vercel/next-app-router-playground) also runs on vinext as an integration test. See the [Tests](#tests) section for details.
286
289
 
287
290
  **Who is reviewing this code?**
288
291
  A mix of humans and AI agents. Humans review PRs before they merge, focused on behavior, structure, and long-term direction. We lean heavily on agent-driven code review to catch issues at PR time and across the codebase. The test suite is the primary quality gate. Outside contributions and deeper human code review are very welcome.
@@ -437,10 +440,14 @@ export default defineConfig({
437
440
 
438
441
  #### Module Federation (client-side)
439
442
 
440
- For client-side Module Federation, configure React and React DOM as singleton shared modules in both the host and remotes:
443
+ Use [`@module-federation/vinext`](https://github.com/module-federation/vinext), a thin wrapper around `@module-federation/vite` with vinext defaults. Register `federation()` before `vinext()`:
444
+
445
+ ```bash
446
+ npm install @module-federation/vinext
447
+ ```
441
448
 
442
449
  ```ts
443
- import { federation } from "@module-federation/vite";
450
+ import { federation } from "@module-federation/vinext";
444
451
  import { defineConfig } from "vite";
445
452
  import vinext from "vinext";
446
453
 
@@ -448,11 +455,14 @@ export default defineConfig({
448
455
  plugins: [
449
456
  federation({
450
457
  name: "host",
451
- shared: {
452
- react: { singleton: true },
453
- "react/": { singleton: true },
454
- "react-dom": { singleton: true },
455
- "react-dom/": { singleton: true },
458
+ remotes: {
459
+ catalog: {
460
+ type: "module",
461
+ name: "catalog",
462
+ entry: "https://catalog.example.com/remoteEntry.js",
463
+ entryGlobalName: "catalog",
464
+ shareScope: "default",
465
+ },
456
466
  },
457
467
  }),
458
468
  vinext(),
@@ -460,6 +470,8 @@ export default defineConfig({
460
470
  });
461
471
  ```
462
472
 
473
+ The wrapper shares `react` and `react-dom` as singletons, defaults `filename` to `remoteEntry.js`, and injects the host's federation startup into the entry, because vinext has no conventional HTML entry. Explicit options override these defaults, and every `@module-federation/vite` option remains available. Passing your own `shared` replaces the default map, so keep `react`, `react/`, `react-dom`, and `react-dom/` as singletons in it, in both the host and remotes.
474
+
463
475
  In a remote client component, use `getVinextReact()` before reading React hooks. vinext registers the host's browser React instance before application modules execute, and the first registration remains stable across remote evaluation and HMR:
464
476
 
465
477
  ```tsx
@@ -478,7 +490,7 @@ export function RemoteCounter() {
478
490
 
479
491
  This bridge is browser-only. It does not provide App Router Module Federation SSR or transparently replace React imports inside third-party packages; compatible React versions remain the responsibility of the Module Federation `shared` configuration.
480
492
 
481
- See the [examples](#live-examples) for complete working configurations.
493
+ See the [Module Federation guide](https://vinext.dev/docs/guides/module-federation) for loading remote components in the host.
482
494
 
483
495
  ### Other platforms (via Nitro)
484
496
 
@@ -1036,7 +1048,7 @@ Or add it to your `package.json` as a file dependency:
1036
1048
  }
1037
1049
  ```
1038
1050
 
1039
- vinext has peer dependencies on `react ^19.2.6`, `react-dom ^19.2.6`, `react-server-dom-webpack ^19.2.6`, and `vite ^8.0.0`. Then replace `next` with `vinext` in your scripts and run as normal.
1051
+ vinext has peer dependencies on `react ^19.3.1`, `react-dom ^19.3.1`, `react-server-dom-webpack ^19.3.1`, and `vite ^8.0.0`. Then replace `next` with `vinext` in your scripts and run as normal.
1040
1052
 
1041
1053
  ## Contributing
1042
1054
 
@@ -116,4 +116,14 @@ export declare function createMultiStageCodeSplittingConfig(existing: VinextBuil
116
116
  * the host's existing output pattern (or vinext's server-assets default).
117
117
  */
118
118
  export declare function createMultiStageChunkFileNames(assetsDir: string, existing: VinextBuildOutput["chunkFileNames"]): ChunkFileNameFunction;
119
+ /**
120
+ * The client environment builds after RSC and SSR, and Vite empties its
121
+ * output directory first. A client output directory that is, or contains, a
122
+ * server output directory would therefore delete the server build.
123
+ */
124
+ export declare function assertClientOutDirIsSeparate(root: string, dirs: {
125
+ clientOutDir?: string;
126
+ rscOutDir?: string;
127
+ ssrOutDir?: string;
128
+ }): void;
119
129
  //#endregion
@@ -1,6 +1,7 @@
1
- import { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
1
+ import path, { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
2
  //#region src/build/client-build-config.ts
3
3
  const ROUTE_OWNED_CLIENT_SHIMS = /* @__PURE__ */ new Set([
4
+ "app-router-tree-context",
4
5
  "compat-router",
5
6
  "dynamic",
6
7
  "dynamic-preload-chunks",
@@ -266,5 +267,20 @@ function createMultiStageChunkFileNames(assetsDir, existing) {
266
267
  return (existing ?? joinAssetFileNamePattern(assetsDir, "[name]-[hash].js")).replaceAll("[name]", name);
267
268
  };
268
269
  }
270
+ /**
271
+ * The client environment builds after RSC and SSR, and Vite empties its
272
+ * output directory first. A client output directory that is, or contains, a
273
+ * server output directory would therefore delete the server build.
274
+ */
275
+ function assertClientOutDirIsSeparate(root, dirs) {
276
+ if (!dirs.clientOutDir) return;
277
+ const clientOutDir = path.resolve(root, dirs.clientOutDir);
278
+ const serverOutDirs = [["rscOutDir", dirs.rscOutDir ?? "dist/server"], ["ssrOutDir", dirs.ssrOutDir ?? "dist/server/ssr"]];
279
+ for (const [name, dir] of serverOutDirs) {
280
+ const relative = path.relative(clientOutDir, path.resolve(root, dir));
281
+ if (relative === ".." || relative.startsWith("../") || path.isAbsolute(relative)) continue;
282
+ throw new Error(`[vinext] clientOutDir "${dirs.clientOutDir}" contains ${name} "${dir}". The client build runs last and empties its output directory, which would delete the server build. Use separate output directories.`);
283
+ }
284
+ }
269
285
  //#endregion
270
- export { RSC_FRAMEWORK_CHUNK_TEST, createClientAssetFileNames, createClientCodeSplittingConfig, createClientFileNameConfig, createClientManualChunks, createMultiStageChunkFileNames, createMultiStageCodeSplittingConfig, createRscFrameworkChunkOutputConfig, getBuildBundlerOptions, getClientTreeshakeConfig, isRscFrameworkModule, sanitizeRscChunkFileName, withBuildBundlerOptions };
286
+ export { RSC_FRAMEWORK_CHUNK_TEST, assertClientOutDirIsSeparate, createClientAssetFileNames, createClientCodeSplittingConfig, createClientFileNameConfig, createClientManualChunks, createMultiStageChunkFileNames, createMultiStageCodeSplittingConfig, createRscFrameworkChunkOutputConfig, getBuildBundlerOptions, getClientTreeshakeConfig, isRscFrameworkModule, sanitizeRscChunkFileName, withBuildBundlerOptions };
@@ -0,0 +1,21 @@
1
+ import MagicString from "magic-string";
2
+ //#region src/build/output-root-externals.d.ts
3
+ /**
4
+ * Rewrite external virtual ids into imports of files that live at a fixed
5
+ * place relative to the output root.
6
+ *
7
+ * Some server modules (the cacheability manifest, the pregenerated concrete
8
+ * paths, the Pages client assets) are written beside the server entry, and
9
+ * deploy steps can rewrite them after the build. Rolldown leaves a relative
10
+ * external specifier exactly as written, but it can move the importing module
11
+ * into any chunk, for example a shared chunk under `_next/static/` when the
12
+ * entry becomes a facade. Keeping the virtual id external and resolving it
13
+ * against each chunk's own location keeps the import pointing at the file.
14
+ *
15
+ * `targets` maps each external id to its file path relative to the output root.
16
+ */
17
+ export declare function rewriteOutputRootExternals(code: string, chunkFileName: string, targets: ReadonlyMap<string, string>): {
18
+ code: string;
19
+ map: ReturnType<MagicString["generateMap"]>;
20
+ } | null;
21
+ //#endregion
@@ -0,0 +1,40 @@
1
+ import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
+ import { escapeRegExp } from "../utils/regex.js";
3
+ import MagicString from "magic-string";
4
+ //#region src/build/output-root-externals.ts
5
+ /**
6
+ * Rewrite external virtual ids into imports of files that live at a fixed
7
+ * place relative to the output root.
8
+ *
9
+ * Some server modules (the cacheability manifest, the pregenerated concrete
10
+ * paths, the Pages client assets) are written beside the server entry, and
11
+ * deploy steps can rewrite them after the build. Rolldown leaves a relative
12
+ * external specifier exactly as written, but it can move the importing module
13
+ * into any chunk, for example a shared chunk under `_next/static/` when the
14
+ * entry becomes a facade. Keeping the virtual id external and resolving it
15
+ * against each chunk's own location keeps the import pointing at the file.
16
+ *
17
+ * `targets` maps each external id to its file path relative to the output root.
18
+ */
19
+ function rewriteOutputRootExternals(code, chunkFileName, targets) {
20
+ let output = null;
21
+ const chunkDir = path.posix.dirname(chunkFileName);
22
+ for (const [id, target] of targets) {
23
+ if (!code.includes(id)) continue;
24
+ let specifier = path.posix.relative(chunkDir, target);
25
+ if (!specifier.startsWith(".")) specifier = `./${specifier}`;
26
+ const quotedId = new RegExp(`(["'])${escapeRegExp(id)}\\1`, "g");
27
+ for (const match of code.matchAll(quotedId)) {
28
+ output ??= new MagicString(code);
29
+ const quote = match[1];
30
+ output.overwrite(match.index, match.index + match[0].length, quote + specifier + quote);
31
+ }
32
+ }
33
+ if (output === null) return null;
34
+ return {
35
+ code: output.toString(),
36
+ map: output.generateMap({ hires: "boundary" })
37
+ };
38
+ }
39
+ //#endregion
40
+ export { rewriteOutputRootExternals };
@@ -2,5 +2,13 @@ import { PagesClientAssets } from "../server/pages-client-assets.js";
2
2
  //#region src/build/pages-client-assets-module.d.ts
3
3
  export declare const PAGES_CLIENT_ASSETS_MODULE = "vinext-client-assets.js";
4
4
  export declare function buildPagesClientAssetsModule(assets: PagesClientAssets): string;
5
+ /**
6
+ * Select the client bundle files that live outside `_next/static`, as URL
7
+ * pathnames. Plugins emit these at the output root, for example a Module
8
+ * Federation host's `remoteEntry.js`. Public directory files are copied
9
+ * outside the bundle, and build metadata (vinext's and Cloudflare's
10
+ * `.assetsignore`) is excluded.
11
+ */
12
+ export declare function collectClientBuildFiles(fileNames: Iterable<string>): string[];
5
13
  export declare function writePagesClientAssetsModuleIfMissing(outputDir: string, moduleSource: string): void;
6
14
  //#endregion
@@ -1,5 +1,7 @@
1
1
  import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
+ import "../utils/asset-prefix.js";
2
3
  import { getSharedChunkFiles } from "../server/pages-asset-tags.js";
4
+ import "../utils/client-entry-manifest.js";
3
5
  import fs from "node:fs";
4
6
  //#region src/build/pages-client-assets-module.ts
5
7
  const PAGES_CLIENT_ASSETS_MODULE = "vinext-client-assets.js";
@@ -10,6 +12,24 @@ function buildPagesClientAssetsModule(assets) {
10
12
  } : assets;
11
13
  return `export default ${JSON.stringify(prepared)};\n`;
12
14
  }
15
+ /**
16
+ * Select the client bundle files that live outside `_next/static`, as URL
17
+ * pathnames. Plugins emit these at the output root, for example a Module
18
+ * Federation host's `remoteEntry.js`. Public directory files are copied
19
+ * outside the bundle, and build metadata (vinext's and Cloudflare's
20
+ * `.assetsignore`) is excluded.
21
+ */
22
+ function collectClientBuildFiles(fileNames) {
23
+ const files = [];
24
+ for (const fileName of fileNames) {
25
+ const pathname = `/${fileName}`;
26
+ if (pathname.includes(`/_next/static/`)) continue;
27
+ if (pathname.startsWith("/.vite/") || fileName === "vinext-client-entry-manifest.json") continue;
28
+ if (fileName === ".assetsignore") continue;
29
+ files.push(pathname);
30
+ }
31
+ return files.sort();
32
+ }
13
33
  function writePagesClientAssetsModuleIfMissing(outputDir, moduleSource) {
14
34
  const outputPath = path.join(outputDir, PAGES_CLIENT_ASSETS_MODULE);
15
35
  if (fs.existsSync(outputPath)) return;
@@ -17,4 +37,4 @@ function writePagesClientAssetsModuleIfMissing(outputDir, moduleSource) {
17
37
  fs.writeFileSync(outputPath, moduleSource);
18
38
  }
19
39
  //#endregion
20
- export { PAGES_CLIENT_ASSETS_MODULE, buildPagesClientAssetsModule, writePagesClientAssetsModuleIfMissing };
40
+ export { PAGES_CLIENT_ASSETS_MODULE, buildPagesClientAssetsModule, collectClientBuildFiles, writePagesClientAssetsModuleIfMissing };
@@ -3,11 +3,11 @@ import { findDir } from "../utils/project.js";
3
3
  import { BLOCKED_PAGES, PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
4
4
  import { VINEXT_PRERENDER_SECRET_HEADER } from "../utils/protocol-headers.js";
5
5
  import { isExternalUrl } from "../utils/external-url.js";
6
+ import "../server/headers.js";
6
7
  import { matchHeaders, matchesRewriteSource } from "../config/config-matchers.js";
7
8
  import { loadNextConfig, resolveNextConfig } from "../config/next-config.js";
8
9
  import { appRouteHasMainTreeLoadingBoundary, appRouter, matchAppRoute } from "../routing/app-router.js";
9
10
  import { apiRouter, matchRoute, pagesRouter } from "../routing/pages-router.js";
10
- import "../server/headers.js";
11
11
  import { matchesMiddlewarePathname } from "../server/middleware-matcher.js";
12
12
  import { extractLocaleFromUrl, normalizeDefaultLocalePathname } from "../server/pages-i18n.js";
13
13
  import { classifyAppRoute, classifyAppRouteHandler, classifyPagesRoute, extractExportConstString, extractMiddlewareMatcherConfig, getAppRouteRenderEntryPath } from "./report.js";
@@ -134,6 +134,7 @@ async function fetchDiscoveryEndpoint(url, headers, retryOptions = {}) {
134
134
  const request = (async () => {
135
135
  const res = await fetch(url, {
136
136
  headers,
137
+ redirect: "manual",
137
138
  signal: controller.signal
138
139
  });
139
140
  return {
@@ -152,10 +153,11 @@ async function fetchDiscoveryEndpoint(url, headers, retryOptions = {}) {
152
153
  if (res.status === 204) return null;
153
154
  return text;
154
155
  }
155
- const userFailure = readDiscoveryUserFailure(res, text);
156
+ const redirected = res.status >= 300 && res.status < 400;
157
+ const userFailure = redirected ? null : readDiscoveryUserFailure(res, text);
156
158
  const detail = userFailure ?? (/cloudflare:|ERR_UNSUPPORTED_ESM_URL_SCHEME/i.test(text) ? text.trim() : "");
157
- lastError = /* @__PURE__ */ new Error(`path discovery returned HTTP ${res.status}${detail ? `: ${detail}` : ""}`);
158
- if (!(res.status === 404 || res.status >= 500 && res.status <= 599 && userFailure === null)) {
159
+ lastError = redirected ? /* @__PURE__ */ new Error(`path discovery returned a redirect (HTTP ${res.status}); refusing to follow it with the prerender secret`) : /* @__PURE__ */ new Error(`path discovery returned HTTP ${res.status}${detail ? `: ${detail}` : ""}`);
160
+ if (!(redirected || res.status === 404 || res.status >= 500 && res.status <= 599 && userFailure === null)) {
159
161
  shouldRetry = false;
160
162
  throw lastError;
161
163
  }
@@ -3,8 +3,8 @@ import { createValidFileMatcher, findFileWithExtensions } from "../routing/file-
3
3
  import { isUnknownRecord } from "../utils/record.js";
4
4
  import { BLOCKED_PAGES } from "../shims/constants.js";
5
5
  import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
6
- import { appRouteLayoutStaticParamsGroups } from "../routing/app-router.js";
7
6
  import { NEXT_CACHE_TAGS_HEADER, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH, VINEXT_PRERENDER_RENDER_ERROR_HEADER, VINEXT_PRERENDER_SPECIAL_ERROR_HEADER } from "../server/headers.js";
7
+ import { appRouteLayoutStaticParamsGroups } from "../routing/app-router.js";
8
8
  import { extractLocaleFromUrl } from "../server/pages-i18n.js";
9
9
  import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath } from "./report.js";
10
10
  import { resolveClientStaleTimeSeconds } from "../utils/cache-control-metadata.js";
@@ -124,7 +124,7 @@ async function runPrerender(options) {
124
124
  let totalUrls = 0;
125
125
  let completedUrls = 0;
126
126
  const progress = new PrerenderProgress();
127
- const outDir = mode === "export" ? path.join(root, "dist", "client") : path.join(manifestDir, "prerendered-routes");
127
+ const outDir = mode === "export" ? path.resolve(root, options.routeRootConfig?.clientOutDir ?? path.join("dist", "client")) : path.join(manifestDir, "prerendered-routes");
128
128
  let sharedProdServer = null;
129
129
  let sharedPrerenderSecret;
130
130
  const restorePrerenderPhase = enterPrerenderPhase();
@@ -14,6 +14,8 @@ export type BundleBackfillChunk = {
14
14
  * `toSlash` where the result becomes a module id / manifest key.
15
15
  */
16
16
  export declare function tryRealpathSync(candidate: string): string | null;
17
+ /** Forward-slash `path.relative(root, moduleId)`, which may escape with `../`. */
18
+ export declare function relativeToRoot(root: string, moduleId: string): string;
17
19
  export declare function relativeWithinRoot(root: string, moduleId: string): string | null;
18
20
  export declare function augmentSsrManifestFromBundle(ssrManifest: Record<string, string[]>, bundle: Record<string, BundleBackfillChunk | {
19
21
  type: string;
@@ -16,8 +16,12 @@ function tryRealpathSync(candidate) {
16
16
  function isWindowsAbsolutePath(candidate) {
17
17
  return /^[a-zA-Z]:[\\/]/.test(candidate) || candidate.startsWith("\\\\");
18
18
  }
19
+ /** Forward-slash `path.relative(root, moduleId)`, which may escape with `../`. */
20
+ function relativeToRoot(root, moduleId) {
21
+ return isWindowsAbsolutePath(root) || isWindowsAbsolutePath(moduleId) ? path.win32.relative(root, moduleId) : path.relative(root, moduleId);
22
+ }
19
23
  function relativeWithinRoot(root, moduleId) {
20
- const relativeId = isWindowsAbsolutePath(root) || isWindowsAbsolutePath(moduleId) ? path.win32.relative(root, moduleId) : path.relative(root, moduleId);
24
+ const relativeId = relativeToRoot(root, moduleId);
21
25
  if (!relativeId || relativeId === ".." || relativeId.startsWith("../")) return null;
22
26
  return relativeId;
23
27
  }
@@ -70,4 +74,4 @@ function augmentSsrManifestFromBundle(ssrManifest, bundle, root, base = "/") {
70
74
  return Object.fromEntries(Object.entries(nextManifest).map(([key, files]) => [key, [...files]]));
71
75
  }
72
76
  //#endregion
73
- export { augmentSsrManifestFromBundle, relativeWithinRoot, tryRealpathSync };
77
+ export { augmentSsrManifestFromBundle, relativeToRoot, relativeWithinRoot, tryRealpathSync };
package/dist/check.js CHANGED
@@ -3,10 +3,9 @@ import path from "./deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
3
3
  import { detectPackageManager, findDir, findViteConfigPath } from "./utils/project.js";
4
4
  import { normalizePageExtensions } from "./routing/file-matcher.js";
5
5
  import { POSTCSS_CONFIG_FILES } from "./plugins/postcss.js";
6
- import { unwrapExpression } from "./plugins/ast-utils.js";
6
+ import { parseProgram, unwrapExpression } from "./plugins/ast-utils.js";
7
7
  import { require_ignore } from "./deps/.pnpm/ignore@5.3.2/deps/ignore/index.js";
8
8
  import fs from "node:fs";
9
- import { parseAst } from "vite";
10
9
  //#region src/check.ts
11
10
  /**
12
11
  * vinext check — compatibility scanner for Next.js apps
@@ -210,7 +209,7 @@ const CONFIG_SUPPORT = {
210
209
  },
211
210
  transpilePackages: {
212
211
  status: "supported",
213
- detail: "listed packages are bundled instead of externalized on the server"
212
+ detail: "listed packages are bundled instead of externalized on the server, and their next/dynamic() calls get SSR preload links"
214
213
  },
215
214
  serverExternalPackages: {
216
215
  status: "supported",
@@ -252,9 +251,10 @@ const CONFIG_SUPPORT = {
252
251
  status: "unsupported",
253
252
  detail: "custom cacheLife profiles are ignored; only the built-in profiles are available"
254
253
  },
255
- skipTrailingSlashRedirect: {
256
- status: "unsupported",
257
- detail: "ignored; trailing-slash redirects are always applied"
254
+ skipTrailingSlashRedirect: { status: "supported" },
255
+ "experimental.skipTrailingSlashRedirect": {
256
+ status: "supported",
257
+ detail: "legacy alias of skipTrailingSlashRedirect"
258
258
  },
259
259
  reactCompiler: {
260
260
  status: "partial",
@@ -433,8 +433,8 @@ const LIBRARY_SUPPORT = {
433
433
  detail: "renders client-side; by default events are only collected when deployed on Vercel"
434
434
  },
435
435
  "next-intl": {
436
- status: "partial",
437
- detail: "auto-detected from (src/)i18n/request.{ts,tsx,js,jsx}, so createNextIntlPlugin isn't needed; client components can fail with \"No intl context found\" (cloudflare/vinext#177)"
436
+ status: "supported",
437
+ detail: "createNextIntlPlugin is only optional with the request config at (src/)i18n/request.*; its experimental messages, precompile and extract options don't work (they need webpack/Turbopack loaders)"
438
438
  },
439
439
  "@clerk/nextjs": {
440
440
  status: "supported",
@@ -812,7 +812,7 @@ function propertyKeyName(prop) {
812
812
  * object — top-level keys plus, for each object-valued property, its child keys
813
813
  * (used for dot-notation options like `experimental.ppr`).
814
814
  *
815
- * Uses Vite's `parseAst` (the bundled oxc parser) instead of scanning text, so
815
+ * Uses Vite's bundled oxc parser instead of scanning text, so
816
816
  * comments, string values, and other non-key mentions of an option name are
817
817
  * never mistaken for a real config option. Returns empty sets if the file cannot
818
818
  * be parsed — the check is advisory, so a parse failure simply reports nothing.
@@ -823,7 +823,7 @@ function collectConfigKeys(source) {
823
823
  let pageExtensions;
824
824
  let program;
825
825
  try {
826
- program = parseAst(source, { lang: "ts" });
826
+ program = parseProgram(source, { lang: "ts" });
827
827
  } catch {
828
828
  return {
829
829
  top,
@@ -970,7 +970,7 @@ function isNextConfigWithCjsGlobals(rel, content) {
970
970
  if (/^next\.config\.(?:ts|mts|cjs)$/.test(rel)) return true;
971
971
  if (rel !== "next.config.js") return false;
972
972
  try {
973
- return !parseAst(content).body.some((node) => node.type === "ImportDeclaration" || node.type.startsWith("Export"));
973
+ return !parseProgram(content).body.some((node) => node.type === "ImportDeclaration" || node.type.startsWith("Export"));
974
974
  } catch {
975
975
  return false;
976
976
  }
@@ -983,7 +983,7 @@ function isNextConfigWithCjsGlobals(rel, content) {
983
983
  function exportsPostcssPluginsOnlyObject(content) {
984
984
  let program;
985
985
  try {
986
- program = parseAst(content, { lang: "ts" });
986
+ program = parseProgram(content, { lang: "ts" });
987
987
  } catch {
988
988
  return false;
989
989
  }
@@ -28,7 +28,7 @@ function claimViteCliDevInvocation(root, isRestart = false, isReserved = false)
28
28
  return true;
29
29
  }
30
30
  function applyDevServerDefaults(server, options) {
31
- server.port = options.port ?? server.port ?? 3e3;
31
+ if (options.port !== void 0) server.port = options.port;
32
32
  server.host = options.hostname ?? server.host ?? "localhost";
33
33
  }
34
34
  function createDevServerLifecyclePlugin(options, isEnabled) {
@@ -114,7 +114,7 @@ function configureDevServerLifecycle(server) {
114
114
  const listenServer = server.listen.bind(server);
115
115
  server.listen = async (port, isRestart) => {
116
116
  if (server.config.server.middlewareMode || process.env.VINEXT_NO_DEV_LOCK === "1") return listenServer(port, isRestart);
117
- const configuredPort = port ?? server.config.server.port ?? 3e3;
117
+ const configuredPort = port ?? server.config.server.port;
118
118
  const hostname = normalizeDevServerHostname(server.config.server.host);
119
119
  const displayHostname = hostname === "0.0.0.0" ? "localhost" : hostname;
120
120
  const restartingLock = activeDevServerLocks.get(root);
@@ -155,7 +155,7 @@ function configureDevServerLifecycle(server) {
155
155
  };
156
156
  server.httpServer?.on("listening", () => {
157
157
  if (!activeLock) return;
158
- const port = server.config.server.port ?? 3e3;
158
+ const port = server.config.server.port;
159
159
  const hostname = normalizeDevServerHostname(server.config.server.host);
160
160
  const displayHostname = hostname === "0.0.0.0" ? "localhost" : hostname;
161
161
  const lock = activeLock;
package/dist/cli.js CHANGED
@@ -4,8 +4,8 @@ import { detectPackageManager, ensureViteConfigCompatibility, findViteConfigPath
4
4
  import { formatReport, runCheck } from "./check.js";
5
5
  import { parseArgs } from "./cli-args.js";
6
6
  import { findViteRoot, getViteCliInvocation, valueOptionName } from "./utils/vite-cli-invocation.js";
7
- import { init } from "./init.js";
8
7
  import { resolveInitOptions } from "./init-platform.js";
8
+ import { init } from "./init.js";
9
9
  import { loadDotenv } from "./config/dotenv.js";
10
10
  import { PHASE_PRODUCTION_BUILD } from "./shims/constants.js";
11
11
  import { loadNextConfig, resolveNextConfig } from "./config/next-config.js";
@@ -275,6 +275,7 @@ function printHelp(cmd) {
275
275
  --no-install Update dependency entries without installing them
276
276
  --force Overwrite existing vite.config.ts
277
277
  --platform <target> Deployment target: cloudflare or node
278
+ --name <name> Cloudflare Worker name (default: derived from package.json)
278
279
  --prerender Configure Vite builds to pre-render static routes
279
280
  (Cloudflare deploy won't serve them unless using Static Assets)
280
281
  --warm-cache Add cache warming to the Cloudflare deploy script
@@ -363,7 +364,7 @@ function printHelp(cmd) {
363
364
  --version Show version
364
365
 
365
366
  Examples:
366
- vinext dev Start dev server on port 3000
367
+ vinext dev Start dev server
367
368
  vinext dev --port 4000 Start dev server on port 4000
368
369
  vinext build Build for production
369
370
  vinext typegen Generate route helper types
@@ -0,0 +1,14 @@
1
+ import { RequestContext } from "../config/request-context.js";
2
+ import { BasePathMatchState, RedirectMatch } from "../config/config-matchers.js";
3
+ import { ClientRedirect } from "./client-redirects.js";
4
+ //#region src/client/client-redirect-matcher.d.ts
5
+ /**
6
+ * Resolve the first matching client-safe redirect. A matching server-owned
7
+ * rule stops client evaluation and returns null so a later client-safe rule
8
+ * cannot take its place. The navigation then continues as it does in Next.js,
9
+ * which never resolves config redirects in the browser: a document request (or
10
+ * a data request that falls back to one) reaches the server rule, while a page
11
+ * without data fetching renders without it.
12
+ */
13
+ export declare function matchClientRedirect(pathname: string, redirects: ClientRedirect[], context: RequestContext, basePathState: BasePathMatchState): RedirectMatch | null;
14
+ //#endregion
@@ -0,0 +1,37 @@
1
+ import { matchRedirect } from "../config/config-matchers.js";
2
+ //#region src/client/client-redirect-matcher.ts
3
+ const matcherRules = /* @__PURE__ */ new WeakMap();
4
+ function getMatcherRule(redirect) {
5
+ let rule = matcherRules.get(redirect);
6
+ if (!rule) {
7
+ rule = [{
8
+ source: redirect.source,
9
+ destination: redirect.destination ?? "/",
10
+ permanent: redirect.permanent ?? false,
11
+ has: redirect.has,
12
+ missing: redirect.missing,
13
+ locale: redirect.locale,
14
+ basePath: redirect.basePath
15
+ }];
16
+ matcherRules.set(redirect, rule);
17
+ }
18
+ return rule;
19
+ }
20
+ /**
21
+ * Resolve the first matching client-safe redirect. A matching server-owned
22
+ * rule stops client evaluation and returns null so a later client-safe rule
23
+ * cannot take its place. The navigation then continues as it does in Next.js,
24
+ * which never resolves config redirects in the browser: a document request (or
25
+ * a data request that falls back to one) reaches the server rule, while a page
26
+ * without data fetching renders without it.
27
+ */
28
+ function matchClientRedirect(pathname, redirects, context, basePathState) {
29
+ for (const redirect of redirects) {
30
+ const match = matchRedirect(pathname, getMatcherRule(redirect), context, basePathState);
31
+ if (match === null) continue;
32
+ return redirect.requiresServerEvaluation ? null : match;
33
+ }
34
+ return null;
35
+ }
36
+ //#endregion
37
+ export { matchClientRedirect };
@@ -0,0 +1,27 @@
1
+ import { NextRedirect, ResolvedNextConfig } from "../config/next-config.js";
2
+ import { ClientHasCondition } from "./client-rewrites.js";
3
+ //#region src/client/client-redirects.d.ts
4
+ type ClientRedirectFields = Pick<NextRedirect, "basePath" | "locale" | "source"> & {
5
+ has?: ClientHasCondition[];
6
+ missing?: ClientHasCondition[];
7
+ };
8
+ /**
9
+ * Redirect data that is safe to publish in a browser bundle.
10
+ *
11
+ * Next.js keeps redirects in the server routes manifest only. Vinext resolves
12
+ * browser-authoritative redirects during Pages Router navigation, but rules
13
+ * with header or cookie conditions are server-owned: their condition values
14
+ * can be private, so the rule keeps only its source and client-safe
15
+ * conditions to preserve first-match ordering, and drops its destination.
16
+ */
17
+ export type ClientRedirect = (ClientRedirectFields & {
18
+ destination: string;
19
+ permanent: boolean;
20
+ requiresServerEvaluation?: never;
21
+ }) | (ClientRedirectFields & {
22
+ destination?: never;
23
+ permanent?: never;
24
+ requiresServerEvaluation: true;
25
+ });
26
+ export declare function toClientRedirects(redirects: ResolvedNextConfig["redirects"]): ClientRedirect[];
27
+ //#endregion
@@ -0,0 +1,28 @@
1
+ import { isClientHasCondition } from "./client-rewrites.js";
2
+ //#region src/client/client-redirects.ts
3
+ function toClientRedirect(redirect) {
4
+ const hasServerOnlyCondition = (redirect.has?.some((condition) => !isClientHasCondition(condition)) ?? false) || (redirect.missing?.some((condition) => !isClientHasCondition(condition)) ?? false);
5
+ const clientHas = redirect.has?.filter(isClientHasCondition);
6
+ const clientMissing = redirect.missing?.filter(isClientHasCondition);
7
+ const common = {
8
+ source: redirect.source,
9
+ has: clientHas?.length ? clientHas : void 0,
10
+ missing: clientMissing?.length ? clientMissing : void 0,
11
+ locale: redirect.locale,
12
+ basePath: redirect.basePath
13
+ };
14
+ if (hasServerOnlyCondition) return {
15
+ ...common,
16
+ requiresServerEvaluation: true
17
+ };
18
+ return {
19
+ ...common,
20
+ destination: redirect.destination,
21
+ permanent: redirect.permanent
22
+ };
23
+ }
24
+ function toClientRedirects(redirects) {
25
+ return redirects.map(toClientRedirect);
26
+ }
27
+ //#endregion
28
+ export { toClientRedirects };
@@ -1,6 +1,6 @@
1
1
  import { HasCondition, NextRewrite, ResolvedNextConfig } from "../config/next-config.js";
2
2
  //#region src/client/client-rewrites.d.ts
3
- type ClientHasCondition = Omit<HasCondition, "type"> & {
3
+ export type ClientHasCondition = Omit<HasCondition, "type"> & {
4
4
  type: "host" | "query";
5
5
  };
6
6
  type ClientRewriteFields = Pick<NextRewrite, "basePath" | "locale" | "source"> & {
@@ -26,5 +26,6 @@ export type ClientRewrites = {
26
26
  beforeFiles: ClientRewrite[];
27
27
  fallback: ClientRewrite[];
28
28
  };
29
+ export declare function isClientHasCondition(condition: HasCondition): condition is ClientHasCondition;
29
30
  export declare function toClientRewrites(rewrites: ResolvedNextConfig["rewrites"]): ClientRewrites;
30
31
  //#endregion
@@ -38,4 +38,4 @@ function toClientRewrites(rewrites) {
38
38
  };
39
39
  }
40
40
  //#endregion
41
- export { toClientRewrites };
41
+ export { isClientHasCondition, toClientRewrites };