vinext 1.1.0 → 1.2.0

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 (234) hide show
  1. package/README.md +21 -9
  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/pages-client-assets-module.d.ts +8 -0
  5. package/dist/build/pages-client-assets-module.js +21 -1
  6. package/dist/build/prerender-paths.js +5 -3
  7. package/dist/build/run-prerender.js +1 -1
  8. package/dist/build/ssr-manifest.d.ts +2 -0
  9. package/dist/build/ssr-manifest.js +6 -2
  10. package/dist/check.js +7 -6
  11. package/dist/cli.js +2 -1
  12. package/dist/client/client-redirect-matcher.d.ts +14 -0
  13. package/dist/client/client-redirect-matcher.js +37 -0
  14. package/dist/client/client-redirects.d.ts +27 -0
  15. package/dist/client/client-redirects.js +28 -0
  16. package/dist/client/client-rewrites.d.ts +2 -1
  17. package/dist/client/client-rewrites.js +1 -1
  18. package/dist/config/config-matchers.d.ts +26 -11
  19. package/dist/config/config-matchers.js +78 -81
  20. package/dist/config/destination-params.d.ts +47 -0
  21. package/dist/config/destination-params.js +116 -0
  22. package/dist/config/next-config.d.ts +7 -0
  23. package/dist/config/next-config.js +3 -0
  24. package/dist/entries/app-rsc-entry.d.ts +4 -0
  25. package/dist/entries/app-rsc-entry.js +20 -6
  26. package/dist/entries/pages-client-entry.js +3 -1
  27. package/dist/entries/pages-server-entry.js +13 -37
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.js +134 -43
  30. package/dist/init-cloudflare.d.ts +9 -0
  31. package/dist/init-cloudflare.js +107 -31
  32. package/dist/init-platform.d.ts +4 -0
  33. package/dist/init-platform.js +27 -2
  34. package/dist/init.d.ts +1 -0
  35. package/dist/init.js +67 -3
  36. package/dist/plugins/ast-scope.d.ts +7 -0
  37. package/dist/plugins/ast-scope.js +59 -3
  38. package/dist/plugins/cache-flight-codec.d.ts +1 -1
  39. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  40. package/dist/plugins/dynamic-preload-metadata.js +9 -8
  41. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  42. package/dist/plugins/ignore-dynamic-requests.js +3 -4
  43. package/dist/plugins/middleware-server-only.d.ts +5 -4
  44. package/dist/plugins/middleware-server-only.js +8 -5
  45. package/dist/plugins/pages-node-externals.js +1 -1
  46. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  47. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  48. package/dist/plugins/strip-server-exports.js +262 -98
  49. package/dist/plugins/transform-result.d.ts +9 -5
  50. package/dist/plugins/transitive-externals.js +1 -1
  51. package/dist/plugins/typeof-window.d.ts +2 -2
  52. package/dist/plugins/use-cache-callable.js +8 -2
  53. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  54. package/dist/plugins/worker-server-only-imports.js +31 -0
  55. package/dist/plugins/worker-use-server.d.ts +25 -0
  56. package/dist/plugins/worker-use-server.js +129 -0
  57. package/dist/server/api-handler.d.ts +1 -0
  58. package/dist/server/api-handler.js +2 -1
  59. package/dist/server/app-browser-action-result.d.ts +1 -1
  60. package/dist/server/app-browser-action-result.js +9 -2
  61. package/dist/server/app-browser-navigation-controller.js +1 -1
  62. package/dist/server/app-browser-server-action-client.js +1 -1
  63. package/dist/server/app-middleware.d.ts +11 -0
  64. package/dist/server/app-middleware.js +17 -2
  65. package/dist/server/app-page-cache.d.ts +1 -1
  66. package/dist/server/app-page-cache.js +1 -1
  67. package/dist/server/app-page-dispatch.d.ts +3 -1
  68. package/dist/server/app-page-dispatch.js +4 -3
  69. package/dist/server/app-page-ppr-runtime.js +1 -1
  70. package/dist/server/app-page-render-observation.js +1 -1
  71. package/dist/server/app-page-request.js +53 -15
  72. package/dist/server/app-page-route-wiring.js +2 -2
  73. package/dist/server/app-pages-bridge.d.ts +1 -1
  74. package/dist/server/app-pages-bridge.js +2 -3
  75. package/dist/server/app-prerender-endpoints.js +9 -6
  76. package/dist/server/app-request-stage-dispatch.js +6 -2
  77. package/dist/server/app-request-stage-independent-entry.js +25 -5
  78. package/dist/server/app-response-stage-entry.js +10 -2
  79. package/dist/server/app-route-handler-cache.d.ts +1 -0
  80. package/dist/server/app-route-handler-cache.js +1 -0
  81. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  82. package/dist/server/app-route-handler-dispatch.js +5 -2
  83. package/dist/server/app-route-handler-execution.d.ts +1 -0
  84. package/dist/server/app-route-handler-execution.js +1 -0
  85. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  86. package/dist/server/app-route-handler-runtime.js +45 -24
  87. package/dist/server/app-router-entry.js +24 -5
  88. package/dist/server/app-rsc-cache-busting.js +1 -1
  89. package/dist/server/app-rsc-errors.js +20 -16
  90. package/dist/server/app-rsc-handler.d.ts +7 -0
  91. package/dist/server/app-rsc-handler.js +85 -35
  92. package/dist/server/app-rsc-response-stage.js +4 -2
  93. package/dist/server/app-segment-config.d.ts +2 -1
  94. package/dist/server/app-server-action-execution.d.ts +2 -1
  95. package/dist/server/app-server-action-execution.js +61 -22
  96. package/dist/server/app-ssr-entry.js +2 -1
  97. package/dist/server/app-worker-stages.d.ts +5 -0
  98. package/dist/server/app-worker-stages.js +1 -1
  99. package/dist/server/cacheability-request.js +2 -2
  100. package/dist/server/client-build-files.d.ts +16 -0
  101. package/dist/server/client-build-files.js +36 -0
  102. package/dist/server/client-reuse-manifest.js +1 -1
  103. package/dist/server/dev-server.js +38 -27
  104. package/dist/server/dev-stack-sourcemap.js +18 -23
  105. package/dist/server/document-initial-head.d.ts +0 -11
  106. package/dist/server/document-initial-head.js +1 -1
  107. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  108. package/dist/server/forwarded-middleware-context.js +22 -0
  109. package/dist/server/headers.d.ts +4 -1
  110. package/dist/server/headers.js +4 -1
  111. package/dist/server/http-conditional.d.ts +11 -0
  112. package/dist/server/http-conditional.js +10 -1
  113. package/dist/server/http-error-responses.d.ts +1 -1
  114. package/dist/server/image-optimization.d.ts +38 -0
  115. package/dist/server/image-optimization.js +93 -4
  116. package/dist/server/isr-cache.d.ts +10 -0
  117. package/dist/server/isr-cache.js +41 -6
  118. package/dist/server/metadata-route-response.js +9 -8
  119. package/dist/server/middleware-matcher.d.ts +2 -0
  120. package/dist/server/middleware-matcher.js +23 -2
  121. package/dist/server/middleware-runtime.d.ts +13 -0
  122. package/dist/server/middleware-runtime.js +33 -20
  123. package/dist/server/middleware.d.ts +1 -1
  124. package/dist/server/middleware.js +3 -1
  125. package/dist/server/multi-stage.d.ts +11 -0
  126. package/dist/server/normalize-path.d.ts +0 -13
  127. package/dist/server/normalize-path.js +1 -34
  128. package/dist/server/pages-api-route.d.ts +3 -2
  129. package/dist/server/pages-api-route.js +4 -3
  130. package/dist/server/pages-cache-tag.d.ts +12 -0
  131. package/dist/server/pages-cache-tag.js +19 -0
  132. package/dist/server/pages-client-assets.d.ts +2 -0
  133. package/dist/server/pages-data-route.d.ts +10 -0
  134. package/dist/server/pages-data-route.js +19 -2
  135. package/dist/server/pages-document-asset-props.d.ts +5 -1
  136. package/dist/server/pages-document-asset-props.js +13 -5
  137. package/dist/server/pages-document-splice.d.ts +19 -0
  138. package/dist/server/pages-document-splice.js +55 -0
  139. package/dist/server/pages-i18n.d.ts +12 -1
  140. package/dist/server/pages-i18n.js +18 -4
  141. package/dist/server/pages-node-compat.d.ts +16 -5
  142. package/dist/server/pages-node-compat.js +20 -12
  143. package/dist/server/pages-page-data.d.ts +32 -24
  144. package/dist/server/pages-page-data.js +50 -120
  145. package/dist/server/pages-page-handler.d.ts +0 -6
  146. package/dist/server/pages-page-handler.js +188 -126
  147. package/dist/server/pages-page-response.d.ts +12 -6
  148. package/dist/server/pages-page-response.js +41 -34
  149. package/dist/server/pages-request-pipeline.d.ts +10 -2
  150. package/dist/server/pages-request-pipeline.js +37 -18
  151. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  152. package/dist/server/pages-request-stage-entry.js +71 -12
  153. package/dist/server/pages-response-stage-entry.js +4 -0
  154. package/dist/server/pages-revalidate.js +1 -1
  155. package/dist/server/pages-router-entry.js +4 -3
  156. package/dist/server/prerender-manifest.d.ts +3 -3
  157. package/dist/server/prerender-manifest.js +4 -3
  158. package/dist/server/prerender-work-unit-setup.js +4 -0
  159. package/dist/server/prod-server.d.ts +12 -0
  160. package/dist/server/prod-server.js +199 -69
  161. package/dist/server/proper-error.d.ts +4 -0
  162. package/dist/server/proper-error.js +31 -0
  163. package/dist/server/request-pipeline.d.ts +4 -1
  164. package/dist/server/request-pipeline.js +3 -2
  165. package/dist/server/revalidation-request.d.ts +1 -0
  166. package/dist/server/revalidation-request.js +1 -1
  167. package/dist/server/rsc-stream-hints.js +31 -2
  168. package/dist/server/seed-cache.d.ts +1 -0
  169. package/dist/server/seed-cache.js +5 -4
  170. package/dist/server/server-reference-loader.d.ts +4 -0
  171. package/dist/server/server-reference-loader.js +29 -0
  172. package/dist/server/socket-error-backstop.d.ts +9 -1
  173. package/dist/server/socket-error-backstop.js +46 -17
  174. package/dist/server/static-file-signal.d.ts +15 -4
  175. package/dist/server/static-file-signal.js +46 -12
  176. package/dist/server/worker-image-optimization.d.ts +34 -0
  177. package/dist/server/worker-image-optimization.js +86 -0
  178. package/dist/server/worker-stages.d.ts +1 -1
  179. package/dist/server/worker-stages.js +2 -2
  180. package/dist/server/worker-utils.d.ts +8 -3
  181. package/dist/server/worker-utils.js +12 -7
  182. package/dist/shims/app-router-scroll.js +4 -1
  183. package/dist/shims/app-router-tree-context.d.ts +5 -0
  184. package/dist/shims/app-router-tree-context.js +27 -0
  185. package/dist/shims/cache-callable-runtime.d.ts +4 -2
  186. package/dist/shims/cache-callable-runtime.js +21 -12
  187. package/dist/shims/cache-handler.js +1 -9
  188. package/dist/shims/cache-runtime.d.ts +6 -1
  189. package/dist/shims/cache-runtime.js +2 -1
  190. package/dist/shims/cache.d.ts +2 -2
  191. package/dist/shims/cache.js +40 -13
  192. package/dist/shims/cdn-cache.d.ts +8 -0
  193. package/dist/shims/cdn-cache.js +17 -1
  194. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  195. package/dist/shims/disable-smooth-scroll.js +36 -0
  196. package/dist/shims/document-placeholders.d.ts +19 -0
  197. package/dist/shims/document-placeholders.js +45 -0
  198. package/dist/shims/document.js +21 -5
  199. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  200. package/dist/shims/dynamic-preload-chunks.js +13 -9
  201. package/dist/shims/dynamic.d.ts +1 -1
  202. package/dist/shims/dynamic.js +60 -8
  203. package/dist/shims/fetch-cache-state.d.ts +66 -0
  204. package/dist/shims/fetch-cache-state.js +92 -0
  205. package/dist/shims/fetch-cache.d.ts +3 -67
  206. package/dist/shims/fetch-cache.js +160 -125
  207. package/dist/shims/head.d.ts +10 -6
  208. package/dist/shims/head.js +15 -13
  209. package/dist/shims/image.js +2 -1
  210. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  211. package/dist/shims/internal/work-store-scope.js +24 -0
  212. package/dist/shims/navigation.js +31 -5
  213. package/dist/shims/request-context.d.ts +2 -0
  214. package/dist/shims/request-state-types.d.ts +2 -1
  215. package/dist/shims/router.js +12 -20
  216. package/dist/shims/server.d.ts +16 -0
  217. package/dist/shims/server.js +24 -4
  218. package/dist/shims/unified-request-context.d.ts +1 -1
  219. package/dist/shims/url-utils.d.ts +2 -0
  220. package/dist/shims/url-utils.js +3 -1
  221. package/dist/utils/client-runtime-metadata.js +1 -1
  222. package/dist/utils/hash.d.ts +7 -0
  223. package/dist/utils/hash.js +11 -1
  224. package/dist/utils/package-name.d.ts +6 -0
  225. package/dist/utils/package-name.js +12 -1
  226. package/dist/utils/plugin-rsc-version.d.ts +4 -0
  227. package/dist/utils/plugin-rsc-version.js +15 -0
  228. package/dist/utils/prerender-output-paths.js +15 -4
  229. package/dist/utils/public-file-path.d.ts +10 -2
  230. package/dist/utils/public-file-path.js +13 -9
  231. package/dist/utils/public-routes.js +2 -2
  232. package/dist/utils/react-version.d.ts +3 -0
  233. package/dist/utils/react-version.js +1 -1
  234. package/package.json +7 -3
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
 
