vinext 0.1.7 → 0.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 (279) hide show
  1. package/README.md +51 -30
  2. package/dist/build/inject-pregenerated-paths.d.ts +4 -0
  3. package/dist/build/inject-pregenerated-paths.js +18 -0
  4. package/dist/build/layout-classification.js +1 -1
  5. package/dist/build/nitro-route-rules.js +1 -1
  6. package/dist/build/pages-client-assets-module.d.ts +11 -0
  7. package/dist/build/pages-client-assets-module.js +27 -0
  8. package/dist/build/prerender.d.ts +2 -1
  9. package/dist/build/prerender.js +13 -6
  10. package/dist/build/report.d.ts +13 -2
  11. package/dist/build/report.js +99 -2
  12. package/dist/build/run-prerender.d.ts +7 -0
  13. package/dist/build/run-prerender.js +10 -1
  14. package/dist/build/standalone.js +2 -0
  15. package/dist/cache/cache-adapters-virtual.js +3 -3
  16. package/dist/check.d.ts +18 -0
  17. package/dist/check.js +77 -19
  18. package/dist/cli-args.d.ts +1 -2
  19. package/dist/cli-args.js +1 -1
  20. package/dist/cli-dev-config.d.ts +12 -0
  21. package/dist/cli-dev-config.js +23 -0
  22. package/dist/cli.js +113 -97
  23. package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
  24. package/dist/{server → client}/dev-error-overlay-store.js +1 -1
  25. package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
  26. package/dist/{server → client}/dev-error-overlay.js +2 -2
  27. package/dist/client/navigation-runtime.d.ts +2 -0
  28. package/dist/client/navigation-runtime.js +3 -1
  29. package/dist/client/window-next.d.ts +2 -1
  30. package/dist/cloudflare/index.d.ts +2 -2
  31. package/dist/cloudflare/index.js +2 -2
  32. package/dist/config/config-matchers.js +14 -10
  33. package/dist/config/next-config.d.ts +6 -3
  34. package/dist/config/next-config.js +47 -1
  35. package/dist/config/prerender.d.ts +34 -0
  36. package/dist/config/prerender.js +55 -0
  37. package/dist/config/server-external-packages.d.ts +4 -0
  38. package/dist/config/server-external-packages.js +91 -0
  39. package/dist/entries/app-rsc-entry.d.ts +2 -1
  40. package/dist/entries/app-rsc-entry.js +89 -15
  41. package/dist/entries/app-rsc-manifest.js +10 -0
  42. package/dist/entries/pages-client-entry.d.ts +3 -0
  43. package/dist/entries/pages-client-entry.js +16 -1
  44. package/dist/entries/pages-server-entry.js +6 -2
  45. package/dist/image/image-adapters-virtual.d.ts +59 -0
  46. package/dist/image/image-adapters-virtual.js +59 -0
  47. package/dist/index.d.ts +33 -0
  48. package/dist/index.js +211 -118
  49. package/dist/init-cloudflare.d.ts +43 -0
  50. package/dist/init-cloudflare.js +839 -0
  51. package/dist/init-platform.d.ts +40 -0
  52. package/dist/init-platform.js +195 -0
  53. package/dist/init.d.ts +16 -38
  54. package/dist/init.js +208 -97
  55. package/dist/node_modules/.pnpm/am-i-vibing@0.5.0/node_modules/am-i-vibing/dist/detector-1yx2Hoe0.js +294 -0
  56. package/dist/node_modules/.pnpm/process-ancestry@0.1.0/node_modules/process-ancestry/dist/index.js +94 -0
  57. package/dist/{cloudflare → packages/cloudflare}/src/cache/cdn-adapter.runtime.js +1 -1
  58. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.d.ts +2 -2
  59. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.js +1 -1
  60. package/dist/packages/cloudflare/src/deploy-config.js +150 -0
  61. package/dist/packages/cloudflare/src/deploy-help.js +55 -0
  62. package/dist/packages/cloudflare/src/deploy.js +276 -0
  63. package/dist/{cloudflare → packages/cloudflare/src}/tpr.d.ts +4 -41
  64. package/dist/{cloudflare → packages/cloudflare/src}/tpr.js +18 -15
  65. package/dist/plugins/ast-scope.d.ts +16 -0
  66. package/dist/plugins/ast-scope.js +62 -0
  67. package/dist/plugins/ast-utils.d.ts +27 -1
  68. package/dist/plugins/ast-utils.js +32 -1
  69. package/dist/plugins/css-module-imports.d.ts +14 -0
  70. package/dist/plugins/css-module-imports.js +59 -0
  71. package/dist/plugins/extensionless-dynamic-import.js +2 -2
  72. package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
  73. package/dist/plugins/ignore-dynamic-requests.js +530 -0
  74. package/dist/plugins/middleware-server-only.d.ts +8 -6
  75. package/dist/plugins/middleware-server-only.js +8 -7
  76. package/dist/plugins/optimize-imports.js +1 -1
  77. package/dist/plugins/typeof-window.d.ts +1 -1
  78. package/dist/plugins/typeof-window.js +28 -56
  79. package/dist/routing/app-route-graph.d.ts +29 -4
  80. package/dist/routing/app-route-graph.js +182 -40
  81. package/dist/routing/app-router.d.ts +5 -0
  82. package/dist/routing/app-router.js +5 -0
  83. package/dist/routing/file-matcher.d.ts +8 -0
  84. package/dist/routing/file-matcher.js +10 -1
  85. package/dist/routing/pages-router.js +2 -2
  86. package/dist/server/api-handler.js +9 -14
  87. package/dist/server/app-browser-action-result.d.ts +4 -1
  88. package/dist/server/app-browser-action-result.js +8 -1
  89. package/dist/server/app-browser-entry.js +194 -190
  90. package/dist/server/app-browser-history-controller.d.ts +2 -1
  91. package/dist/server/app-browser-history-controller.js +6 -2
  92. package/dist/server/app-browser-interception-context.d.ts +1 -0
  93. package/dist/server/app-browser-interception-context.js +4 -2
  94. package/dist/server/app-browser-navigation-controller.d.ts +2 -0
  95. package/dist/server/app-browser-navigation-controller.js +60 -31
  96. package/dist/server/app-browser-server-action-client.d.ts +32 -0
  97. package/dist/server/app-browser-server-action-client.js +127 -0
  98. package/dist/server/app-browser-state.d.ts +11 -1
  99. package/dist/server/app-browser-state.js +25 -8
  100. package/dist/server/app-browser-visible-commit.d.ts +6 -1
  101. package/dist/server/app-browser-visible-commit.js +43 -25
  102. package/dist/server/app-elements-wire.d.ts +9 -2
  103. package/dist/server/app-elements-wire.js +8 -1
  104. package/dist/server/app-elements.d.ts +2 -2
  105. package/dist/server/app-elements.js +4 -3
  106. package/dist/server/app-fallback-renderer.d.ts +5 -2
  107. package/dist/server/app-fallback-renderer.js +15 -7
  108. package/dist/server/app-hydration-cache-publication.d.ts +11 -0
  109. package/dist/server/app-hydration-cache-publication.js +43 -0
  110. package/dist/server/app-middleware.d.ts +4 -3
  111. package/dist/server/app-middleware.js +8 -3
  112. package/dist/server/app-optimistic-routing.js +11 -4
  113. package/dist/server/app-page-boundary-render.d.ts +3 -0
  114. package/dist/server/app-page-boundary-render.js +14 -3
  115. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  116. package/dist/server/app-page-cache-finalizer.js +10 -3
  117. package/dist/server/app-page-cache-render.d.ts +1 -0
  118. package/dist/server/app-page-cache-render.js +8 -4
  119. package/dist/server/app-page-cache.d.ts +1 -0
  120. package/dist/server/app-page-cache.js +5 -2
  121. package/dist/server/app-page-dispatch.d.ts +33 -4
  122. package/dist/server/app-page-dispatch.js +63 -90
  123. package/dist/server/app-page-element-builder.d.ts +9 -2
  124. package/dist/server/app-page-element-builder.js +114 -23
  125. package/dist/server/app-page-head.d.ts +16 -1
  126. package/dist/server/app-page-head.js +44 -22
  127. package/dist/server/app-page-params.d.ts +2 -1
  128. package/dist/server/app-page-params.js +8 -1
  129. package/dist/server/app-page-ppr-runtime.d.ts +7 -0
  130. package/dist/server/app-page-ppr-runtime.js +70 -0
  131. package/dist/server/app-page-probe.d.ts +1 -0
  132. package/dist/server/app-page-probe.js +6 -1
  133. package/dist/server/app-page-render-identity.d.ts +1 -0
  134. package/dist/server/app-page-render-identity.js +1 -1
  135. package/dist/server/app-page-render.d.ts +4 -1
  136. package/dist/server/app-page-render.js +26 -3
  137. package/dist/server/app-page-request.d.ts +22 -1
  138. package/dist/server/app-page-request.js +92 -15
  139. package/dist/server/app-page-route-wiring.d.ts +14 -2
  140. package/dist/server/app-page-route-wiring.js +61 -25
  141. package/dist/server/app-page-search-params-observation.d.ts +4 -2
  142. package/dist/server/app-page-search-params-observation.js +11 -7
  143. package/dist/server/app-page-segment-state.d.ts +2 -1
  144. package/dist/server/app-page-segment-state.js +3 -1
  145. package/dist/server/app-page-stream.d.ts +5 -0
  146. package/dist/server/app-page-stream.js +3 -1
  147. package/dist/server/app-ppr-fallback-shell-render.d.ts +4 -3
  148. package/dist/server/app-route-handler-dispatch.js +1 -0
  149. package/dist/server/app-route-handler-execution.js +7 -2
  150. package/dist/server/app-route-handler-response.js +1 -0
  151. package/dist/server/app-route-handler-runtime.js +1 -1
  152. package/dist/server/app-route-module-loader.d.ts +2 -0
  153. package/dist/server/app-route-module-loader.js +1 -0
  154. package/dist/server/app-router-entry.js +16 -8
  155. package/dist/server/app-rsc-errors.js +7 -1
  156. package/dist/server/app-rsc-handler.d.ts +3 -1
  157. package/dist/server/app-rsc-handler.js +67 -39
  158. package/dist/server/app-rsc-request-normalization.d.ts +3 -2
  159. package/dist/server/app-rsc-request-normalization.js +6 -3
  160. package/dist/server/app-rsc-route-matching.d.ts +7 -0
  161. package/dist/server/app-rsc-route-matching.js +36 -3
  162. package/dist/server/app-segment-config.d.ts +12 -0
  163. package/dist/server/app-segment-config.js +91 -5
  164. package/dist/server/app-server-action-execution.d.ts +5 -0
  165. package/dist/server/app-server-action-execution.js +94 -33
  166. package/dist/server/app-ssr-entry.d.ts +3 -0
  167. package/dist/server/app-ssr-entry.js +19 -8
  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-static-generation.d.ts +1 -0
  171. package/dist/server/app-static-generation.js +1 -0
  172. package/dist/server/app-visited-response-cache.d.ts +6 -0
  173. package/dist/server/app-visited-response-cache.js +3 -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.js +19 -18
  179. package/dist/server/dev-stack-sourcemap.d.ts +1 -1
  180. package/dist/server/dev-stack-sourcemap.js +1 -1
  181. package/dist/server/fetch-handler.d.ts +2 -0
  182. package/dist/server/fetch-handler.js +18 -0
  183. package/dist/server/headers.d.ts +5 -15
  184. package/dist/server/headers.js +4 -15
  185. package/dist/server/image-optimization.d.ts +51 -1
  186. package/dist/server/image-optimization.js +52 -2
  187. package/dist/server/implicit-tags.js +1 -1
  188. package/dist/server/isr-cache.d.ts +1 -1
  189. package/dist/server/isr-cache.js +2 -2
  190. package/dist/server/metadata-routes.d.ts +3 -1
  191. package/dist/server/metadata-routes.js +12 -2
  192. package/dist/server/middleware-runtime.js +6 -1
  193. package/dist/server/navigation-planner.d.ts +1 -0
  194. package/dist/server/navigation-planner.js +14 -3
  195. package/dist/server/pages-api-route.js +2 -2
  196. package/dist/server/pages-asset-tags.d.ts +4 -6
  197. package/dist/server/pages-asset-tags.js +12 -12
  198. package/dist/server/pages-client-assets.d.ts +12 -0
  199. package/dist/server/pages-client-assets.js +10 -0
  200. package/dist/server/pages-node-compat.d.ts +8 -1
  201. package/dist/server/pages-node-compat.js +32 -4
  202. package/dist/server/pages-page-data.d.ts +24 -1
  203. package/dist/server/pages-page-data.js +45 -24
  204. package/dist/server/pages-page-handler.d.ts +2 -1
  205. package/dist/server/pages-page-handler.js +19 -12
  206. package/dist/server/pages-page-response.d.ts +7 -0
  207. package/dist/server/pages-page-response.js +4 -1
  208. package/dist/server/pages-request-pipeline.d.ts +2 -0
  209. package/dist/server/pages-request-pipeline.js +25 -1
  210. package/dist/server/pages-router-entry.d.ts +30 -0
  211. package/dist/server/pages-router-entry.js +108 -0
  212. package/dist/server/prerender-manifest.d.ts +3 -1
  213. package/dist/server/prerender-route-params.js +1 -1
  214. package/dist/server/prod-server.d.ts +1 -1
  215. package/dist/server/prod-server.js +48 -26
  216. package/dist/server/request-pipeline.js +1 -0
  217. package/dist/server/seed-cache.js +4 -4
  218. package/dist/server/worker-utils.d.ts +2 -1
  219. package/dist/server/worker-utils.js +7 -1
  220. package/dist/shims/app-router-scroll-state.d.ts +1 -0
  221. package/dist/shims/app-router-scroll-state.js +1 -0
  222. package/dist/shims/app-router-scroll.js +2 -1
  223. package/dist/shims/cache.js +19 -15
  224. package/dist/shims/cdn-cache.js +1 -1
  225. package/dist/shims/dynamic-preload-chunks.js +2 -1
  226. package/dist/shims/error-boundary.d.ts +19 -1
  227. package/dist/shims/error-boundary.js +11 -1
  228. package/dist/shims/form.d.ts +3 -1
  229. package/dist/shims/form.js +37 -43
  230. package/dist/shims/headers.d.ts +9 -1
  231. package/dist/shims/headers.js +31 -6
  232. package/dist/shims/image.js +75 -14
  233. package/dist/shims/internal/app-route-detection.d.ts +2 -17
  234. package/dist/shims/internal/app-route-detection.js +4 -17
  235. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +23 -0
  236. package/dist/shims/internal/hybrid-client-route-owner-direct.js +51 -0
  237. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -15
  238. package/dist/shims/internal/hybrid-client-route-owner.js +9 -60
  239. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +9 -5
  240. package/dist/shims/internal/pages-data-fetch-dedup.js +73 -14
  241. package/dist/shims/internal/pages-data-target.d.ts +10 -9
  242. package/dist/shims/internal/pages-data-target.js +91 -12
  243. package/dist/shims/internal/pages-router-components.d.ts +7 -0
  244. package/dist/shims/internal/pages-router-components.js +13 -0
  245. package/dist/shims/layout-segment-context.d.ts +4 -1
  246. package/dist/shims/layout-segment-context.js +18 -4
  247. package/dist/shims/link.js +70 -37
  248. package/dist/shims/metadata.d.ts +3 -2
  249. package/dist/shims/metadata.js +8 -4
  250. package/dist/shims/navigation.d.ts +5 -1
  251. package/dist/shims/navigation.js +45 -10
  252. package/dist/shims/navigation.react-server.js +1 -1
  253. package/dist/shims/root-params.d.ts +15 -1
  254. package/dist/shims/root-params.js +21 -1
  255. package/dist/shims/router.d.ts +5 -7
  256. package/dist/shims/router.js +374 -94
  257. package/dist/shims/server.js +3 -2
  258. package/dist/shims/slot.js +3 -6
  259. package/dist/typegen.js +6 -5
  260. package/dist/utils/client-runtime-metadata.d.ts +2 -18
  261. package/dist/utils/client-runtime-metadata.js +31 -22
  262. package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
  263. package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
  264. package/dist/utils/domain-locale.d.ts +6 -3
  265. package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
  266. package/dist/{server → utils}/middleware-request-headers.js +2 -2
  267. package/dist/utils/path.d.ts +2 -1
  268. package/dist/utils/path.js +1 -1
  269. package/dist/utils/project.d.ts +52 -2
  270. package/dist/utils/project.js +230 -5
  271. package/dist/utils/protocol-headers.d.ts +17 -0
  272. package/dist/utils/protocol-headers.js +17 -0
  273. package/dist/utils/react-version.d.ts +4 -0
  274. package/dist/utils/react-version.js +44 -0
  275. package/package.json +53 -4
  276. package/dist/deploy.d.ts +0 -190
  277. package/dist/deploy.js +0 -1033
  278. package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
  279. /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
