vinext 0.1.8 → 0.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 (285) hide show
  1. package/README.md +51 -30
  2. package/dist/build/css-url-assets.d.ts +3 -1
  3. package/dist/build/css-url-assets.js +18 -1
  4. package/dist/build/inject-pregenerated-paths.d.ts +7 -0
  5. package/dist/build/inject-pregenerated-paths.js +21 -0
  6. package/dist/build/layout-classification.js +1 -1
  7. package/dist/build/nitro-route-rules.js +1 -1
  8. package/dist/build/pages-client-assets-module.d.ts +11 -0
  9. package/dist/build/pages-client-assets-module.js +27 -0
  10. package/dist/build/prerender-server-entry.d.ts +1 -0
  11. package/dist/build/prerender-server-entry.js +49 -0
  12. package/dist/build/prerender-server-pool.d.ts +44 -0
  13. package/dist/build/prerender-server-pool.js +194 -0
  14. package/dist/build/prerender.d.ts +2 -1
  15. package/dist/build/prerender.js +98 -27
  16. package/dist/build/report.d.ts +13 -2
  17. package/dist/build/report.js +99 -2
  18. package/dist/build/run-prerender.d.ts +7 -0
  19. package/dist/build/run-prerender.js +12 -2
  20. package/dist/build/standalone.js +2 -0
  21. package/dist/cache/cache-adapters-virtual.js +3 -3
  22. package/dist/check.d.ts +4 -6
  23. package/dist/check.js +48 -11
  24. package/dist/cli-args.d.ts +1 -2
  25. package/dist/cli-args.js +1 -1
  26. package/dist/cli-dev-config.d.ts +12 -0
  27. package/dist/cli-dev-config.js +23 -0
  28. package/dist/cli.js +112 -96
  29. package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
  30. package/dist/{server → client}/dev-error-overlay-store.js +1 -1
  31. package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
  32. package/dist/{server → client}/dev-error-overlay.js +2 -2
  33. package/dist/client/navigation-runtime.d.ts +2 -0
  34. package/dist/client/navigation-runtime.js +3 -1
  35. package/dist/client/vinext-next-data.d.ts +4 -1
  36. package/dist/client/window-next.d.ts +2 -1
  37. package/dist/cloudflare/index.d.ts +2 -2
  38. package/dist/cloudflare/index.js +2 -2
  39. package/dist/config/config-matchers.js +45 -19
  40. package/dist/config/next-config.d.ts +41 -9
  41. package/dist/config/next-config.js +60 -3
  42. package/dist/config/prerender.d.ts +34 -0
  43. package/dist/config/prerender.js +55 -0
  44. package/dist/entries/app-browser-entry.js +5 -1
  45. package/dist/entries/app-rsc-entry.d.ts +5 -3
  46. package/dist/entries/app-rsc-entry.js +44 -10
  47. package/dist/entries/app-rsc-manifest.js +2 -0
  48. package/dist/entries/pages-client-entry.d.ts +3 -0
  49. package/dist/entries/pages-client-entry.js +28 -3
  50. package/dist/entries/pages-server-entry.js +23 -6
  51. package/dist/image/image-adapters-virtual.d.ts +59 -0
  52. package/dist/image/image-adapters-virtual.js +59 -0
  53. package/dist/index.d.ts +33 -0
  54. package/dist/index.js +482 -120
  55. package/dist/init-cloudflare.d.ts +43 -0
  56. package/dist/init-cloudflare.js +839 -0
  57. package/dist/init-platform.d.ts +40 -0
  58. package/dist/init-platform.js +195 -0
  59. package/dist/init.d.ts +16 -38
  60. package/dist/init.js +209 -99
  61. package/dist/node_modules/.pnpm/am-i-vibing@0.5.0/node_modules/am-i-vibing/dist/detector-1yx2Hoe0.js +294 -0
  62. package/dist/node_modules/.pnpm/process-ancestry@0.1.0/node_modules/process-ancestry/dist/index.js +94 -0
  63. package/dist/{cloudflare → packages/cloudflare}/src/cache/cdn-adapter.runtime.js +1 -1
  64. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.d.ts +2 -2
  65. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.js +9 -2
  66. package/dist/packages/cloudflare/src/deploy-config.js +150 -0
  67. package/dist/packages/cloudflare/src/deploy-help.js +55 -0
  68. package/dist/packages/cloudflare/src/deploy.js +272 -0
  69. package/dist/{cloudflare → packages/cloudflare/src}/tpr.d.ts +4 -41
  70. package/dist/{cloudflare → packages/cloudflare/src}/tpr.js +44 -20
  71. package/dist/plugins/ast-scope.d.ts +16 -0
  72. package/dist/plugins/ast-scope.js +62 -0
  73. package/dist/plugins/ast-utils.d.ts +27 -1
  74. package/dist/plugins/ast-utils.js +32 -1
  75. package/dist/plugins/css-module-imports.d.ts +14 -0
  76. package/dist/plugins/css-module-imports.js +59 -0
  77. package/dist/plugins/extensionless-dynamic-import.js +2 -2
  78. package/dist/plugins/fonts.js +1 -1
  79. package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
  80. package/dist/plugins/ignore-dynamic-requests.js +530 -0
  81. package/dist/plugins/og-assets.js +2 -1
  82. package/dist/plugins/optimize-imports.js +1 -1
  83. package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
  84. package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
  85. package/dist/plugins/sass.d.ts +17 -1
  86. package/dist/plugins/sass.js +74 -1
  87. package/dist/plugins/styled-jsx.d.ts +16 -0
  88. package/dist/plugins/styled-jsx.js +149 -0
  89. package/dist/plugins/typeof-window.d.ts +1 -1
  90. package/dist/plugins/typeof-window.js +28 -56
  91. package/dist/routing/app-route-graph.d.ts +23 -2
  92. package/dist/routing/app-route-graph.js +138 -38
  93. package/dist/routing/file-matcher.d.ts +11 -1
  94. package/dist/routing/file-matcher.js +18 -3
  95. package/dist/routing/pages-router.js +2 -2
  96. package/dist/server/api-handler.js +9 -14
  97. package/dist/server/app-browser-action-result.d.ts +4 -1
  98. package/dist/server/app-browser-action-result.js +8 -1
  99. package/dist/server/app-browser-entry.js +198 -57
  100. package/dist/server/app-browser-interception-context.d.ts +1 -0
  101. package/dist/server/app-browser-interception-context.js +4 -2
  102. package/dist/server/app-browser-navigation-controller.d.ts +2 -0
  103. package/dist/server/app-browser-navigation-controller.js +60 -31
  104. package/dist/server/app-browser-server-action-client.d.ts +1 -1
  105. package/dist/server/app-browser-server-action-client.js +8 -7
  106. package/dist/server/app-browser-state.d.ts +12 -1
  107. package/dist/server/app-browser-state.js +25 -8
  108. package/dist/server/app-browser-visible-commit.d.ts +6 -1
  109. package/dist/server/app-browser-visible-commit.js +42 -24
  110. package/dist/server/app-elements-wire.d.ts +9 -2
  111. package/dist/server/app-elements-wire.js +8 -1
  112. package/dist/server/app-elements.d.ts +2 -2
  113. package/dist/server/app-elements.js +4 -3
  114. package/dist/server/app-fallback-renderer.d.ts +2 -1
  115. package/dist/server/app-fallback-renderer.js +10 -6
  116. package/dist/server/app-hydration-cache-publication.d.ts +11 -0
  117. package/dist/server/app-hydration-cache-publication.js +43 -0
  118. package/dist/server/app-middleware.d.ts +4 -3
  119. package/dist/server/app-middleware.js +8 -3
  120. package/dist/server/app-optimistic-routing.js +11 -4
  121. package/dist/server/app-page-boundary.js +2 -1
  122. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  123. package/dist/server/app-page-cache-finalizer.js +5 -4
  124. package/dist/server/app-page-cache-render.d.ts +1 -0
  125. package/dist/server/app-page-cache-render.js +8 -4
  126. package/dist/server/app-page-cache.d.ts +1 -0
  127. package/dist/server/app-page-cache.js +27 -7
  128. package/dist/server/app-page-dispatch.d.ts +1 -0
  129. package/dist/server/app-page-dispatch.js +26 -8
  130. package/dist/server/app-page-element-builder.d.ts +2 -1
  131. package/dist/server/app-page-element-builder.js +59 -4
  132. package/dist/server/app-page-execution.js +2 -1
  133. package/dist/server/app-page-probe.js +5 -0
  134. package/dist/server/app-page-render-identity.d.ts +2 -0
  135. package/dist/server/app-page-render-identity.js +3 -2
  136. package/dist/server/app-page-render.d.ts +2 -0
  137. package/dist/server/app-page-render.js +85 -16
  138. package/dist/server/app-page-request.d.ts +14 -0
  139. package/dist/server/app-page-request.js +69 -4
  140. package/dist/server/app-page-response.d.ts +7 -0
  141. package/dist/server/app-page-response.js +16 -4
  142. package/dist/server/app-page-route-wiring.d.ts +8 -1
  143. package/dist/server/app-page-route-wiring.js +32 -18
  144. package/dist/server/app-page-segment-state.d.ts +2 -1
  145. package/dist/server/app-page-segment-state.js +3 -1
  146. package/dist/server/app-page-stream.d.ts +7 -1
  147. package/dist/server/app-page-stream.js +3 -1
  148. package/dist/server/app-route-handler-execution.js +5 -1
  149. package/dist/server/app-route-handler-response.js +1 -0
  150. package/dist/server/app-route-handler-runtime.js +1 -1
  151. package/dist/server/app-route-tree-prefetch.d.ts +43 -0
  152. package/dist/server/app-route-tree-prefetch.js +187 -0
  153. package/dist/server/app-router-entry.js +9 -2
  154. package/dist/server/app-rsc-cache-busting.d.ts +2 -1
  155. package/dist/server/app-rsc-cache-busting.js +9 -4
  156. package/dist/server/app-rsc-handler.d.ts +7 -0
  157. package/dist/server/app-rsc-handler.js +148 -39
  158. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  159. package/dist/server/app-rsc-render-mode.js +4 -1
  160. package/dist/server/app-rsc-request-normalization.d.ts +6 -6
  161. package/dist/server/app-rsc-request-normalization.js +10 -8
  162. package/dist/server/app-segment-config.d.ts +11 -0
  163. package/dist/server/app-segment-config.js +62 -6
  164. package/dist/server/app-server-action-execution.d.ts +1 -0
  165. package/dist/server/app-server-action-execution.js +61 -30
  166. package/dist/server/app-ssr-entry.d.ts +3 -0
  167. package/dist/server/app-ssr-entry.js +13 -5
  168. package/dist/server/app-ssr-stream.d.ts +7 -3
  169. package/dist/server/app-ssr-stream.js +10 -6
  170. package/dist/server/app-visited-response-cache.d.ts +6 -0
  171. package/dist/server/app-visited-response-cache.js +3 -1
  172. package/dist/server/cache-control.d.ts +3 -1
  173. package/dist/server/cache-control.js +13 -1
  174. package/dist/server/client-trace-metadata.js +26 -0
  175. package/dist/server/default-global-not-found-module.d.ts +14 -0
  176. package/dist/server/default-global-not-found-module.js +14 -0
  177. package/dist/server/dev-route-files.js +4 -0
  178. package/dist/server/dev-server.d.ts +11 -1
  179. package/dist/server/dev-server.js +94 -35
  180. package/dist/server/dev-stack-sourcemap.d.ts +1 -1
  181. package/dist/server/dev-stack-sourcemap.js +1 -1
  182. package/dist/server/fetch-handler.d.ts +2 -0
  183. package/dist/server/fetch-handler.js +18 -0
  184. package/dist/server/headers.d.ts +7 -15
  185. package/dist/server/headers.js +11 -18
  186. package/dist/server/image-optimization.d.ts +51 -1
  187. package/dist/server/image-optimization.js +52 -2
  188. package/dist/server/implicit-tags.js +1 -1
  189. package/dist/server/isr-cache.d.ts +3 -2
  190. package/dist/server/isr-cache.js +9 -5
  191. package/dist/server/metadata-routes.d.ts +3 -1
  192. package/dist/server/metadata-routes.js +12 -2
  193. package/dist/server/middleware-runtime.js +6 -1
  194. package/dist/server/navigation-planner.d.ts +1 -0
  195. package/dist/server/navigation-planner.js +14 -3
  196. package/dist/server/pages-api-route.js +2 -2
  197. package/dist/server/pages-asset-tags.d.ts +4 -6
  198. package/dist/server/pages-asset-tags.js +12 -12
  199. package/dist/server/pages-client-assets.d.ts +12 -0
  200. package/dist/server/pages-client-assets.js +10 -0
  201. package/dist/server/pages-data-route.d.ts +4 -2
  202. package/dist/server/pages-data-route.js +18 -4
  203. package/dist/server/pages-dev-module-url.d.ts +2 -1
  204. package/dist/server/pages-dev-module-url.js +6 -3
  205. package/dist/server/pages-node-compat.d.ts +19 -1
  206. package/dist/server/pages-node-compat.js +81 -4
  207. package/dist/server/pages-page-data.d.ts +33 -1
  208. package/dist/server/pages-page-data.js +62 -33
  209. package/dist/server/pages-page-handler.d.ts +2 -1
  210. package/dist/server/pages-page-handler.js +38 -19
  211. package/dist/server/pages-page-response.d.ts +8 -0
  212. package/dist/server/pages-page-response.js +7 -3
  213. package/dist/server/pages-request-pipeline.d.ts +9 -5
  214. package/dist/server/pages-request-pipeline.js +55 -7
  215. package/dist/server/pages-router-entry.d.ts +30 -0
  216. package/dist/server/pages-router-entry.js +108 -0
  217. package/dist/server/prerender-manifest.d.ts +3 -1
  218. package/dist/server/prerender-route-params.js +1 -1
  219. package/dist/server/prod-server.d.ts +5 -3
  220. package/dist/server/prod-server.js +68 -39
  221. package/dist/server/request-pipeline.js +2 -1
  222. package/dist/server/seed-cache.js +4 -4
  223. package/dist/shims/app-router-scroll-state.d.ts +1 -0
  224. package/dist/shims/app-router-scroll-state.js +1 -0
  225. package/dist/shims/app-router-scroll.js +2 -1
  226. package/dist/shims/cache-handler.js +8 -1
  227. package/dist/shims/cache.js +22 -15
  228. package/dist/shims/cdn-cache.js +1 -1
  229. package/dist/shims/dynamic-preload-chunks.js +2 -1
  230. package/dist/shims/fetch-cache.d.ts +3 -1
  231. package/dist/shims/fetch-cache.js +77 -52
  232. package/dist/shims/form.d.ts +4 -2
  233. package/dist/shims/form.js +37 -43
  234. package/dist/shims/headers.d.ts +7 -1
  235. package/dist/shims/headers.js +17 -3
  236. package/dist/shims/image.js +75 -14
  237. package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
  238. package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
  239. package/dist/shims/internal/app-route-detection.d.ts +2 -17
  240. package/dist/shims/internal/app-route-detection.js +4 -17
  241. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +23 -0
  242. package/dist/shims/internal/hybrid-client-route-owner-direct.js +51 -0
  243. package/dist/shims/internal/hybrid-client-route-owner.d.ts +3 -15
  244. package/dist/shims/internal/hybrid-client-route-owner.js +40 -58
  245. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +10 -5
  246. package/dist/shims/internal/pages-data-fetch-dedup.js +84 -14
  247. package/dist/shims/internal/pages-data-target.d.ts +18 -12
  248. package/dist/shims/internal/pages-data-target.js +97 -13
  249. package/dist/shims/internal/pages-router-components.d.ts +7 -0
  250. package/dist/shims/internal/pages-router-components.js +13 -0
  251. package/dist/shims/layout-segment-context.d.ts +4 -1
  252. package/dist/shims/layout-segment-context.js +18 -4
  253. package/dist/shims/link.d.ts +1 -1
  254. package/dist/shims/link.js +224 -58
  255. package/dist/shims/navigation.d.ts +19 -3
  256. package/dist/shims/navigation.js +245 -47
  257. package/dist/shims/request-context.js +18 -0
  258. package/dist/shims/root-params.d.ts +15 -1
  259. package/dist/shims/root-params.js +21 -1
  260. package/dist/shims/router.d.ts +5 -7
  261. package/dist/shims/router.js +387 -102
  262. package/dist/shims/server.js +3 -2
  263. package/dist/shims/slot.js +3 -6
  264. package/dist/shims/unified-request-context.js +1 -0
  265. package/dist/typegen.js +1 -1
  266. package/dist/utils/client-runtime-metadata.d.ts +2 -18
  267. package/dist/utils/client-runtime-metadata.js +31 -22
  268. package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
  269. package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
  270. package/dist/utils/domain-locale.d.ts +6 -3
  271. package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
  272. package/dist/{server → utils}/middleware-request-headers.js +3 -3
  273. package/dist/utils/project.d.ts +48 -2
  274. package/dist/utils/project.js +225 -4
  275. package/dist/utils/protocol-headers.d.ts +23 -0
  276. package/dist/utils/protocol-headers.js +23 -0
  277. package/dist/utils/react-version.d.ts +4 -0
  278. package/dist/utils/react-version.js +44 -0
  279. package/dist/utils/vite-version.d.ts +2 -0
  280. package/dist/utils/vite-version.js +11 -1
  281. package/package.json +47 -2
  282. package/dist/deploy.d.ts +0 -197
  283. package/dist/deploy.js +0 -1040
  284. package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
  285. /package/dist/{cloudflare → packages/cloudflare}/src/utils/cache-control-metadata.js +0 -0