@@ -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 };
@@ -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 };
@@ -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
  }
@@ -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
@@ -210,7 +210,7 @@ const CONFIG_SUPPORT = {
210
210
  },
211
211
  transpilePackages: {
212
212
  status: "supported",
213
- detail: "listed packages are bundled instead of externalized on the server"
213
+ detail: "listed packages are bundled instead of externalized on the server, and their next/dynamic() calls get SSR preload links"
214
214
  },
215
215
  serverExternalPackages: {
216
216
  status: "supported",
@@ -252,9 +252,10 @@ const CONFIG_SUPPORT = {
252
252
  status: "unsupported",
253
253
  detail: "custom cacheLife profiles are ignored; only the built-in profiles are available"
254
254
  },
255
- skipTrailingSlashRedirect: {
256
- status: "unsupported",
257
- detail: "ignored; trailing-slash redirects are always applied"
255
+ skipTrailingSlashRedirect: { status: "supported" },
256
+ "experimental.skipTrailingSlashRedirect": {
257
+ status: "supported",
258
+ detail: "legacy alias of skipTrailingSlashRedirect"
258
259
  },
259
260
  reactCompiler: {
260
261
  status: "partial",
@@ -433,8 +434,8 @@ const LIBRARY_SUPPORT = {
433
434
  detail: "renders client-side; by default events are only collected when deployed on Vercel"
434
435
  },
435
436
  "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)"
437
+ status: "supported",
438
+ 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
439
  },
439
440
  "@clerk/nextjs": {
440
441
  status: "supported",
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
@@ -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 };
@@ -1,5 +1,6 @@
1
1
  import { HasCondition, NextHeader, NextI18nConfig, NextRedirect, NextRewrite } from "./next-config.js";
2
2
  import { RequestContext, normalizeHost, parseCookies, requestContextFromRequest } from "./request-context.js";
3
+ import { RedirectDestinationQueryPart } from "./destination-params.js";
3
4
  import { isExternalUrl } from "../utils/external-url.js";
4
5
  //#region src/config/config-matchers.d.ts
5
6
  /**
@@ -75,6 +76,15 @@ export declare function applyMiddlewareRequestHeaders(middlewareHeaders: Record<
75
76
  };
76
77
  export declare function checkHasConditions(has: HasCondition[] | undefined, missing: HasCondition[] | undefined, ctx: RequestContext): boolean;
77
78
  export declare function matchConfigPattern(pathname: string, pattern: string): Record<string, string> | null;
79
+ /**
80
+ * A matched config redirect. `destinationQuery` holds the destination query's
81
+ * parts, split before params were substituted, for merging the request query.
82
+ */
83
+ export type RedirectMatch = {
84
+ destination: string;
85
+ permanent: boolean;
86
+ destinationQuery: RedirectDestinationQueryPart[];
87
+ };
78
88
  /**
79
89
  * Apply redirect rules from next.config.js.
80
90
  * Returns the redirect info if a redirect was matched, or null.
@@ -110,10 +120,7 @@ export declare function matchConfigPattern(pathname: string, pattern: string): R
110
120
  * an original index < N are checked via matchConfigPattern first — they are
111
121
  * few in practice (typically zero) so this is not a hot-path concern.
112
122
  */
113
- export declare function matchRedirect(pathname: string, redirects: NextRedirect[], ctx: RequestContext, basePathState?: BasePathMatchState, onRuleSourceMatch?: (rule: NextRedirect) => void): {
114
- destination: string;
115
- permanent: boolean;
116
- } | null;
123
+ export declare function matchRedirect(pathname: string, redirects: NextRedirect[], ctx: RequestContext, basePathState?: BasePathMatchState, onRuleSourceMatch?: (rule: NextRedirect) => void): RedirectMatch | null;
117
124
  /**
118
125
  * Apply rewrite rules from next.config.js.
119
126
  * Returns the rewritten URL or null if no rewrite matched.
@@ -142,7 +149,8 @@ export declare function matchesRewriteSource(pathname: string, rewrite: NextRewr
142
149
  * users off-site.
143
150
  *
144
151
  * This function collapses any leading double (or more) slashes to a single
145
- * slash for non-external (relative) destinations.
152
+ * slash for non-external (relative) destinations, including tab/newline and
153
+ * leading C0 control characters that browsers ignore while parsing.
146
154
  */
147
155
  export declare function sanitizeDestination(dest: string): string;
148
156
  /**
@@ -162,13 +170,20 @@ export declare function sanitizeDestination(dest: string): string;
162
170
  * still treated as an RSC fetch. Dropping it breaks RSC fetch semantics
163
171
  * (issue #1529).
164
172
  *
165
- * Destination query params win — a request param is only carried over when
166
- * the destination does not already specify that key. Mirrors the merge
167
- * semantics in `proxyExternalRequest`. External destinations are returned
168
- * untouched (a config redirect to another origin should not leak the
169
- * original request's query).
173
+ * Ported from Next.js's `{ ...requestQuery, ...destinationQuery }` merge and
174
+ * `stringifyQuery`: request keys keep their order (integer-like keys first, as
175
+ * in a JS object), a destination key overrides the request value in place,
176
+ * and destination-only keys follow. Request keys and values are re-encoded;
177
+ * destination query text is emitted verbatim, except that, like Next.js,
178
+ * destination strings equal to a request key or value are re-encoded too.
179
+ * Pass the match's
180
+ * `destinationQuery` so substituted params keep their value boundaries;
181
+ * without it the destination's own query is split. External destinations are returned untouched (a config redirect to another
182
+ * origin should not leak the original request's query).
183
+ *
184
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
170
185
  */
171
- export declare function preserveRedirectDestinationQuery(destination: string, requestSearch: string): string;
186
+ export declare function preserveRedirectDestinationQuery(destination: string, requestSearch: string, destinationQuery?: RedirectDestinationQueryPart[]): string;
172
187
  /**
173
188
  * Proxy an incoming request to an external URL and return the upstream response.
174
189
  *