@@ -0,0 +1,4 @@
1
+ //#region src/build/inject-pregenerated-paths.d.ts
2
+ declare function injectPregeneratedConcretePaths(root: string): void;
3
+ //#endregion
4
+ export { injectPregeneratedConcretePaths };
@@ -0,0 +1,18 @@
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) code = `${VINEXT_PREGEN_START}\nglobalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = ${JSON.stringify(table)};\n${VINEXT_PREGEN_END}\n` + code;
15
+ fs.writeFileSync(workerEntry, code);
16
+ }
17
+ //#endregion
18
+ 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 };
@@ -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;
@@ -1,16 +1,17 @@
1
1
  import { createValidFileMatcher, findFileWithExtensions } from "../routing/file-matcher.js";
2
- import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "../server/headers.js";
3
- import { normalizeStaticPathsEntry } from "../routing/route-pattern.js";
2
+ import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "../utils/protocol-headers.js";
3
+ import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath } from "./report.js";
4
4
  import { NoOpCacheHandler, getCacheHandler, setCacheHandler } from "../shims/cache-handler.js";
5
5
  import { headersContextFromRequest, runWithHeadersContext } from "../shims/headers.js";
6
6
  import { _consumeRequestScopedCacheLife } from "../shims/cache-request-state.js";