package/README.md CHANGED
@@ -49,30 +49,37 @@ Replace `next` with `vinext` in your scripts:
49
49
  ```bash
50
50
  vinext dev # Development server with HMR
51
51
  vinext build # Production build
52
- vinext deploy # Build and deploy to Cloudflare Workers
52
+ npx @vinext/cloudflare deploy # Build and deploy to Cloudflare Workers
53
53
  ```
54
54
 
55
+ With Vite+, use `vpx @vinext/cloudflare deploy`, or
56
+ `vp exec vinext-cloudflare deploy` when running the locally installed bin.
57
+
55
58
  vinext auto-detects your `app/` or `pages/` directory, loads `next.config.js`, and configures Vite automatically. No `vite.config.ts` required for basic usage.
56
59
 
57
60
  Your existing `pages/`, `app/`, `next.config.js`, and `public/` directories work as-is. Run `vinext check` first to scan for known compatibility issues, or use `vinext init` to [automate the full migration](#migrating-an-existing-nextjs-project).
58
61
 
59
62
  ### CLI reference
60
63
 
61
- | Command | Description |
62
- | --------------- | ----------------------------------------------------------------------- |
63
- | `vinext dev` | Start dev server with HMR |
64
- | `vinext build` | Production build (multi-environment for App Router: RSC + SSR + client) |
65
- | `vinext start` | Start local production server for testing |
66
- | `vinext deploy` | Build and deploy to Cloudflare Workers |
67
- | `vinext init` | Migrate a Next.js project to run under vinext |
68
- | `vinext check` | Scan your Next.js app for compatibility issues before migrating |
69
- | `vinext lint` | Delegate to eslint or oxlint |
64
+ | Command | Description |
65
+ | ---------------------------------- | ----------------------------------------------------------------------- |
66
+ | `vinext dev` | Start dev server with HMR |
67
+ | `vinext build` | Production build (multi-environment for App Router: RSC + SSR + client) |
68
+ | `vinext start` | Start local production server for testing |
69
+ | `npx @vinext/cloudflare deploy` | Build and deploy to Cloudflare Workers |
70
+ | `vp exec vinext-cloudflare deploy` | Build and deploy to Cloudflare Workers with Vite+ |
71
+ | `vinext init` | Migrate a Next.js project to run under vinext |
72
+ | `vinext check` | Scan your Next.js app for compatibility issues before migrating |
73
+ | `vinext lint` | Delegate to eslint or oxlint |
70
74
 
71
75
  Options: `-p / --port <port>`, `-H / --hostname <host>`, `--turbopack` (accepted, no-op).
72
76
 
73
- `vinext deploy` options: `--preview`, `--env <name>`, `--name <name>`, `--skip-build`, `--dry-run`, `--experimental-tpr`.
77
+ `@vinext/cloudflare deploy` options: `--preview`, `--env <name>`, `--name <name>`, `--skip-build`, `--dry-run`, `--experimental-tpr`.
78
+
79
+ `vinext init` prompts for a deployment target, defaulting to Cloudflare. Agents must ask the
80
+ user which target they want, then pass `--platform=cloudflare` or `--platform=node`.
74
81
 
75
- `vinext init` options: `--port <port>` (default: 3001), `--skip-check`, `--force`.
82
+ Other options: `--port <port>` (default: 3001), `--skip-check`, `--force`.
76
83
 
77
84
  If your `next.config.*` sets `output: "standalone"`, `vinext build` emits a self-hosting bundle at `dist/standalone/`. Start it with:
78
85
 
@@ -105,7 +112,9 @@ This will:
105
112
  3. Rename CJS config files (e.g. `postcss.config.js` -> `.cjs`) to avoid ESM conflicts
106
113
  4. Add `"type": "module"` to `package.json`
107
114
  5. Add `dev:vinext`, `build:vinext`, and `start:vinext` scripts to `package.json`
108
- 6. Generate a minimal `vite.config.ts`
115
+ 6. Prompt for a deployment platform (Cloudflare by default, or Node)
116
+ 7. Generate the matching `vite.config.ts`
117
+ 8. For Cloudflare, generate `wrangler.jsonc`
109
118
 
110
119
  The migration is non-destructive -- your existing Next.js setup continues to work alongside vinext. It does not modify `next.config`, `tsconfig.json`, or any source files, and it does not remove Next.js dependencies.
111
120
 
@@ -118,7 +127,10 @@ npm run start:vinext # Start vinext production server
118
127
  npm run dev # Still runs Next.js as before
119
128
  ```
120
129
 
121
- Use `--force` to overwrite an existing `vite.config.ts`, or `--skip-check` to skip the compatibility report.
130
+ Use `--platform=cloudflare` or `--platform=node` to skip the platform prompt. Cloudflare init
131
+ updates an existing JavaScript or TypeScript Vite config using its AST, preserving unrelated
132
+ settings. Use `--force` to replace an existing Node-target Vite config, or `--skip-check` to skip
133
+ the compatibility report.
122
134
 
123
135
  ## Why
124
136
 
@@ -126,7 +138,7 @@ Vite has become the default build tool for modern web frameworks — fast HMR, a
126
138
 
127
139
  vinext is an experiment: can we reimplement the Next.js API surface on Vite, so that existing Next.js applications can run on a completely different toolchain? The answer, so far, is mostly yes.
128
140
 
129
- vinext works everywhere. It natively supports Cloudflare Workers (with `vinext deploy`, bindings, KV caching), and can be deployed to Vercel, Netlify, AWS, Deno Deploy, and more via the [Nitro](https://v3.nitro.build/) Vite plugin. Native support for additional platforms is [planned](https://github.com/cloudflare/vinext/issues/80).
141
+ vinext works everywhere. It natively supports Cloudflare Workers (with `npx @vinext/cloudflare deploy` or `vp exec vinext-cloudflare deploy`, bindings, KV caching), and can be deployed to Vercel, Netlify, AWS, Deno Deploy, and more via the [Nitro](https://v3.nitro.build/) Vite plugin. Native support for additional platforms is [planned](https://github.com/cloudflare/vinext/issues/80).
130
142
 
131
143
  **Alternatives worth knowing about:**
132
144
 
@@ -173,7 +185,7 @@ Both. File-system routing, SSR, client hydration, and deployment to Cloudflare W
173
185
  Next.js 16.x. No support for deprecated APIs from older versions.
174
186
 
175
187
  **Can I deploy to AWS/Netlify/other platforms?**
176
- Yes. Add the [Nitro](https://v3.nitro.build/) Vite plugin alongside vinext, and you can deploy to Vercel, Netlify, AWS Amplify, Deno Deploy, Azure, and [many more](https://v3.nitro.build/deploy). See [Other platforms (via Nitro)](#other-platforms-via-nitro) for setup. For Cloudflare Workers, the native integration (`vinext deploy`) gives you the smoothest experience. Native adapters for more platforms are [planned](https://github.com/cloudflare/vinext/issues/80).
188
+ Yes. Add the [Nitro](https://v3.nitro.build/) Vite plugin alongside vinext, and you can deploy to Vercel, Netlify, AWS Amplify, Deno Deploy, Azure, and [many more](https://v3.nitro.build/deploy). See [Other platforms (via Nitro)](#other-platforms-via-nitro) for setup. For Cloudflare Workers, the native integration (`npx @vinext/cloudflare deploy` or `vp exec vinext-cloudflare deploy`) gives you the smoothest experience. Native adapters for more platforms are [planned](https://github.com/cloudflare/vinext/issues/80).
177
189
 
178
190
  **What happens when Next.js releases a new feature?**
179
191
  We track the public Next.js API surface and add support for new stable features. Experimental or unstable Next.js features are lower priority. The plan is to add commit-level tracking of the Next.js repo so we can stay current as new versions are released.
@@ -182,16 +194,16 @@ We track the public Next.js API surface and add support for new stable features.
182
194
 
183
195
  ### Cloudflare Workers
184
196
 
185
- vinext has native integration with Cloudflare Workers through `@cloudflare/vite-plugin`, including bindings access via `cloudflare:workers`, KV caching, image optimization, and the `vinext deploy` one-command workflow.
197
+ vinext has native integration with Cloudflare Workers through `@cloudflare/vite-plugin`, including bindings access via `cloudflare:workers`, KV caching, image optimization, and the `@vinext/cloudflare deploy` one-command workflow.
186
198
 
187
199
  #### Prerequisites
188
200
 
189
- Before running `vinext deploy` for the first time you need to authenticate with Cloudflare and tell wrangler which account to deploy to.
201
+ Before running `npx @vinext/cloudflare deploy` for the first time you need to authenticate with Cloudflare and tell wrangler which account to deploy to.
190
202
 
191
203
  **Authentication — pick one:**
192
204
 
193
205
  - **`wrangler login`** (recommended for local development) — opens a browser window to authenticate. Run it once and wrangler caches the token.
194
- - **`CLOUDFLARE_API_TOKEN` env var** (CI / non-interactive) — create a token at [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens) using the **Edit Cloudflare Workers** template. That template grants all the permissions `vinext deploy` needs.
206
+ - **`CLOUDFLARE_API_TOKEN` env var** (CI / non-interactive) — create a token at [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens) using the **Edit Cloudflare Workers** template. That template grants all the permissions `@vinext/cloudflare deploy` needs.
195
207
 
196
208
  **Account ID:**
197
209
 
@@ -208,16 +220,24 @@ Find your account ID in the Cloudflare dashboard URL (`dash.cloudflare.com/<acco
208
220
 
209
221
  Alternatively, set the `CLOUDFLARE_ACCOUNT_ID` environment variable instead of hardcoding it in the config file.
210
222
 
211
- `vinext deploy` auto-generates the necessary configuration files (`vite.config.ts`, `wrangler.jsonc`, `worker/index.ts`) if they don't exist, builds the application, and deploys to Workers.
223
+ Run `vinext init --platform=cloudflare` first to install dependencies and create or AST-update
224
+ `vite.config.*` and `wrangler.jsonc`. `@vinext/cloudflare deploy` then validates that setup, builds the
225
+ application, and deploys to Workers without rewriting project configuration.
226
+
227
+ Cloudflare init can also configure image optimization declaratively in the Vite config with
228
+ `imagesOptimizer()` and add the matching Wrangler Images binding. The built-in fetch handlers register
229
+ that optimizer at runtime; image optimization is not implemented or generated by `@vinext/cloudflare deploy`.
212
230
 
213
231
  ```bash
214
- vinext deploy
215
- vinext deploy --env staging
232
+ npx @vinext/cloudflare deploy
233
+ vp exec vinext-cloudflare deploy
234
+ npx @vinext/cloudflare deploy --env staging
235
+ vp exec vinext-cloudflare deploy --env staging
216
236
  ```
217
237
 
218
238
  Use `--env <name>` to target `wrangler.jsonc` `env.<name>`. `--preview` is shorthand for `--env preview`.
219
239
 
220
- The deploy command also auto-detects and fixes common migration issues:
240
+ The init command also auto-detects and fixes common migration issues:
221
241
 
222
242
  - Adds `"type": "module"` to package.json if missing
223
243
  - Resolves tsconfig.json path aliases automatically (via `vite-tsconfig-paths`)
@@ -263,10 +283,11 @@ For TypeScript types, generate them with `wrangler types` and the `env` import w
263
283
  TPR queries Cloudflare zone analytics at deploy time to find which pages actually get traffic, pre-renders only those, and uploads them to KV cache. The result is SSG-level latency for popular pages without pre-rendering your entire site.
264
284
 
265
285
  ```bash
266
- vinext deploy --experimental-tpr # Pre-render pages covering 90% of traffic
267
- vinext deploy --experimental-tpr --tpr-coverage 95 # More aggressive coverage
268
- vinext deploy --experimental-tpr --tpr-limit 500 # Cap at 500 pages
269
- vinext deploy --experimental-tpr --tpr-window 48 # Use 48h of analytics
286
+ npx @vinext/cloudflare deploy --experimental-tpr # Pre-render pages covering 90% of traffic
287
+ vp exec vinext-cloudflare deploy --experimental-tpr # Same, with Vite+
288
+ npx @vinext/cloudflare deploy --experimental-tpr --tpr-coverage 95 # More aggressive coverage
289
+ npx @vinext/cloudflare deploy --experimental-tpr --tpr-limit 500 # Cap at 500 pages
290
+ npx @vinext/cloudflare deploy --experimental-tpr --tpr-window 48 # Use 48h of analytics
270
291
  ```
271
292
 
272
293
  Requires a custom domain (zone analytics are unavailable on `*.workers.dev`) and `CLOUDFLARE_API_TOKEN` with Zone.Analytics read permission.
@@ -326,7 +347,7 @@ NITRO_PRESET=netlify npx vite build
326
347
  NITRO_PRESET=deno_deploy npx vite build
327
348
  ```
328
349
 
329
- > **Deploying to Cloudflare?** You can use Nitro, but the native integration (`vinext deploy` / `@cloudflare/vite-plugin`) is recommended. It provides the best developer experience with `cloudflare:workers` bindings, KV caching, image optimization, and one-command deploys.
350
+ > **Deploying to Cloudflare?** You can use Nitro, but the native integration (`npx @vinext/cloudflare deploy`, `vp exec vinext-cloudflare deploy`, and `@cloudflare/vite-plugin`) is recommended. It provides the best developer experience with `cloudflare:workers` bindings, KV caching, image optimization, and one-command deploys.
330
351
 
331
352
  <details>
332
353
  <summary>Vercel</summary>
@@ -508,7 +529,7 @@ Load order matches Next.js (highest priority first):
508
529
  Modes:
509
530
 
510
531
  - `vinext dev` uses `development`
511
- - `vinext build`, `vinext start`, and `vinext deploy` use `production`
532
+ - `vinext build`, `vinext start`, and `@vinext/cloudflare deploy` use `production`
512
533
 
513
534
  Variable expansion (`$VAR` / `${VAR}`) is supported.
514
535
 
@@ -597,7 +618,7 @@ These are gaps we'd like to close — distinct from the [intentional exclusions]
597
618
  - **Google Fonts are loaded from the CDN, not self-hosted.** No `size-adjust` fallback font metrics. Local fonts work but `@font-face` CSS is injected at runtime, not extracted at build time.
598
619
  - **Route segment config** — `runtime` and `preferredRegion` are ignored (everything runs in the same environment).
599
620
  - **Node.js production server (`vinext start`)** works for testing but is less complete than Workers deployment. Cloudflare Workers is the primary target.
600
- - **Native Node modules (sharp, resvg, satori, lightningcss, @napi-rs/canvas)** crash Vite's RSC dev environment. Dynamic OG image/icon routes using these work in production builds but not in dev mode. These are auto-stubbed during `vinext deploy`.
621
+ - **Native Node modules (sharp, resvg, satori, lightningcss, @napi-rs/canvas)** crash Vite's RSC dev environment. Dynamic OG image/icon routes using these work in production builds but not in dev mode. These are auto-stubbed during `@vinext/cloudflare deploy`.
601
622
  - **`next.config.ts` `baseUrl` bare imports require Vite 8.** A `next.config.ts` that imports a bare specifier resolved through `tsconfig.json`'s `compilerOptions.baseUrl` (e.g. `import { bar } from "bar"` resolving to a local `bar.ts`) relies on Vite 8's native `resolve.tsconfigPaths` (Rolldown/oxc-resolver). On Vite 7 there is no native equivalent, so these imports are not resolved. `compilerOptions.paths` aliases (e.g. `@/foo`) work on both Vite 7 and 8. Note that if a bare import matches both a `baseUrl`-local file and an installed package of the same name, the installed package wins (vinext keeps packages externalized so CJS config plugins like `@next/mdx` keep working).
602
623
 
603
624
  ## Benchmarks
@@ -13,6 +13,8 @@ type EmitRestoredCssUrlAsset = (asset: {
13
13
  * safe to run on every build-environment stylesheet, vendored CSS included.
14
14
  */
15
15
  declare function markCssUrlAssetReferences(code: string, id: string): string | null;
16
+ /** Rebase relative asset URLs when Sass inlines a partial into an entry file. */
17
+ declare function rebaseCssUrlAssetReferences(code: string, sourceDirectory: string, targetDirectory: string): string | null;
16
18
  /**
17
19
  * Mutates emitted CSS assets in place so byte-identical `url()` dependencies
18
20
  * keep their distinct Next-compatible filenames. Sibling files are produced via
@@ -26,4 +28,4 @@ declare function markCssUrlAssetReferences(code: string, id: string): string | n
26
28
  */
27
29
  declare function restoreDedupedCssAssetReferences(bundle: CssUrlAssetBundle, emitRestoredAsset: EmitRestoredCssUrlAsset): void;
28
30
  //#endregion
29
- export { markCssUrlAssetReferences, restoreDedupedCssAssetReferences };
31
+ export { markCssUrlAssetReferences, rebaseCssUrlAssetReferences, restoreDedupedCssAssetReferences };
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  //#region src/build/css-url-assets.ts
2
3
  /**
3
4
  * Next-parity for CSS `url()` asset dependencies.
@@ -128,6 +129,22 @@ function markCssUrlAssetReferences(code, id) {
128
129
  });
129
130
  });
130
131
  }
132
+ /** Rebase relative asset URLs when Sass inlines a partial into an entry file. */
133
+ function rebaseCssUrlAssetReferences(code, sourceDirectory, targetDirectory) {
134
+ return rewriteCssUrls(code, (rawUrl) => {
135
+ const parts = splitUrl(rawUrl.trim());
136
+ if (!isRelativeAssetUrl(parts.path)) return null;
137
+ const lower = parts.path.toLowerCase();
138
+ if (!CSS_ASSET_EXT_RE.test(lower) || lower.endsWith(".css")) return null;
139
+ const absolutePath = path.resolve(sourceDirectory, parts.path);
140
+ let rebasedPath = path.relative(targetDirectory, absolutePath).replaceAll("\\", "/");
141
+ if (!rebasedPath.startsWith(".")) rebasedPath = `./${rebasedPath}`;
142
+ return joinUrl({
143
+ ...parts,
144
+ path: rebasedPath
145
+ });
146
+ });
147
+ }
131
148
  function isCssFileName(fileName) {
132
149
  return fileName.toLowerCase().endsWith(".css");
133
150
  }
@@ -210,4 +227,4 @@ function stripChunkMarkers(code) {
210
227
  return code.replace(CSS_URL_MARKER_PARAM_RE, (_m, leadingQuestion) => leadingQuestion ? leadingQuestion : "");
211
228
  }
212
229
  //#endregion
213
- export { markCssUrlAssetReferences, restoreDedupedCssAssetReferences };
230
+ export { markCssUrlAssetReferences, rebaseCssUrlAssetReferences, restoreDedupedCssAssetReferences };
@@ -0,0 +1,7 @@
1
+ //#region src/build/inject-pregenerated-paths.d.ts
2
+ declare global {
3
+ var __VINEXT_PREGENERATED_CONCRETE_PATHS: unknown;
4
+ }
5
+ declare function injectPregeneratedConcretePaths(root: string): void;
6
+ //#endregion
7
+ export { injectPregeneratedConcretePaths };
@@ -0,0 +1,21 @@
1
+ import { escapeRegExp } from "../utils/regex.js";
2
+ import { readPrerenderManifest } from "../server/prerender-manifest.js";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ //#region src/build/inject-pregenerated-paths.ts
6
+ const VINEXT_PREGEN_START = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_START__ */";
7
+ const VINEXT_PREGEN_END = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_END__ */";
8
+ const VINEXT_PREGEN_RE = new RegExp(`${escapeRegExp(VINEXT_PREGEN_START)}[\\s\\S]*?${escapeRegExp(VINEXT_PREGEN_END)}\\n?`, "g");
9
+ function injectPregeneratedConcretePaths(root) {
10
+ const workerEntry = path.resolve(root, "dist", "server", "index.js");
11
+ if (!fs.existsSync(workerEntry)) return;
12
+ let code = fs.readFileSync(workerEntry, "utf-8").replace(VINEXT_PREGEN_RE, "");
13
+ const table = readPrerenderManifest(path.join(root, "dist", "server", "vinext-prerender.json"))?.pregeneratedConcretePaths ?? [];
14
+ if (table.length > 0) {
15
+ globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = table;
16
+ code = `${VINEXT_PREGEN_START}\nglobalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = ${JSON.stringify(table)};\n${VINEXT_PREGEN_END}\n` + code;
17
+ } else delete globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS;
18
+ fs.writeFileSync(workerEntry, code);
19
+ }
20
+ //#endregion
21
+ export { injectPregeneratedConcretePaths };
@@ -1,6 +1,6 @@
1
+ import { classifyLayoutSegmentConfig } from "./report.js";
1
2
  import { AppElementsWire } from "../server/app-elements-wire.js";
2
3
  import "../server/app-elements.js";
3
- import { classifyLayoutSegmentConfig } from "./report.js";
4
4
  import { createAppPageTreePath } from "../server/app-page-route-wiring.js";
5
5
  //#region src/build/layout-classification.ts
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import { apiRouter, pagesRouter } from "../routing/pages-router.js";
2
- import { appRouter } from "../routing/app-router.js";
3
2
  import { buildReportRows } from "./report.js";
3
+ import { appRouter } from "../routing/app-router.js";
4
4
  //#region src/build/nitro-route-rules.ts
5
5
  /**
6
6
  * Scans the filesystem for route files and generates Nitro `routeRules` for ISR routes.
@@ -0,0 +1,11 @@
1
+ import { PagesClientAssets } from "../server/pages-client-assets.js";
2
+
3
+ //#region src/build/pages-client-assets-module.d.ts
4
+ declare const PAGES_CLIENT_ASSETS_MODULE = "vinext-client-assets.js";
5
+ declare function buildPagesClientAssetsModule(assets: PagesClientAssets): string;
6
+ declare function writePagesClientAssetsModuleIfMissing(outputDir: string, moduleSource: string): void;
7
+ declare function setPagesClientAssetsBuildMetadata(buildSession: string, moduleSource: string): void;
8
+ declare function takePagesClientAssetsBuildMetadata(buildSession: string): string | null;
9
+ declare function clearPagesClientAssetsBuildMetadata(buildSession: string): void;
10
+ //#endregion
11
+ export { PAGES_CLIENT_ASSETS_MODULE, buildPagesClientAssetsModule, clearPagesClientAssetsBuildMetadata, setPagesClientAssetsBuildMetadata, takePagesClientAssetsBuildMetadata, writePagesClientAssetsModuleIfMissing };
@@ -0,0 +1,27 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ //#region src/build/pages-client-assets-module.ts
4
+ const PAGES_CLIENT_ASSETS_MODULE = "vinext-client-assets.js";
5
+ const pagesClientAssetsByBuildSession = /* @__PURE__ */ new Map();
6
+ function buildPagesClientAssetsModule(assets) {
7
+ return `export default ${JSON.stringify(assets)};\n`;
8
+ }
9
+ function writePagesClientAssetsModuleIfMissing(outputDir, moduleSource) {
10
+ const outputPath = path.join(outputDir, PAGES_CLIENT_ASSETS_MODULE);
11
+ if (fs.existsSync(outputPath)) return;
12
+ fs.mkdirSync(outputDir, { recursive: true });
13
+ fs.writeFileSync(outputPath, moduleSource);
14
+ }
15
+ function setPagesClientAssetsBuildMetadata(buildSession, moduleSource) {
16
+ pagesClientAssetsByBuildSession.set(buildSession, moduleSource);
17
+ }
18
+ function takePagesClientAssetsBuildMetadata(buildSession) {
19
+ const moduleSource = pagesClientAssetsByBuildSession.get(buildSession) ?? null;
20
+ pagesClientAssetsByBuildSession.delete(buildSession);
21
+ return moduleSource;
22
+ }
23
+ function clearPagesClientAssetsBuildMetadata(buildSession) {
24
+ pagesClientAssetsByBuildSession.delete(buildSession);
25
+ }
26
+ //#endregion
27
+ export { PAGES_CLIENT_ASSETS_MODULE, buildPagesClientAssetsModule, clearPagesClientAssetsBuildMetadata, setPagesClientAssetsBuildMetadata, takePagesClientAssetsBuildMetadata, writePagesClientAssetsModuleIfMissing };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,49 @@
1
+ import { NoOpCacheHandler, setCacheHandler } from "../shims/cache-handler.js";
2
+ import { startProdServer } from "../server/prod-server.js";
3
+ //#region src/build/prerender-server-entry.ts
4
+ /**
5
+ * Child-process entry for a parallel prerender render server.
6
+ *
7
+ * Forked by `prerender-server-pool.ts`. React SSR/RSC rendering is CPU-bound
8
+ * JS, so a single-process promise pool serializes every render on one core.
9
+ * This entry lets the prerender phase fan rendering out across N OS processes
10
+ * (one per core, like Next.js's static worker pool): each child starts a normal
11
+ * vinext production server on an ephemeral port and reports it to the parent
12
+ * over IPC. The parent then load-balances per-route fetches across the pool.
13
+ *
14
+ * `VINEXT_PRERENDER` / `VINEXT_PRERENDER_OUTDIR` are passed via the fork env so
15
+ * they are set before any module loads (some server modules read the flag at
16
+ * import time).
17
+ */
18
+ async function main() {
19
+ const outDir = process.env.VINEXT_PRERENDER_OUTDIR;
20
+ if (!outDir) throw new Error("[vinext] prerender server worker: VINEXT_PRERENDER_OUTDIR not set");
21
+ setCacheHandler(new NoOpCacheHandler());
22
+ const { port } = await startProdServer({
23
+ port: 0,
24
+ host: "127.0.0.1",
25
+ outDir,
26
+ noCompression: true,
27
+ purpose: "prerender",
28
+ silent: true
29
+ });
30
+ if (typeof process.send === "function") process.send({
31
+ type: "ready",
32
+ port
33
+ });
34
+ else throw new Error("[vinext] prerender server worker: no IPC channel to parent");
35
+ }
36
+ process.on("disconnect", () => process.exit(0));
37
+ main().catch((err) => {
38
+ const message = err instanceof Error ? err.stack ?? err.message : String(err);
39
+ if (typeof process.send === "function") {
40
+ process.send({
41
+ type: "error",
42
+ error: message
43
+ }, () => process.exit(1));
44
+ return;
45
+ }
46
+ process.exit(1);
47
+ });
48
+ //#endregion
49
+ export {};
@@ -0,0 +1,44 @@
1
+ //#region src/build/prerender-server-pool.d.ts
2
+ /**
3
+ * Whether the forkable worker entry exists as a runnable `.js` sibling. False
4
+ * when vinext runs from source (e.g. the test suite transpiles `.ts` on the
5
+ * fly and a forked plain-node child can't load `.ts`); callers fall back to the
6
+ * single in-process server. True in the published/built package.
7
+ */
8
+ declare function prerenderPoolAvailable(): boolean;
9
+ type PrerenderServerPool = {
10
+ /** Ports of the forked render servers, for round-robin per-route fetches. */ports: number[];
11
+ /**
12
+ * Throw if any worker exited unexpectedly (i.e. not via `close()`). The
13
+ * caller MUST call this after the render loop: a crashed worker makes every
14
+ * route routed to it fail with a connection error, which the per-route
15
+ * handler records as a (non-fatal) error — without this check the build
16
+ * would emit partial output and still exit 0. Mirrors the single-process
17
+ * design, where a server crash takes the whole build down loudly.
18
+ */
19
+ assertHealthy: () => void;
20
+ /**
21
+ * Record a failed fetch to a render worker. This closes the tiny ordering gap
22
+ * where the request can fail before Node has delivered the child `exit` event.
23
+ */
24
+ recordRenderError: (err: unknown) => void; /** Kill every forked server. Safe to call more than once. */
25
+ close: () => Promise<void>;
26
+ };
27
+ /**
28
+ * Choose how many render processes to fork for `routeCount` routes.
29
+ *
30
+ * Returns 1 to mean "don't fork — render in-process as before" (small apps,
31
+ * single/dual-core, low memory, or an explicit `--prerender-concurrency 1`).
32
+ * `maxOverride` comes from `--prerender-concurrency`, which caps both the
33
+ * number of in-flight route fetches and the worker count (the default is capped
34
+ * at `min(cores, 8)`).
35
+ */
36
+ declare function resolvePrerenderPoolSize(routeCount: number, maxOverride?: number): number;
37
+ /**
38
+ * Fork `size` production servers against `outDir` and resolve once all are
39
+ * listening. Each child reports its port over IPC. Rejects (and tears down any
40
+ * already-started children) if any child fails to come up.
41
+ */
42
+ declare function startPrerenderServerPool(outDir: string, size: number, entry?: string): Promise<PrerenderServerPool>;
43
+ //#endregion
44
+ export { PrerenderServerPool, prerenderPoolAvailable, resolvePrerenderPoolSize, startPrerenderServerPool };
@@ -0,0 +1,194 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import os from "node:os";
5
+ import { fork } from "node:child_process";
6
+ //#region src/build/prerender-server-pool.ts
7
+ /**
8
+ * Parallel prerender server pool.
9
+ *
10
+ * React SSR/RSC rendering during prerender is CPU-bound JS. vinext renders
11
+ * every route by fetching it from a single in-process production server, so the
12
+ * promise pool in `prerender.ts` only overlaps I/O — the actual rendering
13
+ * serializes on one core. This module forks a pool of OS processes (one vinext
14
+ * production server each, on its own ephemeral port) so the prerender phase can
15
+ * spread rendering across cores, mirroring Next.js's static worker pool. The
16
+ * caller load-balances per-route fetches across `ports`.
17
+ *
18
+ * child_process (not worker_threads) is deliberate: worker threads share the
19
+ * process and contend badly for CPU on this workload (measured ~2× slower
20
+ * per route and non-scaling), which is also why Next.js uses processes.
21
+ */
22
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
+ const WORKER_ENTRY = path.join(__dirname, "prerender-server-entry.js");
24
+ /**
25
+ * Whether the forkable worker entry exists as a runnable `.js` sibling. False
26
+ * when vinext runs from source (e.g. the test suite transpiles `.ts` on the
27
+ * fly and a forked plain-node child can't load `.ts`); callers fall back to the
28
+ * single in-process server. True in the published/built package.
29
+ */
30
+ function prerenderPoolAvailable() {
31
+ return fs.existsSync(WORKER_ENTRY);
32
+ }
33
+ /** Hard cap on render processes. Beyond this the main thread (which collects
34
+ * HTML and writes files) becomes the bottleneck, so more workers don't help. */
35
+ const MAX_POOL_SIZE = 8;
36
+ /** Don't fork a dedicated process unless a worker gets at least this many
37
+ * routes — below this the fork + bundle-import startup cost outweighs the
38
+ * parallel-render saving (measured: a 2-worker pool only reliably beats the
39
+ * single server from ~50 routes/worker up). */
40
+ const MIN_ROUTES_PER_WORKER = 48;
41
+ /** Rough memory budget reserved for the main build process plus its in-process
42
+ * prerender server. */
43
+ const APPROX_MAIN_PROCESS_BYTES = 768 * 1024 * 1024;
44
+ /** Rough memory budget per worker (a worker loads the whole server bundle).
45
+ * Used with total system memory as a coarse upper bound on constrained machines
46
+ * so K render processes don't OOM. Deliberately under-forks rather than risking
47
+ * OOM; measured mid-size apps are usually lower (~260-380 MB/worker). */
48
+ const APPROX_BYTES_PER_WORKER = 768 * 1024 * 1024;
49
+ /** Per-worker readiness timeout. */
50
+ const WORKER_READY_TIMEOUT_MS = 6e4;
51
+ /** Best-effort close timeout so build cleanup is not blocked forever by a wedged child. */
52
+ const WORKER_CLOSE_TIMEOUT_MS = 5e3;
53
+ const PRERENDER_WORKER_TRANSPORT_ERROR_CODES = new Set([
54
+ "ECONNREFUSED",
55
+ "ECONNRESET",
56
+ "EPIPE",
57
+ "UND_ERR_SOCKET"
58
+ ]);
59
+ /**
60
+ * Choose how many render processes to fork for `routeCount` routes.
61
+ *
62
+ * Returns 1 to mean "don't fork — render in-process as before" (small apps,
63
+ * single/dual-core, low memory, or an explicit `--prerender-concurrency 1`).
64
+ * `maxOverride` comes from `--prerender-concurrency`, which caps both the
65
+ * number of in-flight route fetches and the worker count (the default is capped
66
+ * at `min(cores, 8)`).
67
+ */
68
+ function resolvePrerenderPoolSize(routeCount, maxOverride) {
69
+ const cores = Math.max(1, os.availableParallelism());
70
+ const byCores = Math.max(1, Math.min(cores - 1, MAX_POOL_SIZE));
71
+ const memoryForWorkers = Math.max(0, os.totalmem() - APPROX_MAIN_PROCESS_BYTES);
72
+ const byMemory = Math.max(1, Math.floor(memoryForWorkers / APPROX_BYTES_PER_WORKER));
73
+ const cap = maxOverride && maxOverride > 0 ? Math.min(maxOverride, byCores) : byCores;
74
+ const byRoutes = Math.floor(routeCount / MIN_ROUTES_PER_WORKER);
75
+ return Math.max(1, Math.min(cap, byMemory, byRoutes));
76
+ }
77
+ function getErrorCauseCode(err) {
78
+ if (!(err instanceof Error)) return void 0;
79
+ const cause = err.cause;
80
+ return typeof cause?.code === "string" ? cause.code : void 0;
81
+ }
82
+ function isWorkerTransportError(err) {
83
+ const code = getErrorCauseCode(err);
84
+ if (code && PRERENDER_WORKER_TRANSPORT_ERROR_CODES.has(code)) return true;
85
+ return err instanceof TypeError && err.message === "fetch failed";
86
+ }
87
+ /**
88
+ * Fork `size` production servers against `outDir` and resolve once all are
89
+ * listening. Each child reports its port over IPC. Rejects (and tears down any
90
+ * already-started children) if any child fails to come up.
91
+ */
92
+ async function startPrerenderServerPool(outDir, size, entry = WORKER_ENTRY) {
93
+ const children = [];
94
+ let shuttingDown = false;
95
+ let crash = null;
96
+ let renderTransportError = null;
97
+ const close = async () => {
98
+ shuttingDown = true;
99
+ await Promise.all(children.map(closeChild));
100
+ };
101
+ try {
102
+ const readies = Array.from({ length: size }, () => {
103
+ const child = fork(entry, [], {
104
+ cwd: process.cwd(),
105
+ env: {
106
+ ...process.env,
107
+ VINEXT_PRERENDER: "1",
108
+ VINEXT_PRERENDER_OUTDIR: outDir
109
+ },
110
+ stdio: [
111
+ "ignore",
112
+ "inherit",
113
+ "inherit",
114
+ "ipc"
115
+ ]
116
+ });
117
+ children.push(child);
118
+ return new Promise((resolve, reject) => {
119
+ const timer = setTimeout(() => {
120
+ reject(/* @__PURE__ */ new Error("[vinext] prerender render server did not start within 60s"));
121
+ }, WORKER_READY_TIMEOUT_MS);
122
+ const cleanupStartupListeners = () => {
123
+ clearTimeout(timer);
124
+ child.off("message", onMessage);
125
+ child.off("error", onError);
126
+ child.off("exit", onStartupExit);
127
+ };
128
+ const onMessage = (msg) => {
129
+ cleanupStartupListeners();
130
+ if (msg?.type === "ready" && typeof msg.port === "number") {
131
+ child.once("exit", (code, signal) => {
132
+ if (!shuttingDown && !crash) crash = {
133
+ port: msg.port,
134
+ code,
135
+ signal
136
+ };
137
+ });
138
+ resolve(msg.port);
139
+ } else reject(/* @__PURE__ */ new Error(`[vinext] prerender render server failed: ${msg?.error ?? "unknown"}`));
140
+ };
141
+ const onError = (err) => {
142
+ cleanupStartupListeners();
143
+ reject(err);
144
+ };
145
+ const onStartupExit = (code, signal) => {
146
+ if (code !== null || signal !== null) {
147
+ cleanupStartupListeners();
148
+ reject(/* @__PURE__ */ new Error(`[vinext] prerender render server exited during startup (code ${code}, signal ${signal})`));
149
+ }
150
+ };
151
+ child.once("message", onMessage);
152
+ child.once("error", onError);
153
+ child.once("exit", onStartupExit);
154
+ });
155
+ });
156
+ const ports = await Promise.all(readies);
157
+ const recordRenderError = (err) => {
158
+ if (!renderTransportError && isWorkerTransportError(err)) renderTransportError = err instanceof Error ? err : new Error(String(err));
159
+ };
160
+ const assertHealthy = () => {
161
+ if (crash) throw new Error(`[vinext] A prerender render worker (port ${crash.port}) exited unexpectedly (code ${crash.code}, signal ${crash.signal}) during the build. Prerender output is incomplete; failing the build. This is often an out-of-memory kill — try a lower --prerender-concurrency.`);
162
+ if (renderTransportError) {
163
+ const causeCode = getErrorCauseCode(renderTransportError);
164
+ throw new Error(`[vinext] A prerender render worker request failed` + (causeCode ? ` (${causeCode})` : "") + " before the worker exit was observed. Prerender output is incomplete; failing the build. This is often an out-of-memory kill — try a lower --prerender-concurrency.");
165
+ }
166
+ };
167
+ return {
168
+ ports,
169
+ assertHealthy,
170
+ recordRenderError,
171
+ close
172
+ };
173
+ } catch (err) {
174
+ await close();
175
+ throw err;
176
+ }
177
+ }
178
+ async function closeChild(child) {
179
+ if (child.exitCode !== null || child.signalCode !== null) return;
180
+ const exited = new Promise((resolve) => {
181
+ const timer = setTimeout(resolve, WORKER_CLOSE_TIMEOUT_MS);
182
+ timer.unref?.();
183
+ const finish = () => {
184
+ clearTimeout(timer);
185
+ resolve();
186
+ };
187
+ child.once("exit", finish);
188
+ if (child.exitCode !== null || child.signalCode !== null) finish();
189
+ });
190
+ if (!child.killed) child.kill("SIGKILL");
191
+ await exited;
192
+ }
193
+ //#endregion
194
+ export { prerenderPoolAvailable, resolvePrerenderPoolSize, startPrerenderServerPool };
@@ -22,7 +22,8 @@ type PrerenderRouteResult = {
22
22
  * Omitted for non-dynamic routes where pattern === path.
23
23
  */
24
24
  path?: string; /** Which router produced this route. Used by cache seeding. */
25
- router: "app" | "pages"; /** Set to true when this is a PPR fallback shell. */
25
+ router: "app" | "pages"; /** Response headers that must be replayed with the prerendered artifact. */
26
+ headers?: Record<string, string>; /** Set to true when this is a PPR fallback shell. */
26
27
  fallback?: boolean;
27
28
  } | {
28
29
  route: string;