7
- import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath } from "./report.js";
7
+ import { normalizeStaticPathsEntry } from "../routing/route-pattern.js";
8
8
  import { BLOCKED_PAGES } from "../shims/constants.js";
9
+ import { buildPregeneratedConcretePathTable } from "../server/prerender-manifest.js";
9
10
  import { concatUint8Arrays, decodeRscEmbeddedChunk } from "../server/app-rsc-embedded-chunks.js";
10
11
  import { navigationRuntimeRscBootstrapExpression } from "../server/app-ssr-stream.js";
11
- import { createAppPprFallbackShells, markAppPprDynamicFallbackShellHtml } from "../server/app-ppr-fallback-shell.js";
12
12
  import { encodePrerenderRouteParams, serializePrerenderRouteParamsHeader } from "../server/prerender-route-params.js";
13
13
  import { readPrerenderSecret } from "./server-manifest.js";
14
+ import { createAppPprFallbackShells, markAppPprDynamicFallbackShellHtml } from "../server/app-ppr-fallback-shell.js";
14
15
  import { getOutputPath, getRscOutputPath } from "../utils/prerender-output-paths.js";
15
16
  import { startProdServer } from "../server/prod-server.js";
16
17
  import fs from "node:fs";
@@ -80,7 +81,7 @@ function extractRscPayloadFromPrerenderedHtml(html) {
80
81
  let match;
81
82
  while ((match = scriptPattern.exec(html)) !== null) {
82
83
  const script = (match[1] ?? "").trim().replace(/;$/, "");
83
- if (script === RSC_RUNTIME_DONE_SCRIPT || script === RSC_LEGACY_DONE_SCRIPT) {
84
+ if (script === RSC_RUNTIME_DONE_SCRIPT || script === RSC_LEGACY_DONE_SCRIPT || script.endsWith(`;${RSC_RUNTIME_DONE_SCRIPT}`)) {
84
85
  sawDone = true;
85
86
  continue;
86
87
  }
@@ -721,10 +722,12 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
721
722
  const htmlRender = await runWithHeadersContext(headersContextFromRequest(htmlRequest), async () => {
722
723
  const response = await rscHandler(htmlRequest);
723
724
  const cacheControl = response.headers.get("cache-control") ?? "";
725
+ const linkHeader = response.headers.get("link");
724
726
  if (!response.ok || isSpeculative && cacheControl.includes("no-store")) {
725
727
  await response.body?.cancel();
726
728
  return {
727
729
  cacheControl,
730
+ linkHeader,
728
731
  html: null,
729
732
  ok: response.ok,
730
733
  requestCacheLife: null,
@@ -733,6 +736,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
733
736
  }
734
737
  return {
735
738
  cacheControl,
739
+ linkHeader,
736
740
  html: await response.text(),
737
741
  ok: true,
738
742
  requestCacheLife: _consumeRequestScopedCacheLife(),
@@ -800,6 +804,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
800
804
  revalidate: renderedRevalidate,
801
805
  ...typeof renderedRevalidate === "number" ? { expire: renderedCacheControl.expire } : {},
802
806
  router: "app",
807
+ ...htmlRender.linkHeader ? { headers: { link: htmlRender.linkHeader } } : {},
803
808
  ...urlPath !== routePattern ? { path: urlPath } : {},
804
809
  ...isFallback ? { fallback: true } : {}
805
810
  };
@@ -907,6 +912,7 @@ function writePrerenderIndex(routes, outDir, options) {
907
912
  revalidate: r.revalidate,
908
913
  ...typeof r.revalidate === "number" ? { expire: r.expire } : {},
909
914
  router: r.router,
915
+ ...r.headers ? { headers: r.headers } : {},
910
916
  ...r.path ? { path: r.path } : {},
911
917
  ...r.fallback ? { fallback: true } : {}
912
918
  };
@@ -924,7 +930,8 @@ function writePrerenderIndex(routes, outDir, options) {
924
930
  const index = {
925
931
  ...buildId ? { buildId } : {},
926
932
  ...typeof trailingSlash === "boolean" ? { trailingSlash } : {},
927
- routes: indexRoutes
933
+ routes: indexRoutes,
934
+ pregeneratedConcretePaths: buildPregeneratedConcretePathTable({ routes: indexRoutes })
928
935
  };
929
936
  fs.writeFileSync(path.join(outDir, "vinext-prerender.json"), JSON.stringify(index, null, 2), "utf-8");
930
937
  }
@@ -17,6 +17,13 @@ type RouteRow = {
17
17
  prerendered?: boolean;
18
18
  };
19
19
  type AppRouteRenderEntry = Pick<AppRoute, "pagePath" | "routePath" | "parallelSlots">;
20
+ type StaticMiddlewareMatcherObject = {
21
+ source: string;
22
+ locale?: false;
23
+ has?: Array<Record<string, string>>;
24
+ missing?: Array<Record<string, string>>;
25
+ };
26
+ type StaticMiddlewareMatcher = string | Array<string | StaticMiddlewareMatcherObject>;
20
27
  declare function getAppRouteRenderEntryPath(route: AppRouteRenderEntry): string | null;
21
28
  /**
22
29
  * Returns true if the source code contains an export declaration with the given name.
@@ -28,12 +35,15 @@ declare function getAppRouteRenderEntryPath(route: AppRouteRenderEntry): string
28
35
  * export { foo }
29
36
  */
30
37
  declare function hasNamedExport(code: string, name: string): boolean;
38
+ /** Returns true when Next.js' analyzer recognizes the requested export name. */
39
+ declare function hasExportedName(code: string, name: string): boolean;
31
40
  /**
32
41
  * Extracts the string value of `export const <name> = "value"`.
33
42
  * Handles TypeScript annotations/assertions and no-substitution template literals.
34
43
  * Returns null if the export is absent or not a string literal.
35
44
  */
36
45
  declare function extractExportConstString(code: string, name: string): string | null;
46
+ declare function extractMiddlewareMatcherConfig(filePath: string): StaticMiddlewareMatcher | undefined;
37
47
  /**
38
48
  * Extracts the numeric value of `export const <name> = <number|false>`.
39
49
  * Supports integers, decimals, negative values, `Infinity`, and `false`.
@@ -121,7 +131,8 @@ declare function formatBuildReport(rows: RouteRow[], routerLabel?: string): stri
121
131
  * Scans the project at `root`, classifies all routes, and prints the
122
132
  * Next.js-style build report to stdout.
123
133
  *
124
- * Called at the end of `vinext build` in cli.ts.
134
+ * `root` must be forward-slash it is passed to `findDir`. The caller (the
135
+ * `vinext build` entry in cli.ts) normalizes it.
125
136
  */
126
137
  declare function printBuildReport(options: {
127
138
  root: string;
@@ -129,4 +140,4 @@ declare function printBuildReport(options: {
129
140
  prerenderResult?: PrerenderResult;
130
141
  }): Promise<void>;
131
142
  //#endregion
132
- export { RouteRow, RouteType, buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, formatBuildReport, getAppRouteRenderEntryPath, hasNamedExport, printBuildReport };
143
+ export { RouteRow, RouteType, StaticMiddlewareMatcher, buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, extractMiddlewareMatcherConfig, formatBuildReport, getAppRouteRenderEntryPath, hasExportedName, hasNamedExport, printBuildReport };
@@ -22,6 +22,7 @@ import { parseSync } from "vite";
22
22
  * `export const revalidate` are classified as "unknown" rather than "static"
23
23
  * to avoid false confidence.
24
24
  */
25
+ const UNSUPPORTED_STATIC_VALUE = Symbol("unsupported static value");
25
26
  function getAppRouteRenderEntryPath(route) {
26
27
  if (route.pagePath) return route.pagePath;
27
28
  if (route.routePath) return null;
@@ -72,6 +73,21 @@ function hasNamedExport(code, name) {
72
73
  if (!program) return false;
73
74
  return hasNamedExportInProgram(program, name);
74
75
  }
76
+ /** Returns true when Next.js' analyzer recognizes the requested export name. */
77
+ function hasExportedName(code, name) {
78
+ const program = parseRouteModule(code);
79
+ if (!program) return false;
80
+ for (const node of program.body) {
81
+ if (node.type !== "ExportNamedDeclaration") continue;
82
+ if (node.exportKind === "type") continue;
83
+ if (declarationHasBindingName(node.declaration, name)) return true;
84
+ for (const specifier of node.specifiers) {
85
+ if (specifier.exportKind === "type") continue;
86
+ if (moduleExportNameValue(specifier.local) === name) return true;
87
+ }
88
+ }
89
+ return false;
90
+ }
75
91
  function hasNamedExportInProgram(program, name) {
76
92
  for (const node of program.body) {
77
93
  if (node.type !== "ExportNamedDeclaration") continue;
@@ -117,6 +133,86 @@ function extractStringFromConstInitializer(initializer) {
117
133
  if (expression.type === "TemplateLiteral" && expression.expressions.length === 0) return expression.quasis[0]?.value.cooked ?? expression.quasis[0]?.value.raw ?? null;
118
134
  return null;
119
135
  }
136
+ function extractMiddlewareMatcherConfig(filePath) {
137
+ let code;
138
+ try {
139
+ code = fs.readFileSync(filePath, "utf8");
140
+ } catch {
141
+ return;
142
+ }
143
+ const initializer = findExportedConstInitializer(code, "config");
144
+ if (!initializer) return void 0;
145
+ const config = unwrapStaticExpression(initializer);
146
+ if (config.type !== "ObjectExpression") return void 0;
147
+ const matcherExpression = objectPropertyValue(config, "matcher");
148
+ if (!matcherExpression) return void 0;
149
+ const value = extractStaticJsonValue(matcherExpression);
150
+ return isStaticMiddlewareMatcher(value) ? value : void 0;
151
+ }
152
+ function objectPropertyValue(object, key) {
153
+ for (const property of object.properties) {
154
+ if (property.type !== "Property" || property.computed) continue;
155
+ if (propertyKeyName(property.key) !== key) continue;
156
+ return property.value;
157
+ }
158
+ return null;
159
+ }
160
+ function propertyKeyName(key) {
161
+ if (key.type === "Identifier") return key.name;
162
+ if (key.type === "Literal" && typeof key.value === "string") return key.value;
163
+ return null;
164
+ }
165
+ function extractStaticJsonValue(expression) {
166
+ const value = unwrapStaticExpression(expression);
167
+ if (value.type === "Literal") {
168
+ if (typeof value.value === "string" || typeof value.value === "number" || typeof value.value === "boolean" || value.value === null) return value.value;
169
+ return UNSUPPORTED_STATIC_VALUE;
170
+ }
171
+ if (value.type === "TemplateLiteral" && value.expressions.length === 0) return value.quasis[0]?.value.cooked ?? value.quasis[0]?.value.raw ?? "";
172
+ if (value.type === "ArrayExpression") {
173
+ const items = [];
174
+ for (const element of value.elements) {
175
+ if (!element || element.type === "SpreadElement") return UNSUPPORTED_STATIC_VALUE;
176
+ const item = extractStaticJsonValue(element);
177
+ if (item === UNSUPPORTED_STATIC_VALUE) return UNSUPPORTED_STATIC_VALUE;
178
+ items.push(item);
179
+ }
180
+ return items;
181
+ }
182
+ if (value.type === "ObjectExpression") {
183
+ const object = {};
184
+ for (const property of value.properties) {
185
+ if (property.type !== "Property" || property.computed) return UNSUPPORTED_STATIC_VALUE;
186
+ const key = propertyKeyName(property.key);
187
+ if (!key) return UNSUPPORTED_STATIC_VALUE;
188
+ const propertyValue = extractStaticJsonValue(property.value);
189
+ if (propertyValue === UNSUPPORTED_STATIC_VALUE) return UNSUPPORTED_STATIC_VALUE;
190
+ object[key] = propertyValue;
191
+ }
192
+ return object;
193
+ }
194
+ return UNSUPPORTED_STATIC_VALUE;
195
+ }
196
+ function isStaticMiddlewareMatcher(value) {
197
+ if (typeof value === "string") return true;
198
+ if (!Array.isArray(value)) return false;
199
+ return value.every((item) => typeof item === "string" || isStaticMiddlewareMatcherObject(item));
200
+ }
201
+ function isStaticMiddlewareMatcherObject(value) {
202
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
203
+ const record = value;
204
+ if (typeof record.source !== "string") return false;
205
+ if (record.locale !== void 0 && record.locale !== false) return false;
206
+ return isStaticMatcherConditions(record.has) && isStaticMatcherConditions(record.missing);
207
+ }
208
+ function isStaticMatcherConditions(value) {
209
+ if (value === void 0) return true;
210
+ if (!Array.isArray(value)) return false;
211
+ return value.every((item) => {
212
+ if (!item || typeof item !== "object" || Array.isArray(item)) return false;
213
+ return Object.values(item).every((entry) => typeof entry === "string");
214
+ });
215
+ }
120
216
  /**
121
217
  * Extracts the numeric value of `export const <name> = <number|false>`.
122
218
  * Supports integers, decimals, negative values, `Infinity`, and `false`.
@@ -474,7 +570,8 @@ function formatBuildReport(rows, routerLabel = "app") {
474
570
  * Scans the project at `root`, classifies all routes, and prints the
475
571
  * Next.js-style build report to stdout.
476
572
  *
477
- * Called at the end of `vinext build` in cli.ts.
573
+ * `root` must be forward-slash it is passed to `findDir`. The caller (the
574
+ * `vinext build` entry in cli.ts) normalizes it.
478
575
  */
479
576
  async function printBuildReport(options) {
480
577
  const { root } = options;
@@ -501,4 +598,4 @@ async function printBuildReport(options) {
501
598
  }
502
599
  }
503
600
  //#endregion
504
- export { buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, formatBuildReport, getAppRouteRenderEntryPath, hasNamedExport, printBuildReport };
601
+ export { buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, extractMiddlewareMatcherConfig, formatBuildReport, getAppRouteRenderEntryPath, hasExportedName, hasNamedExport, printBuildReport };
@@ -56,6 +56,13 @@ type RunPrerenderOptions = {
56
56
  * `output: 'export'`. Exported for direct unit testing. Refs cloudflare/vinext#1982
57
57
  */
58
58
  declare function assertNoFatalPrerenderRoutes(routes: readonly PrerenderRouteResult[]): void;
59
+ /**
60
+ * Statically generate routes and return the prerender result.
61
+ *
62
+ * `options.root` must be forward-slash — it is passed to `findDir` and flows
63
+ * into the route model. The caller (the `vinext build` entry in cli.ts)
64
+ * normalizes it.
65
+ */
59
66
  declare function runPrerender(options: RunPrerenderOptions): Promise<PrerenderResult | null>;
60
67
  //#endregion
61
68
  export { assertNoFatalPrerenderRoutes, runPrerender };
@@ -6,12 +6,13 @@ import { loadNextConfig, resolveNextConfig } from "../config/next-config.js";
6
6
  import { readPrerenderSecret } from "./server-manifest.js";
7
7
  import { startProdServer } from "../server/prod-server.js";
8
8
  import { prerenderApp, prerenderPages, writePrerenderIndex } from "./prerender.js";
9
+ import { injectPregeneratedConcretePaths } from "./inject-pregenerated-paths.js";
9
10
  import fs from "node:fs";
10
11
  import path from "node:path";
11
12
  //#region src/build/run-prerender.ts
12
13
  /**
13
14
  * Shared prerender runner used by both `vinext build` (cli.ts) and
14
- * `vinext deploy --prerender-all` (deploy.ts).
15
+ * `vinext-cloudflare deploy --prerender-all` (deploy.ts).
15
16
  *
16
17
  * `runPrerender` handles route scanning, dynamic imports, progress reporting,
17
18
  * and result summarisation.
@@ -95,6 +96,13 @@ function assertNoFatalPrerenderRoutes(routes) {
95
96
  const fatalList = fatalRoutes.map((r) => ` ${r.route}: ${r.error}`).join("\n");
96
97
  throw new Error(`Prerender failed: ${fatalRoutes.length} route${fatalRoutes.length !== 1 ? "s" : ""} errored during static generation.\n${fatalList}`);
97
98
  }
99
+ /**
100
+ * Statically generate routes and return the prerender result.
101
+ *
102
+ * `options.root` must be forward-slash — it is passed to `findDir` and flows
103
+ * into the route model. The caller (the `vinext build` entry in cli.ts)
104
+ * normalizes it.
105
+ */
98
106
  async function runPrerender(options) {
99
107
  const { root } = options;
100
108
  const appDir = findDir(root, "app", "src/app");
@@ -215,6 +223,7 @@ async function runPrerender(options) {
215
223
  const errorRoutes = allRoutes.filter((r) => r.status === "error").map((r) => ` ${r.route}: ${r.error}`).join("\n");
216
224
  throw new Error(`Static export failed: ${errors} route${errors !== 1 ? "s" : ""} cannot be statically exported.\n${errorRoutes}\n\nRemove server-side data fetching (getServerSideProps, force-dynamic, revalidate) from these routes, or remove \`output: "export"\` from next.config.js.`);
217
225
  }
226
+ injectPregeneratedConcretePaths(root);
218
227
  return {
219
228
  routes: allRoutes,
220
229
  ...allOutputFiles.length > 0 ? { outputFiles: allOutputFiles } : {}
@@ -167,6 +167,8 @@ function emitStandaloneOutput(options) {
167
167
  dereference: true,
168
168
  filter: (src) => !path.relative(serverDir, src).split(path.sep).includes("node_modules")
169
169
  });
170
+ const clientAssetsSidecar = path.join(outDir, "vinext-client-assets.js");
171
+ if (fs.existsSync(clientAssetsSidecar)) fs.copyFileSync(clientAssetsSidecar, path.join(standaloneDistDir, "vinext-client-assets.js"));
170
172
  const publicDir = path.join(root, "public");
171
173
  if (fs.existsSync(publicDir)) fs.cpSync(publicDir, path.join(standaloneDir, "public"), {
172
174
  recursive: true,
@@ -35,9 +35,9 @@ function generateCacheAdaptersModule(cache) {
35
35
  lines.push(`import __vinextCdnAdapterFactory from ${JSON.stringify(cdn.adapter)};`);
36
36
  lines.push(`import { setCdnCacheAdapter } from "vinext/shims/cdn-cache";`);
37
37
  }
38
- lines.push("", "// A factory that throws (e.g. a missing binding on an incompatible runtime)", "// is logged and skipped so the default handler stays in place.", "let __vinextCacheAdaptersRegistered = false;", "", "export function registerConfiguredCacheAdapters(env) {", " if (__vinextCacheAdaptersRegistered) return;", " __vinextCacheAdaptersRegistered = true;");
39
- if (data?.adapter) lines.push(" try {", ` setDataCacheHandler(__vinextDataAdapterFactory({ env, options: ${inlineOptions(data.adapter, data.options)} }));`, " } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured data cache adapter; using the default handler.\", error);", " }");
40
- if (cdn?.adapter) lines.push(" try {", ` setCdnCacheAdapter(__vinextCdnAdapterFactory({ env, options: ${inlineOptions(cdn.adapter, cdn.options)} }));`, " } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured CDN cache adapter; using the default adapter.\", error);", " }");
38
+ lines.push("", "// A factory that throws (e.g. a missing binding on an incompatible runtime)", "// is logged and skipped so the default handler stays in place.", "function __vinextFormatAdapterError(error) {", " if (error instanceof Error && error.message) return error.message;", " try {", " return String(error);", " } catch {", " return '<unknown error>';", " }", "}", "", "let __vinextCacheAdaptersRegistered = false;", "", "export function registerConfiguredCacheAdapters(env) {", " if (__vinextCacheAdaptersRegistered) return;", " __vinextCacheAdaptersRegistered = true;");
39
+ if (data?.adapter) lines.push(" try {", ` setDataCacheHandler(__vinextDataAdapterFactory({ env, options: ${inlineOptions(data.adapter, data.options)} }));`, " } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured data cache adapter; using the default handler.\\n\" + __vinextFormatAdapterError(error));", " }");
40
+ if (cdn?.adapter) lines.push(" try {", ` setCdnCacheAdapter(__vinextCdnAdapterFactory({ env, options: ${inlineOptions(cdn.adapter, cdn.options)} }));`, " } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured CDN cache adapter; using the default adapter.\\n\" + __vinextFormatAdapterError(error));", " }");
41
41
  lines.push("}", "");
42
42
  return lines.join("\n");
43
43
  }