vinext 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/README.md +93 -10
  2. package/dist/build/app-page-static-eligibility.d.ts +15 -0
  3. package/dist/build/app-page-static-eligibility.js +82 -0
  4. package/dist/build/client-build-config.d.ts +10 -0
  5. package/dist/build/client-build-config.js +18 -2
  6. package/dist/build/pages-client-assets-module.d.ts +8 -0
  7. package/dist/build/pages-client-assets-module.js +21 -1
  8. package/dist/build/prerender-paths.js +8 -81
  9. package/dist/build/prerender.js +33 -8
  10. package/dist/build/run-prerender.js +1 -1
  11. package/dist/build/server-manifest.d.ts +7 -0
  12. package/dist/build/server-manifest.js +11 -1
  13. package/dist/build/ssr-manifest.d.ts +2 -0
  14. package/dist/build/ssr-manifest.js +6 -2
  15. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  16. package/dist/cache/cache-adapters-virtual.js +3 -3
  17. package/dist/check.js +7 -6
  18. package/dist/cli.js +2 -1
  19. package/dist/client/client-redirect-matcher.d.ts +14 -0
  20. package/dist/client/client-redirect-matcher.js +37 -0
  21. package/dist/client/client-redirects.d.ts +27 -0
  22. package/dist/client/client-redirects.js +28 -0
  23. package/dist/client/client-rewrites.d.ts +2 -1
  24. package/dist/client/client-rewrites.js +1 -1
  25. package/dist/client/navigation-runtime.d.ts +11 -0
  26. package/dist/client/navigation-runtime.js +1 -1
  27. package/dist/config/config-matchers.d.ts +26 -11
  28. package/dist/config/config-matchers.js +81 -82
  29. package/dist/config/destination-params.d.ts +47 -0
  30. package/dist/config/destination-params.js +116 -0
  31. package/dist/config/next-config.d.ts +14 -0
  32. package/dist/config/next-config.js +21 -3
  33. package/dist/entries/app-rsc-entry.d.ts +6 -0
  34. package/dist/entries/app-rsc-entry.js +84 -49
  35. package/dist/entries/pages-client-entry.js +3 -1
  36. package/dist/entries/pages-server-entry.js +13 -37
  37. package/dist/index.d.ts +7 -2
  38. package/dist/index.js +193 -73
  39. package/dist/init-cloudflare.d.ts +9 -0
  40. package/dist/init-cloudflare.js +107 -31
  41. package/dist/init-platform.d.ts +4 -0
  42. package/dist/init-platform.js +27 -2
  43. package/dist/init.d.ts +1 -0
  44. package/dist/init.js +67 -3
  45. package/dist/plugins/ast-scope.d.ts +7 -0
  46. package/dist/plugins/ast-scope.js +59 -3
  47. package/dist/plugins/cache-flight-codec.d.ts +19 -0
  48. package/dist/plugins/cache-flight-codec.js +68 -0
  49. package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
  50. package/dist/plugins/commonjs-esm-facade.js +80 -0
  51. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  52. package/dist/plugins/dynamic-preload-metadata.js +9 -8
  53. package/dist/plugins/fonts.d.ts +8 -11
  54. package/dist/plugins/fonts.js +71 -34
  55. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  56. package/dist/plugins/ignore-dynamic-requests.js +3 -4
  57. package/dist/plugins/middleware-server-only.d.ts +5 -4
  58. package/dist/plugins/middleware-server-only.js +8 -5
  59. package/dist/plugins/pages-node-externals.js +1 -1
  60. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  61. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  62. package/dist/plugins/strip-server-exports.js +262 -98
  63. package/dist/plugins/transform-result.d.ts +9 -5
  64. package/dist/plugins/transitive-externals.js +1 -1
  65. package/dist/plugins/typeof-window.d.ts +2 -2
  66. package/dist/plugins/use-cache-callable.js +8 -2
  67. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  68. package/dist/plugins/worker-server-only-imports.js +31 -0
  69. package/dist/plugins/worker-use-server.d.ts +25 -0
  70. package/dist/plugins/worker-use-server.js +129 -0
  71. package/dist/routing/file-matcher.d.ts +1 -0
  72. package/dist/routing/file-matcher.js +1 -1
  73. package/dist/server/api-handler.d.ts +1 -0
  74. package/dist/server/api-handler.js +19 -8
  75. package/dist/server/app-browser-action-result.d.ts +1 -1
  76. package/dist/server/app-browser-action-result.js +9 -2
  77. package/dist/server/app-browser-entry.js +11 -7
  78. package/dist/server/app-browser-history-controller.d.ts +2 -0
  79. package/dist/server/app-browser-history-controller.js +8 -0
  80. package/dist/server/app-browser-interception-context.js +26 -2
  81. package/dist/server/app-browser-navigation-controller.d.ts +7 -1
  82. package/dist/server/app-browser-navigation-controller.js +94 -11
  83. package/dist/server/app-browser-server-action-client.js +1 -1
  84. package/dist/server/app-fallback-renderer.d.ts +2 -0
  85. package/dist/server/app-fallback-renderer.js +1 -0
  86. package/dist/server/app-history-state.d.ts +3 -0
  87. package/dist/server/app-history-state.js +7 -0
  88. package/dist/server/app-middleware.d.ts +11 -0
  89. package/dist/server/app-middleware.js +19 -3
  90. package/dist/server/app-optimistic-routing.js +1 -1
  91. package/dist/server/app-page-boundary-render.d.ts +5 -0
  92. package/dist/server/app-page-boundary-render.js +6 -4
  93. package/dist/server/app-page-cache-finalizer.d.ts +11 -0
  94. package/dist/server/app-page-cache-finalizer.js +41 -7
  95. package/dist/server/app-page-cache-render.d.ts +18 -0
  96. package/dist/server/app-page-cache-render.js +63 -43
  97. package/dist/server/app-page-cache.d.ts +6 -1
  98. package/dist/server/app-page-cache.js +28 -8
  99. package/dist/server/app-page-dispatch.d.ts +12 -2
  100. package/dist/server/app-page-dispatch.js +51 -33
  101. package/dist/server/app-page-element-builder.d.ts +7 -0
  102. package/dist/server/app-page-element-builder.js +27 -42
  103. package/dist/server/app-page-execution.d.ts +15 -12
  104. package/dist/server/app-page-execution.js +30 -40
  105. package/dist/server/app-page-head.d.ts +5 -0
  106. package/dist/server/app-page-head.js +9 -3
  107. package/dist/server/app-page-ppr-runtime.js +1 -1
  108. package/dist/server/app-page-probe.d.ts +0 -109
  109. package/dist/server/app-page-probe.js +3 -136
  110. package/dist/server/app-page-render-identity.d.ts +0 -1
  111. package/dist/server/app-page-render-identity.js +1 -1
  112. package/dist/server/app-page-render-observation.js +3 -3
  113. package/dist/server/app-page-render.d.ts +13 -5
  114. package/dist/server/app-page-render.js +219 -119
  115. package/dist/server/app-page-request.d.ts +0 -1
  116. package/dist/server/app-page-request.js +54 -17
  117. package/dist/server/app-page-response.d.ts +4 -3
  118. package/dist/server/app-page-response.js +4 -3
  119. package/dist/server/app-page-route-wiring.d.ts +17 -5
  120. package/dist/server/app-page-route-wiring.js +57 -35
  121. package/dist/server/app-page-rsc-render-status.d.ts +48 -0
  122. package/dist/server/app-page-rsc-render-status.js +54 -0
  123. package/dist/server/app-page-stream.d.ts +15 -8
  124. package/dist/server/app-page-stream.js +6 -0
  125. package/dist/server/app-pages-bridge.d.ts +1 -1
  126. package/dist/server/app-pages-bridge.js +2 -3
  127. package/dist/server/app-prerender-endpoints.js +9 -6
  128. package/dist/server/app-request-stage-dispatch.js +7 -3
  129. package/dist/server/app-request-stage-independent-entry.js +29 -9
  130. package/dist/server/app-response-header-provenance.d.ts +2 -0
  131. package/dist/server/app-response-header-provenance.js +10 -4
  132. package/dist/server/app-response-stage-entry.js +10 -2
  133. package/dist/server/app-route-handler-cache.d.ts +1 -0
  134. package/dist/server/app-route-handler-cache.js +1 -0
  135. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  136. package/dist/server/app-route-handler-dispatch.js +6 -3
  137. package/dist/server/app-route-handler-execution.d.ts +1 -0
  138. package/dist/server/app-route-handler-execution.js +1 -0
  139. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  140. package/dist/server/app-route-handler-runtime.js +45 -24
  141. package/dist/server/app-router-entry.js +27 -8
  142. package/dist/server/app-rsc-cache-busting.js +1 -1
  143. package/dist/server/app-rsc-errors.js +20 -16
  144. package/dist/server/app-rsc-handler.d.ts +11 -0
  145. package/dist/server/app-rsc-handler.js +291 -125
  146. package/dist/server/app-rsc-request-normalization.d.ts +3 -1
  147. package/dist/server/app-rsc-request-normalization.js +4 -2
  148. package/dist/server/app-rsc-response-stage.d.ts +1 -1
  149. package/dist/server/app-rsc-response-stage.js +10 -6
  150. package/dist/server/app-rsc-route-matching.d.ts +9 -1
  151. package/dist/server/app-rsc-route-matching.js +36 -19
  152. package/dist/server/app-segment-config.d.ts +2 -1
  153. package/dist/server/app-server-action-execution.d.ts +2 -1
  154. package/dist/server/app-server-action-execution.js +62 -23
  155. package/dist/server/app-ssr-entry.d.ts +3 -2
  156. package/dist/server/app-ssr-entry.js +8 -5
  157. package/dist/server/app-ssr-stream.d.ts +6 -1
  158. package/dist/server/app-ssr-stream.js +34 -15
  159. package/dist/server/app-worker-stages.d.ts +7 -2
  160. package/dist/server/app-worker-stages.js +4 -4
  161. package/dist/server/cacheability-request.js +11 -7
  162. package/dist/server/client-build-files.d.ts +16 -0
  163. package/dist/server/client-build-files.js +36 -0
  164. package/dist/server/client-reuse-manifest.js +1 -1
  165. package/dist/server/config-headers.js +2 -0
  166. package/dist/server/dev-server.js +50 -29
  167. package/dist/server/dev-stack-sourcemap.js +18 -23
  168. package/dist/server/document-initial-head.d.ts +0 -11
  169. package/dist/server/document-initial-head.js +1 -1
  170. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  171. package/dist/server/forwarded-middleware-context.js +22 -0
  172. package/dist/server/headers.d.ts +18 -1
  173. package/dist/server/headers.js +19 -2
  174. package/dist/server/http-conditional.d.ts +11 -0
  175. package/dist/server/http-conditional.js +10 -1
  176. package/dist/server/http-error-responses.d.ts +1 -1
  177. package/dist/server/image-optimization.d.ts +54 -0
  178. package/dist/server/image-optimization.js +118 -9
  179. package/dist/server/isr-cache.d.ts +10 -0
  180. package/dist/server/isr-cache.js +43 -8
  181. package/dist/server/metadata-route-response.js +9 -8
  182. package/dist/server/middleware-matcher-pattern.js +8 -4
  183. package/dist/server/middleware-matcher.d.ts +2 -0
  184. package/dist/server/middleware-matcher.js +23 -2
  185. package/dist/server/middleware-response-headers.js +2 -0
  186. package/dist/server/middleware-runtime.d.ts +13 -0
  187. package/dist/server/middleware-runtime.js +33 -20
  188. package/dist/server/middleware.d.ts +1 -1
  189. package/dist/server/middleware.js +3 -1
  190. package/dist/server/multi-stage.d.ts +19 -0
  191. package/dist/server/navigation-planner.js +1 -1
  192. package/dist/server/node-response-signal.d.ts +16 -0
  193. package/dist/server/node-response-signal.js +26 -0
  194. package/dist/server/normalize-path.d.ts +0 -13
  195. package/dist/server/normalize-path.js +1 -34
  196. package/dist/server/open-redirect.d.ts +47 -0
  197. package/dist/server/open-redirect.js +74 -1
  198. package/dist/server/pages-api-route.d.ts +3 -2
  199. package/dist/server/pages-api-route.js +4 -3
  200. package/dist/server/pages-cache-tag.d.ts +12 -0
  201. package/dist/server/pages-cache-tag.js +19 -0
  202. package/dist/server/pages-client-assets.d.ts +2 -0
  203. package/dist/server/pages-data-route.d.ts +10 -0
  204. package/dist/server/pages-data-route.js +19 -2
  205. package/dist/server/pages-document-asset-props.d.ts +5 -1
  206. package/dist/server/pages-document-asset-props.js +13 -5
  207. package/dist/server/pages-document-splice.d.ts +19 -0
  208. package/dist/server/pages-document-splice.js +55 -0
  209. package/dist/server/pages-i18n.d.ts +12 -1
  210. package/dist/server/pages-i18n.js +18 -4
  211. package/dist/server/pages-node-compat.d.ts +16 -5
  212. package/dist/server/pages-node-compat.js +20 -12
  213. package/dist/server/pages-page-data.d.ts +32 -24
  214. package/dist/server/pages-page-data.js +50 -120
  215. package/dist/server/pages-page-handler.d.ts +0 -6
  216. package/dist/server/pages-page-handler.js +209 -130
  217. package/dist/server/pages-page-response.d.ts +12 -6
  218. package/dist/server/pages-page-response.js +41 -34
  219. package/dist/server/pages-request-pipeline.d.ts +10 -2
  220. package/dist/server/pages-request-pipeline.js +37 -18
  221. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  222. package/dist/server/pages-request-stage-entry.js +79 -16
  223. package/dist/server/pages-response-stage-entry.js +4 -0
  224. package/dist/server/pages-revalidate.js +1 -1
  225. package/dist/server/pages-router-entry.js +4 -3
  226. package/dist/server/prerender-manifest.d.ts +3 -3
  227. package/dist/server/prerender-manifest.js +4 -3
  228. package/dist/server/prerender-work-unit-setup.js +4 -0
  229. package/dist/server/prod-server.d.ts +20 -7
  230. package/dist/server/prod-server.js +343 -129
  231. package/dist/server/proper-error.d.ts +4 -0
  232. package/dist/server/proper-error.js +31 -0
  233. package/dist/server/request-pipeline.d.ts +24 -9
  234. package/dist/server/request-pipeline.js +24 -9
  235. package/dist/server/response-compression.d.ts +43 -0
  236. package/dist/server/response-compression.js +111 -0
  237. package/dist/server/revalidation-request.d.ts +1 -0
  238. package/dist/server/revalidation-request.js +1 -1
  239. package/dist/server/rsc-stream-hints.js +31 -2
  240. package/dist/server/seed-cache.d.ts +1 -0
  241. package/dist/server/seed-cache.js +15 -11
  242. package/dist/server/server-reference-loader.d.ts +4 -0
  243. package/dist/server/server-reference-loader.js +29 -0
  244. package/dist/server/socket-error-backstop.d.ts +23 -1
  245. package/dist/server/socket-error-backstop.js +69 -17
  246. package/dist/server/static-file-signal.d.ts +15 -4
  247. package/dist/server/static-file-signal.js +46 -12
  248. package/dist/server/worker-image-optimization.d.ts +34 -0
  249. package/dist/server/worker-image-optimization.js +86 -0
  250. package/dist/server/worker-stages.d.ts +1 -1
  251. package/dist/server/worker-stages.js +2 -2
  252. package/dist/server/worker-utils.d.ts +8 -3
  253. package/dist/server/worker-utils.js +12 -7
  254. package/dist/shims/app-router-scroll.js +4 -1
  255. package/dist/shims/app-router-tree-context.d.ts +5 -0
  256. package/dist/shims/app-router-tree-context.js +27 -0
  257. package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
  258. package/dist/shims/cache-adapter-instantiate.js +118 -0
  259. package/dist/shims/cache-callable-runtime.d.ts +6 -42
  260. package/dist/shims/cache-callable-runtime.js +65 -174
  261. package/dist/shims/cache-flight-arguments.d.ts +22 -0
  262. package/dist/shims/cache-flight-arguments.js +56 -0
  263. package/dist/shims/cache-handler.js +1 -9
  264. package/dist/shims/cache-request-state.d.ts +15 -0
  265. package/dist/shims/cache-request-state.js +31 -8
  266. package/dist/shims/cache-runtime.d.ts +12 -20
  267. package/dist/shims/cache-runtime.js +166 -225
  268. package/dist/shims/cache.d.ts +4 -4
  269. package/dist/shims/cache.js +44 -16
  270. package/dist/shims/cacheability-classification.d.ts +7 -0
  271. package/dist/shims/cdn-cache.d.ts +8 -0
  272. package/dist/shims/cdn-cache.js +17 -1
  273. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  274. package/dist/shims/disable-smooth-scroll.js +36 -0
  275. package/dist/shims/document-placeholders.d.ts +19 -0
  276. package/dist/shims/document-placeholders.js +45 -0
  277. package/dist/shims/document.js +21 -5
  278. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  279. package/dist/shims/dynamic-preload-chunks.js +13 -9
  280. package/dist/shims/dynamic.d.ts +1 -1
  281. package/dist/shims/dynamic.js +60 -8
  282. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  283. package/dist/shims/error-boundary-navigation.js +19 -1
  284. package/dist/shims/error-boundary.d.ts +10 -10
  285. package/dist/shims/error-boundary.js +26 -7
  286. package/dist/shims/fetch-cache-state.d.ts +66 -0
  287. package/dist/shims/fetch-cache-state.js +92 -0
  288. package/dist/shims/fetch-cache.d.ts +3 -61
  289. package/dist/shims/fetch-cache.js +161 -108
  290. package/dist/shims/form.js +1 -1
  291. package/dist/shims/head.d.ts +10 -6
  292. package/dist/shims/head.js +15 -13
  293. package/dist/shims/headers.d.ts +13 -0
  294. package/dist/shims/headers.js +47 -4
  295. package/dist/shims/image.js +2 -1
  296. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
  297. package/dist/shims/internal/app-page-props-cache-key.js +24 -15
  298. package/dist/shims/internal/headers-state.d.ts +0 -1
  299. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  300. package/dist/shims/internal/work-store-scope.js +24 -0
  301. package/dist/shims/metadata.js +3 -3
  302. package/dist/shims/navigation-errors.d.ts +6 -0
  303. package/dist/shims/navigation-errors.js +11 -1
  304. package/dist/shims/navigation.d.ts +7 -0
  305. package/dist/shims/navigation.js +45 -9
  306. package/dist/shims/request-context.d.ts +11 -0
  307. package/dist/shims/request-state-types.d.ts +2 -1
  308. package/dist/shims/router.js +12 -20
  309. package/dist/shims/server.d.ts +16 -0
  310. package/dist/shims/server.js +24 -4
  311. package/dist/shims/thenable-params.d.ts +4 -0
  312. package/dist/shims/thenable-params.js +25 -3
  313. package/dist/shims/unified-request-context.d.ts +1 -1
  314. package/dist/shims/unified-request-context.js +1 -0
  315. package/dist/shims/url-utils.d.ts +2 -0
  316. package/dist/shims/url-utils.js +3 -1
  317. package/dist/utils/client-runtime-metadata.js +1 -1
  318. package/dist/utils/hash.d.ts +7 -0
  319. package/dist/utils/hash.js +11 -1
  320. package/dist/utils/mdx-scan.d.ts +5 -0
  321. package/dist/utils/mdx-scan.js +8 -1
  322. package/dist/utils/package-name.d.ts +6 -0
  323. package/dist/utils/package-name.js +12 -1
  324. package/dist/utils/plugin-rsc-version.d.ts +4 -0
  325. package/dist/utils/plugin-rsc-version.js +15 -0
  326. package/dist/utils/prerender-output-paths.js +15 -4
  327. package/dist/utils/public-file-path.d.ts +10 -2
  328. package/dist/utils/public-file-path.js +13 -9
  329. package/dist/utils/public-routes.js +2 -2
  330. package/dist/utils/react-version.d.ts +3 -0
  331. package/dist/utils/react-version.js +1 -1
  332. package/dist/utils/regex-safety.d.ts +10 -0
  333. package/dist/utils/regex-safety.js +70 -18
  334. package/package.json +9 -5
package/README.md CHANGED
@@ -147,6 +147,9 @@ Other options: `--port <port>` (default: 3001), `--skip-check`, `--force`.
147
147
  Cloudflare init uses `cf` and `cloudflare.config.ts` by default. Both `vinext init`
148
148
  and `create-vinext-app` accept `--legacy-wrangler-cloudflare-init` to retain the
149
149
  legacy Wrangler setup. Existing Wrangler configs are not automatically migrated.
150
+ `vinext init --name <name>` sets the Worker name in the generated config instead of
151
+ deriving it from `package.json`. An existing config keeps its own Worker name, and init
152
+ prints a warning.
150
153
 
151
154
  If your `next.config.*` sets `output: "standalone"`, `vite build` emits a self-hosting bundle at `dist/standalone/`. Start it with:
152
155
 
@@ -158,6 +161,42 @@ Environment variables: `PORT` (default `3000`), `HOST` (default `0.0.0.0`).
158
161
 
159
162
  > **Note:** Next.js standalone uses `HOSTNAME` for the bind address, but vinext uses `HOST` to avoid collision with the system-set `HOSTNAME` variable on Linux. Update your deployment config accordingly.
160
163
 
164
+ #### Custom standalone entries
165
+
166
+ The generated `server.js` is a small wrapper around `startProdServer` from `vinext/server/prod-server`. To control where the server loads the build output from, write your own entry and call `startProdServer` directly. All options are optional:
167
+
168
+ | Option | Default | Description |
169
+ | ----------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
170
+ | `port` | `PORT` env var, otherwise `3000` | Port to listen on. |
171
+ | `host` | `"0.0.0.0"` | Address to bind to. Unlike the generated `server.js`, `startProdServer` doesn't read `HOST`, so pass `process.env.HOST` yourself if you need it. |
172
+ | `outDir` | `dist`, resolved against the current directory | Build output directory containing `client/` and `server/`. Relative paths resolve against the current directory. In standalone output, this is `dist/standalone/dist`. |
173
+ | `serverDir` | `<outDir>/server` | Directory containing the server bundles, manifests, and prerendered output. |
174
+ | `rscEntryPath` | `<serverDir>/index.js` | App Router server entry. If this file exists, the server runs in App Router mode. |
175
+ | `serverEntryPath` | `<serverDir>/entry.js` | Pages Router server entry. Used when there's no App Router entry. |
176
+ | `noCompression` | `false` | Disable response compression. |
177
+
178
+ `ProdServerOptions` also has `purpose` and `silent`, which vinext uses for its own build-time servers. Leave them unset. The returned promise resolves to `{ server, port }` once the server is listening.
179
+
180
+ **Compiled single-file binaries.** A `bun build --compile` binary can't use the generated `server.js`. Bun fixes `import.meta.dirname` when it compiles the binary, so `join(import.meta.dirname, "dist")` doesn't point at the directory the binary runs from. Depending on the Bun version, it points at the build machine's path or at Bun's embedded `/$bunfs/root`. Resolve `outDir` from the executable's location instead:
181
+
182
+ ```js
183
+ // server-bin.mjs, in your project root
184
+ import path from "node:path";
185
+ import { startProdServer } from "vinext/server/prod-server";
186
+
187
+ await startProdServer({
188
+ port: Number.parseInt(process.env.PORT ?? "3000", 10),
189
+ host: process.env.HOST ?? "0.0.0.0",
190
+ outDir: path.join(path.dirname(process.execPath), "dist"),
191
+ });
192
+ ```
193
+
194
+ ```bash
195
+ bun build --compile --compile-autoload-package-json server-bin.mjs --outfile dist/standalone/server
196
+ ```
197
+
198
+ Keep the entry outside `dist/standalone`: each build deletes and recreates that directory. Compile after `vite build`, then ship the whole `dist/standalone` directory, because the `server` binary needs the `dist/` and `node_modules/` directories next to it. The server bundles in `dist/server` still import their dependencies from `node_modules` at runtime. `--compile-autoload-package-json` lets the binary read package `exports` and `main` fields when it resolves them. Under plain Node, `process.execPath` is the Node binary, so use this entry only for compiled builds.
199
+
161
200
  ### Starting a new vinext project
162
201
 
163
202
  Use `create-vinext-app` for new projects. It creates a TypeScript App Router project
@@ -246,7 +285,7 @@ You can, with caution. vinext has known compatibility gaps and has not yet been
246
285
  Yes. Next.js supports [self-hosting](https://nextjs.org/docs/app/building-your-application/deploying#self-hosting) on Node.js servers, Docker containers, and static exports. If you're happy with the Next.js toolchain and just want to run it somewhere other than Vercel, self-hosting is the simplest path.
247
286
 
248
287
  **How are you verifying this works?**
249
- The test suite has over 1,700 Vitest tests and 380 Playwright E2E tests. This includes tests ported directly from the [Next.js test suite](https://github.com/vercel/next.js/tree/canary/test) and [OpenNext's Cloudflare conformance suite](https://github.com/opennextjs/opennextjs-cloudflare), covering routing, SSR, RSC, server actions, caching, metadata, middleware, streaming, and more. Vercel's [App Router Playground](https://github.com/vercel/next-app-router-playground) also runs on vinext as an integration test. See the [Tests](#tests) section and `tests/nextjs-compat/TRACKING.md` for details.
288
+ The test suite has over 1,700 Vitest tests and 380 Playwright E2E tests. This includes tests ported directly from the [Next.js test suite](https://github.com/vercel/next.js/tree/canary/test) and [OpenNext's Cloudflare conformance suite](https://github.com/opennextjs/opennextjs-cloudflare), covering routing, SSR, RSC, server actions, caching, metadata, middleware, streaming, and more. Vercel's [App Router Playground](https://github.com/vercel/next-app-router-playground) also runs on vinext as an integration test. See the [Tests](#tests) section for details.
250
289
 
251
290
  **Who is reviewing this code?**
252
291
  A mix of humans and AI agents. Humans review PRs before they merge, focused on behavior, structure, and long-term direction. We lean heavily on agent-driven code review to catch issues at PR time and across the codebase. The test suite is the primary quality gate. Outside contributions and deeper human code review are very welcome.
@@ -401,10 +440,14 @@ export default defineConfig({
401
440
 
402
441
  #### Module Federation (client-side)
403
442
 
404
- For client-side Module Federation, configure React and React DOM as singleton shared modules in both the host and remotes:
443
+ Use [`@module-federation/vinext`](https://github.com/module-federation/vinext), a thin wrapper around `@module-federation/vite` with vinext defaults. Register `federation()` before `vinext()`:
444
+
445
+ ```bash
446
+ npm install @module-federation/vinext
447
+ ```
405
448
 
406
449
  ```ts
407
- import { federation } from "@module-federation/vite";
450
+ import { federation } from "@module-federation/vinext";
408
451
  import { defineConfig } from "vite";
409
452
  import vinext from "vinext";
410
453
 
@@ -412,11 +455,14 @@ export default defineConfig({
412
455
  plugins: [
413
456
  federation({
414
457
  name: "host",
415
- shared: {
416
- react: { singleton: true },
417
- "react/": { singleton: true },
418
- "react-dom": { singleton: true },
419
- "react-dom/": { singleton: true },
458
+ remotes: {
459
+ catalog: {
460
+ type: "module",
461
+ name: "catalog",
462
+ entry: "https://catalog.example.com/remoteEntry.js",
463
+ entryGlobalName: "catalog",
464
+ shareScope: "default",
465
+ },
420
466
  },
421
467
  }),
422
468
  vinext(),
@@ -424,6 +470,8 @@ export default defineConfig({
424
470
  });
425
471
  ```
426
472
 
473
+ The wrapper shares `react` and `react-dom` as singletons, defaults `filename` to `remoteEntry.js`, and injects the host's federation startup into the entry, because vinext has no conventional HTML entry. Explicit options override these defaults, and every `@module-federation/vite` option remains available. Passing your own `shared` replaces the default map, so keep `react`, `react/`, `react-dom`, and `react-dom/` as singletons in it, in both the host and remotes.
474
+
427
475
  In a remote client component, use `getVinextReact()` before reading React hooks. vinext registers the host's browser React instance before application modules execute, and the first registration remains stable across remote evaluation and HMR:
428
476
 
429
477
  ```tsx
@@ -442,7 +490,7 @@ export function RemoteCounter() {
442
490
 
443
491
  This bridge is browser-only. It does not provide App Router Module Federation SSR or transparently replace React imports inside third-party packages; compatible React versions remain the responsibility of the Module Federation `shared` configuration.
444
492
 
445
- See the [examples](#live-examples) for complete working configurations.
493
+ See the [Module Federation guide](https://vinext.dev/docs/guides/module-federation) for loading remote components in the host.
446
494
 
447
495
  ### Other platforms (via Nitro)
448
496
 
@@ -792,7 +840,7 @@ their `env` binding lookups) are instantiated lazily on the first request.
792
840
 
793
841
  Registration is wired into **every router and runtime** — App Router and Pages Router, on Cloudflare Workers as well as the Node.js server (`vinext start`) and dev. It self-guards (instantiated once per isolate) and is resilient: if an adapter can't initialize on a given runtime (e.g. a KV binding doesn't exist on the Node server), vinext logs a warning and falls back to the default handler instead of failing requests.
794
842
 
795
- To write your own adapter, point a slot at any module by path and default-export a factory that receives `{ env, options }` at runtime and returns a data-cache `CacheHandler` (or a CDN adapter):
843
+ To write your own adapter, point a slot at any module by path. Its default export receives one `{ env, options }` argument at runtime and produces a data-cache `CacheHandler` (or a CDN adapter):
796
844
 
797
845
  ```ts
798
846
  vinext({
@@ -805,6 +853,41 @@ vinext({
805
853
  });
806
854
  ```
807
855
 
856
+ The default export can be a factory or a class:
857
+
858
+ ```ts
859
+ // my-adapter.js: a factory
860
+ export default ({ env, options }) => ({
861
+ async get(key) {
862
+ /* … */
863
+ },
864
+ async set(key, data) {
865
+ /* … */
866
+ },
867
+ async revalidateTag(tags) {
868
+ /* … */
869
+ },
870
+ });
871
+
872
+ // my-adapter.js: or a class
873
+ export default class MyAdapter {
874
+ constructor({ env, options }) {
875
+ /* … */
876
+ }
877
+ async get(key) {
878
+ /* … */
879
+ }
880
+ async set(key, data) {
881
+ /* … */
882
+ }
883
+ async revalidateTag(tags) {
884
+ /* … */
885
+ }
886
+ }
887
+ ```
888
+
889
+ vinext invokes class syntax with `new` and calls any other function, so existing factories keep plain-call semantics. A constructor that isn't class syntax (an ES5-compiled, bound, or Proxy-wrapped class) can't be told apart from a factory, so export a factory that constructs it: `export default (args) => new MyAdapter(args)`. The result must be the adapter itself, not a Promise. A data adapter implements `get`, `set`, and `revalidateTag`; a CDN adapter also implements `buildResponseHeaders` and sets `ownsBackgroundRevalidation` to a boolean. If the export isn't a function or the result is missing a member, vinext logs a warning naming the slot and what's wrong, then keeps the default handler.
890
+
808
891
  ## What's NOT supported (and won't be)
809
892
 
810
893
  These are intentional exclusions. For things that are missing today but on the roadmap, see [Known gaps we're working on](#known-gaps-were-working-on) above.
@@ -0,0 +1,15 @@
1
+ import { AppRoute } from "../routing/app-route-graph.js";
2
+ //#region src/build/app-page-static-eligibility.d.ts
3
+ /**
4
+ * Whether Next.js classifies an App page route as static or SSG, read from its
5
+ * layout, page and parallel-slot sources with the helpers dispatch applies to
6
+ * the loaded modules. Only such a route has listed paths.
7
+ *
8
+ * An MDX source read without the MDX parser has unknown exports, so its route
9
+ * is "unreadable". Its paths are still listed: the built runtime reads the real
10
+ * exports and never stores a route that isn't static, while an unlisted path's
11
+ * render failure would be dropped instead of failing. But no probe renders an
12
+ * unreadable route's fallback, so it never certifies one.
13
+ */
14
+ export declare function classifyAppPageRouteStaticEligibility(route: AppRoute, readMdxEsm: ((source: string) => string) | null): "eligible" | "ineligible" | "unreadable";
15
+ //#endregion
@@ -0,0 +1,82 @@
1
+ import { extractExportConstNumber, extractExportConstString, hasRuntimeExportedName } from "./report.js";
2
+ import { collectAppPageStaticGenerationRuntimes, hasAppPageGenerateStaticParamsAtLastDynamicSegment, isAppPageStaticEligible, isEdgeRuntime, resolveAppPageSegmentConfig, resolveAppPageStaticGenerationRuntime } from "../server/app-segment-config.js";
3
+ import fs from "node:fs";
4
+ //#region src/build/app-page-static-eligibility.ts
5
+ /**
6
+ * Whether Next.js classifies an App page route as static or SSG, read from its
7
+ * layout, page and parallel-slot sources with the helpers dispatch applies to
8
+ * the loaded modules. Only such a route has listed paths.
9
+ *
10
+ * An MDX source read without the MDX parser has unknown exports, so its route
11
+ * is "unreadable". Its paths are still listed: the built runtime reads the real
12
+ * exports and never stores a route that isn't static, while an unlisted path's
13
+ * render failure would be dropped instead of failing. But no probe renders an
14
+ * unreadable route's fallback, so it never certifies one.
15
+ */
16
+ function classifyAppPageRouteStaticEligibility(route, readMdxEsm) {
17
+ let unreadable = false;
18
+ const readSegmentConfig = (filePath) => {
19
+ if (!filePath) return null;
20
+ let code = fs.readFileSync(filePath, "utf8");
21
+ if (filePath.toLowerCase().endsWith(".mdx")) {
22
+ let esm = null;
23
+ try {
24
+ esm = readMdxEsm?.(code) ?? null;
25
+ } catch {}
26
+ unreadable ||= esm === null;
27
+ code = esm ?? "";
28
+ }
29
+ const dynamic = extractExportConstString(code, "dynamic");
30
+ const revalidate = extractExportConstNumber(code, "revalidate");
31
+ const runtime = extractExportConstString(code, "runtime");
32
+ return {
33
+ ...dynamic === null ? {} : { dynamic },
34
+ ...hasRuntimeExportedName(code, "generateStaticParams") ? { generateStaticParams() {} } : {},
35
+ ...revalidate === null ? {} : { revalidate },
36
+ ...runtime === null ? {} : { runtime }
37
+ };
38
+ };
39
+ const layouts = route.layouts.map(readSegmentConfig);
40
+ const page = readSegmentConfig(route.pagePath);
41
+ const parallelBranches = route.parallelSlots.map((slot) => ({
42
+ configLayouts: (slot.configLayoutPaths ?? []).map(readSegmentConfig),
43
+ configLayoutTreePositions: slot.configLayoutTreePositions ?? [],
44
+ isDefault: !slot.pagePath,
45
+ layout: readSegmentConfig(slot.layoutPath),
46
+ name: slot.name,
47
+ ownerTreePosition: slot.ownerTreePosition ?? null,
48
+ page: readSegmentConfig(slot.pagePath ?? slot.defaultPath),
49
+ routeSegments: slot.routeSegments
50
+ }));
51
+ if (unreadable) return "unreadable";
52
+ const segmentConfig = resolveAppPageSegmentConfig({
53
+ layouts,
54
+ layoutTreePositions: route.layoutTreePositions,
55
+ page,
56
+ parallelBranches,
57
+ routeSegments: route.routeSegments
58
+ });
59
+ return isAppPageStaticEligible({
60
+ dynamicConfig: segmentConfig.dynamicConfig,
61
+ hasGenerateStaticParams: hasAppPageGenerateStaticParamsAtLastDynamicSegment({
62
+ childrenSlot: route.childrenSlot ?? null,
63
+ layouts,
64
+ layoutTreePositions: route.layoutTreePositions,
65
+ page,
66
+ parallelBranches,
67
+ routeSegments: route.routeSegments
68
+ }),
69
+ isDynamicRoute: route.isDynamic,
70
+ isStaticGenerationEdgeRuntime: isEdgeRuntime(resolveAppPageStaticGenerationRuntime(collectAppPageStaticGenerationRuntimes({
71
+ childrenSlot: route.childrenSlot ?? null,
72
+ layouts,
73
+ layoutTreePositions: route.layoutTreePositions,
74
+ page,
75
+ parallelBranches,
76
+ routeSegments: route.routeSegments
77
+ }))),
78
+ revalidateSeconds: segmentConfig.revalidateSeconds
79
+ }) ? "eligible" : "ineligible";
80
+ }
81
+ //#endregion
82
+ export { classifyAppPageRouteStaticEligibility };
@@ -116,4 +116,14 @@ export declare function createMultiStageCodeSplittingConfig(existing: VinextBuil
116
116
  * the host's existing output pattern (or vinext's server-assets default).
117
117
  */
118
118
  export declare function createMultiStageChunkFileNames(assetsDir: string, existing: VinextBuildOutput["chunkFileNames"]): ChunkFileNameFunction;
119
+ /**
120
+ * The client environment builds after RSC and SSR, and Vite empties its
121
+ * output directory first. A client output directory that is, or contains, a
122
+ * server output directory would therefore delete the server build.
123
+ */
124
+ export declare function assertClientOutDirIsSeparate(root: string, dirs: {
125
+ clientOutDir?: string;
126
+ rscOutDir?: string;
127
+ ssrOutDir?: string;
128
+ }): void;
119
129
  //#endregion
@@ -1,6 +1,7 @@
1
- import { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
1
+ import path, { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
2
  //#region src/build/client-build-config.ts
3
3
  const ROUTE_OWNED_CLIENT_SHIMS = /* @__PURE__ */ new Set([
4
+ "app-router-tree-context",
4
5
  "compat-router",
5
6
  "dynamic",
6
7
  "dynamic-preload-chunks",
@@ -266,5 +267,20 @@ function createMultiStageChunkFileNames(assetsDir, existing) {
266
267
  return (existing ?? joinAssetFileNamePattern(assetsDir, "[name]-[hash].js")).replaceAll("[name]", name);
267
268
  };
268
269
  }
270
+ /**
271
+ * The client environment builds after RSC and SSR, and Vite empties its
272
+ * output directory first. A client output directory that is, or contains, a
273
+ * server output directory would therefore delete the server build.
274
+ */
275
+ function assertClientOutDirIsSeparate(root, dirs) {
276
+ if (!dirs.clientOutDir) return;
277
+ const clientOutDir = path.resolve(root, dirs.clientOutDir);
278
+ const serverOutDirs = [["rscOutDir", dirs.rscOutDir ?? "dist/server"], ["ssrOutDir", dirs.ssrOutDir ?? "dist/server/ssr"]];
279
+ for (const [name, dir] of serverOutDirs) {
280
+ const relative = path.relative(clientOutDir, path.resolve(root, dir));
281
+ if (relative === ".." || relative.startsWith("../") || path.isAbsolute(relative)) continue;
282
+ throw new Error(`[vinext] clientOutDir "${dirs.clientOutDir}" contains ${name} "${dir}". The client build runs last and empties its output directory, which would delete the server build. Use separate output directories.`);
283
+ }
284
+ }
269
285
  //#endregion
270
- export { RSC_FRAMEWORK_CHUNK_TEST, createClientAssetFileNames, createClientCodeSplittingConfig, createClientFileNameConfig, createClientManualChunks, createMultiStageChunkFileNames, createMultiStageCodeSplittingConfig, createRscFrameworkChunkOutputConfig, getBuildBundlerOptions, getClientTreeshakeConfig, isRscFrameworkModule, sanitizeRscChunkFileName, withBuildBundlerOptions };
286
+ export { RSC_FRAMEWORK_CHUNK_TEST, assertClientOutDirIsSeparate, createClientAssetFileNames, createClientCodeSplittingConfig, createClientFileNameConfig, createClientManualChunks, createMultiStageChunkFileNames, createMultiStageCodeSplittingConfig, createRscFrameworkChunkOutputConfig, getBuildBundlerOptions, getClientTreeshakeConfig, isRscFrameworkModule, sanitizeRscChunkFileName, withBuildBundlerOptions };
@@ -2,5 +2,13 @@ import { PagesClientAssets } from "../server/pages-client-assets.js";
2
2
  //#region src/build/pages-client-assets-module.d.ts
3
3
  export declare const PAGES_CLIENT_ASSETS_MODULE = "vinext-client-assets.js";
4
4
  export declare function buildPagesClientAssetsModule(assets: PagesClientAssets): string;
5
+ /**
6
+ * Select the client bundle files that live outside `_next/static`, as URL
7
+ * pathnames. Plugins emit these at the output root, for example a Module
8
+ * Federation host's `remoteEntry.js`. Public directory files are copied
9
+ * outside the bundle, and build metadata (vinext's and Cloudflare's
10
+ * `.assetsignore`) is excluded.
11
+ */
12
+ export declare function collectClientBuildFiles(fileNames: Iterable<string>): string[];
5
13
  export declare function writePagesClientAssetsModuleIfMissing(outputDir: string, moduleSource: string): void;
6
14
  //#endregion
@@ -1,5 +1,7 @@
1
1
  import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
+ import "../utils/asset-prefix.js";
2
3
  import { getSharedChunkFiles } from "../server/pages-asset-tags.js";
4
+ import "../utils/client-entry-manifest.js";
3
5
  import fs from "node:fs";
4
6
  //#region src/build/pages-client-assets-module.ts
5
7
  const PAGES_CLIENT_ASSETS_MODULE = "vinext-client-assets.js";
@@ -10,6 +12,24 @@ function buildPagesClientAssetsModule(assets) {
10
12
  } : assets;
11
13
  return `export default ${JSON.stringify(prepared)};\n`;
12
14
  }
15
+ /**
16
+ * Select the client bundle files that live outside `_next/static`, as URL
17
+ * pathnames. Plugins emit these at the output root, for example a Module
18
+ * Federation host's `remoteEntry.js`. Public directory files are copied
19
+ * outside the bundle, and build metadata (vinext's and Cloudflare's
20
+ * `.assetsignore`) is excluded.
21
+ */
22
+ function collectClientBuildFiles(fileNames) {
23
+ const files = [];
24
+ for (const fileName of fileNames) {
25
+ const pathname = `/${fileName}`;
26
+ if (pathname.includes(`/_next/static/`)) continue;
27
+ if (pathname.startsWith("/.vite/") || fileName === "vinext-client-entry-manifest.json") continue;
28
+ if (fileName === ".assetsignore") continue;
29
+ files.push(pathname);
30
+ }
31
+ return files.sort();
32
+ }
13
33
  function writePagesClientAssetsModuleIfMissing(outputDir, moduleSource) {
14
34
  const outputPath = path.join(outputDir, PAGES_CLIENT_ASSETS_MODULE);
15
35
  if (fs.existsSync(outputPath)) return;
@@ -17,4 +37,4 @@ function writePagesClientAssetsModuleIfMissing(outputDir, moduleSource) {
17
37
  fs.writeFileSync(outputPath, moduleSource);
18
38
  }
19
39
  //#endregion
20
- export { PAGES_CLIENT_ASSETS_MODULE, buildPagesClientAssetsModule, writePagesClientAssetsModuleIfMissing };
40
+ export { PAGES_CLIENT_ASSETS_MODULE, buildPagesClientAssetsModule, collectClientBuildFiles, writePagesClientAssetsModuleIfMissing };
@@ -10,17 +10,18 @@ import { apiRouter, matchRoute, pagesRouter } from "../routing/pages-router.js";
10
10
  import "../server/headers.js";
11
11
  import { matchesMiddlewarePathname } from "../server/middleware-matcher.js";
12
12
  import { extractLocaleFromUrl, normalizeDefaultLocalePathname } from "../server/pages-i18n.js";
13
- import { classifyAppRoute, classifyAppRouteHandler, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractMiddlewareMatcherConfig, getAppRouteRenderEntryPath, hasRuntimeExportedName } from "./report.js";
13
+ import { classifyAppRoute, classifyAppRouteHandler, classifyPagesRoute, extractExportConstString, extractMiddlewareMatcherConfig, getAppRouteRenderEntryPath } from "./report.js";
14
14
  import { normalizeStaticPathname, normalizeStaticPathsEntry } from "../routing/route-pattern.js";
15
15
  import { buildPagesDataHref } from "../shims/internal/pages-data-url.js";
16
16
  import { normalizePathTrailingSlash } from "../shims/url-utils.js";
17
+ import { resolveAppPageDynamicConfig } from "../server/app-segment-config.js";
18
+ import { classifyAppPageRouteStaticEligibility } from "./app-page-static-eligibility.js";
17
19
  import { pagesRouteHasPriorityOverAppRoute } from "../server/hybrid-route-priority.js";
18
20
  import { readPrerenderSecret } from "./server-manifest.js";
19
21
  import { loadMdxEsmReader } from "../utils/mdx-scan.js";
20
22
  import { startProdServer } from "../server/prod-server.js";
21
23
  import { enterPrerenderPhase } from "./prerender-phase.js";
22
24
  import { buildUrlFromParams, layoutOnlyParamSets, localizePagesPath, resolveParentParams, routeStaticParamSets, validateDiscoveredParams } from "./prerender.js";
23
- import { collectAppPageStaticGenerationRuntimes, hasAppPageGenerateStaticParamsAtLastDynamicSegment, isAppPageStaticEligible, isEdgeRuntime, resolveAppPageDynamicConfig, resolveAppPageSegmentConfig, resolveAppPageStaticGenerationRuntime } from "../server/app-segment-config.js";
24
25
  import { resolveBuiltPagesEntryPath, resolveBuiltRscEntryPath } from "./server-entry.js";
25
26
  import fs from "node:fs";
26
27
  //#region src/build/prerender-paths.ts
@@ -133,6 +134,7 @@ async function fetchDiscoveryEndpoint(url, headers, retryOptions = {}) {
133
134
  const request = (async () => {
134
135
  const res = await fetch(url, {
135
136
  headers,
137
+ redirect: "manual",
136
138
  signal: controller.signal
137
139
  });
138
140
  return {
@@ -151,10 +153,11 @@ async function fetchDiscoveryEndpoint(url, headers, retryOptions = {}) {
151
153
  if (res.status === 204) return null;
152
154
  return text;
153
155
  }
154
- const userFailure = readDiscoveryUserFailure(res, text);
156
+ const redirected = res.status >= 300 && res.status < 400;
157
+ const userFailure = redirected ? null : readDiscoveryUserFailure(res, text);
155
158
  const detail = userFailure ?? (/cloudflare:|ERR_UNSUPPORTED_ESM_URL_SCHEME/i.test(text) ? text.trim() : "");
156
- lastError = /* @__PURE__ */ new Error(`path discovery returned HTTP ${res.status}${detail ? `: ${detail}` : ""}`);
157
- if (!(res.status === 404 || res.status >= 500 && res.status <= 599 && userFailure === null)) {
159
+ lastError = redirected ? /* @__PURE__ */ new Error(`path discovery returned a redirect (HTTP ${res.status}); refusing to follow it with the prerender secret`) : /* @__PURE__ */ new Error(`path discovery returned HTTP ${res.status}${detail ? `: ${detail}` : ""}`);
160
+ if (!(redirected || res.status === 404 || res.status >= 500 && res.status <= 599 && userFailure === null)) {
158
161
  shouldRetry = false;
159
162
  throw lastError;
160
163
  }
@@ -351,82 +354,6 @@ function extractPagesStaticPathLocale(url, i18n) {
351
354
  url: `${rest || "/"}${query}`
352
355
  };
353
356
  }
354
- /**
355
- * Whether Next.js classifies an App page route as static or SSG, read from its
356
- * layout, page and parallel-slot sources with the helpers dispatch applies to
357
- * the loaded modules. Only such a route has listed paths.
358
- *
359
- * An MDX source read without the MDX parser has unknown exports, so its route
360
- * is "unreadable". Its paths are still listed: the built runtime reads the real
361
- * exports and never stores a route that isn't static, while an unlisted path's
362
- * render failure would be dropped instead of failing. But no probe renders an
363
- * unreadable route's fallback, so it never certifies one.
364
- */
365
- function classifyAppPageRouteStaticEligibility(route, readMdxEsm) {
366
- let unreadable = false;
367
- const readSegmentConfig = (filePath) => {
368
- if (!filePath) return null;
369
- let code = fs.readFileSync(filePath, "utf8");
370
- if (filePath.toLowerCase().endsWith(".mdx")) {
371
- let esm = null;
372
- try {
373
- esm = readMdxEsm?.(code) ?? null;
374
- } catch {}
375
- unreadable ||= esm === null;
376
- code = esm ?? "";
377
- }
378
- const dynamic = extractExportConstString(code, "dynamic");
379
- const revalidate = extractExportConstNumber(code, "revalidate");
380
- const runtime = extractExportConstString(code, "runtime");
381
- return {
382
- ...dynamic === null ? {} : { dynamic },
383
- ...hasRuntimeExportedName(code, "generateStaticParams") ? { generateStaticParams() {} } : {},
384
- ...revalidate === null ? {} : { revalidate },
385
- ...runtime === null ? {} : { runtime }
386
- };
387
- };
388
- const layouts = route.layouts.map(readSegmentConfig);
389
- const page = readSegmentConfig(route.pagePath);
390
- const parallelBranches = route.parallelSlots.map((slot) => ({
391
- configLayouts: (slot.configLayoutPaths ?? []).map(readSegmentConfig),
392
- configLayoutTreePositions: slot.configLayoutTreePositions ?? [],
393
- isDefault: !slot.pagePath,
394
- layout: readSegmentConfig(slot.layoutPath),
395
- name: slot.name,
396
- ownerTreePosition: slot.ownerTreePosition ?? null,
397
- page: readSegmentConfig(slot.pagePath ?? slot.defaultPath),
398
- routeSegments: slot.routeSegments
399
- }));
400
- if (unreadable) return "unreadable";
401
- const segmentConfig = resolveAppPageSegmentConfig({
402
- layouts,
403
- layoutTreePositions: route.layoutTreePositions,
404
- page,
405
- parallelBranches,
406
- routeSegments: route.routeSegments
407
- });
408
- return isAppPageStaticEligible({
409
- dynamicConfig: segmentConfig.dynamicConfig,
410
- hasGenerateStaticParams: hasAppPageGenerateStaticParamsAtLastDynamicSegment({
411
- childrenSlot: route.childrenSlot ?? null,
412
- layouts,
413
- layoutTreePositions: route.layoutTreePositions,
414
- page,
415
- parallelBranches,
416
- routeSegments: route.routeSegments
417
- }),
418
- isDynamicRoute: route.isDynamic,
419
- isStaticGenerationEdgeRuntime: isEdgeRuntime(resolveAppPageStaticGenerationRuntime(collectAppPageStaticGenerationRuntimes({
420
- childrenSlot: route.childrenSlot ?? null,
421
- layouts,
422
- layoutTreePositions: route.layoutTreePositions,
423
- page,
424
- parallelBranches,
425
- routeSegments: route.routeSegments
426
- }))),
427
- revalidateSeconds: segmentConfig.revalidateSeconds
428
- }) ? "eligible" : "ineligible";
429
- }
430
357
  /** Keys a path an App page route lists by the route and the pathname together. */
431
358
  function appPageListingKey(routePattern, pathname) {
432
359
  return `${routePattern}\0${pathname}`;
@@ -4,7 +4,7 @@ import { isUnknownRecord } from "../utils/record.js";
4
4
  import { BLOCKED_PAGES } from "../shims/constants.js";
5
5
  import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
6
6
  import { appRouteLayoutStaticParamsGroups } from "../routing/app-router.js";
7
- import { NEXT_CACHE_TAGS_HEADER, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH, VINEXT_PRERENDER_RENDER_ERROR_HEADER } from "../server/headers.js";
7
+ import { NEXT_CACHE_TAGS_HEADER, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH, VINEXT_PRERENDER_RENDER_ERROR_HEADER, VINEXT_PRERENDER_SPECIAL_ERROR_HEADER } from "../server/headers.js";
8
8
  import { extractLocaleFromUrl } from "../server/pages-i18n.js";
9
9
  import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath } from "./report.js";
10
10
  import { resolveClientStaleTimeSeconds } from "../utils/cache-control-metadata.js";
@@ -12,6 +12,7 @@ import { NoOpCacheHandler, getCacheHandler, setCacheHandler } from "../shims/cac
12
12
  import { headersContextFromRequest, runWithHeadersContext } from "../shims/headers.js";
13
13
  import { _consumeRequestScopedCacheLife } from "../shims/cache-request-state.js";
14
14
  import { normalizeStaticPathsEntry } from "../routing/route-pattern.js";
15
+ import { createRscRequestUrl } from "../server/app-rsc-cache-busting.js";
15
16
  import { readPrerenderSecret } from "./server-manifest.js";
16
17
  import { buildPregeneratedConcretePathTable } from "../server/prerender-manifest.js";
17
18
  import { concatUint8Arrays, decodeRscEmbeddedChunk } from "../server/app-rsc-embedded-chunks.js";
@@ -1041,17 +1042,22 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
1041
1042
  const responseCacheLife = readPrerenderCacheLifeHeader(response.headers);
1042
1043
  const cacheTags = readPrerenderCacheTagsHeader(response.headers);
1043
1044
  const fatal = response.headers.get(VINEXT_PRERENDER_RENDER_ERROR_HEADER) === "1";
1044
- if (!response.ok || cacheControl.includes("no-store")) {
1045
+ const specialError = mode === "export" ? null : readPrerenderSpecialErrorHeader(response.headers);
1046
+ const isSpecialError = specialError !== null;
1047
+ const ok = response.ok || isSpecialError;
1048
+ if (!ok || cacheControl.includes("no-store")) {
1045
1049
  await response.body?.cancel();
1046
1050
  return {
1047
1051
  cacheControl,
1048
1052
  linkHeader,
1049
1053
  html: null,
1050
- ok: response.ok,
1054
+ ok,
1051
1055
  requestCacheLife: null,
1052
1056
  tags: [],
1053
1057
  status: response.status,
1054
- fatal
1058
+ fatal,
1059
+ specialErrorLocation: null,
1060
+ isSpecialError
1055
1061
  };
1056
1062
  }
1057
1063
  const html = await response.text();
@@ -1064,7 +1070,9 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
1064
1070
  requestCacheLife: responseCacheLife ?? processCacheLife,
1065
1071
  status: response.status,
1066
1072
  tags: cacheTags,
1067
- fatal: false
1073
+ fatal: false,
1074
+ specialErrorLocation: specialError?.location ?? null,
1075
+ isSpecialError
1068
1076
  };
1069
1077
  });
1070
1078
  const htmlCacheControl = htmlRender.cacheControl;
@@ -1092,7 +1100,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
1092
1100
  error: "RSC handler returned no prerender HTML"
1093
1101
  };
1094
1102
  const html = isFallback ? markAppPprDynamicFallbackShellHtml(htmlRender.html) : htmlRender.html;
1095
- let rscData = extractRscPayloadFromPrerenderedHtml(html);
1103
+ let rscData = htmlRender.isSpecialError ? null : extractRscPayloadFromPrerenderedHtml(html);
1096
1104
  if (rscData === null) {
1097
1105
  const rscHeaders = new Headers({
1098
1106
  Accept: "text/x-component",
@@ -1100,7 +1108,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
1100
1108
  });
1101
1109
  if (prerenderRouteParamsHeader !== null) rscHeaders.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);
1102
1110
  if (isSpeculative) rscHeaders.set(VINEXT_PRERENDER_SPECULATIVE_HEADER, "1");
1103
- const rscRequest = new Request(`http://localhost${requestPath}`, { headers: rscHeaders });
1111
+ const rscRequest = new Request(`http://localhost${await createRscRequestUrl(requestPath, rscHeaders)}`, { headers: rscHeaders });
1104
1112
  const rscRes = await runWithHeadersContext(headersContextFromRequest(rscRequest), () => rscHandler(rscRequest));
1105
1113
  if (!rscRes.ok) {
1106
1114
  await rscRes.body?.cancel();
@@ -1135,7 +1143,11 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
1135
1143
  ...renderedStale === void 0 ? {} : { stale: renderedStale },
1136
1144
  router: "app",
1137
1145
  ...htmlRender.tags.length > 0 ? { tags: htmlRender.tags } : {},
1138
- ...htmlRender.linkHeader ? { headers: { link: htmlRender.linkHeader } } : {},
1146
+ ...htmlRender.linkHeader || htmlRender.specialErrorLocation !== null ? { headers: {
1147
+ ...htmlRender.linkHeader ? { link: htmlRender.linkHeader } : {},
1148
+ ...htmlRender.specialErrorLocation !== null ? { location: htmlRender.specialErrorLocation } : {}
1149
+ } } : {},
1150
+ ...htmlRender.isSpecialError ? { responseStatus: htmlRender.status } : {},
1139
1151
  ...urlPath !== routePattern ? { path: urlPath } : {},
1140
1152
  ...isFallback ? { fallback: true } : {}
1141
1153
  };
@@ -1239,6 +1251,19 @@ function readPrerenderCacheLifeHeader(headers) {
1239
1251
  return null;
1240
1252
  }
1241
1253
  }
1254
+ /** Reads the special-error marker: the page's stored headers, or null. */
1255
+ function readPrerenderSpecialErrorHeader(headers) {
1256
+ const value = headers.get(VINEXT_PRERENDER_SPECIAL_ERROR_HEADER);
1257
+ if (!value) return null;
1258
+ try {
1259
+ const parsed = JSON.parse(value);
1260
+ if (typeof parsed !== "object" || parsed === null) return null;
1261
+ const location = parsed.location;
1262
+ return { location: typeof location === "string" ? location : null };
1263
+ } catch {
1264
+ return null;
1265
+ }
1266
+ }
1242
1267
  function readPrerenderCacheTagsHeader(headers) {
1243
1268
  const value = headers.get(NEXT_CACHE_TAGS_HEADER);
1244
1269
  if (!value) return [];
@@ -124,7 +124,7 @@ async function runPrerender(options) {
124
124
  let totalUrls = 0;
125
125
  let completedUrls = 0;
126
126
  const progress = new PrerenderProgress();
127
- const outDir = mode === "export" ? path.join(root, "dist", "client") : path.join(manifestDir, "prerendered-routes");
127
+ const outDir = mode === "export" ? path.resolve(root, options.routeRootConfig?.clientOutDir ?? path.join("dist", "client")) : path.join(manifestDir, "prerendered-routes");
128
128
  let sharedProdServer = null;
129
129
  let sharedPrerenderSecret;
130
130
  const restorePrerenderPhase = enterPrerenderPhase();
@@ -14,6 +14,13 @@
14
14
  * warn when this returns `undefined`.
15
15
  */
16
16
  export declare function readPrerenderSecret(serverDir: string): string | undefined;
17
+ /**
18
+ * Read next.config `compress` from `vinext-server.json` in `serverDir`.
19
+ *
20
+ * Like `next start`, compression stays on unless the build recorded an
21
+ * explicit `compress: false`, so builds without the field keep compressing.
22
+ */
23
+ export declare function readServerCompress(serverDir: string): boolean;
17
24
  /**
18
25
  * Read every server output root that contains a deployable response-stage
19
26
  * graph. Paths are stored relative to the project root so build artifacts stay
@@ -20,6 +20,16 @@ function readPrerenderSecret(serverDir) {
20
20
  return readJsonFile(manifestPath)?.prerenderSecret;
21
21
  }
22
22
  /**
23
+ * Read next.config `compress` from `vinext-server.json` in `serverDir`.
24
+ *
25
+ * Like `next start`, compression stays on unless the build recorded an
26
+ * explicit `compress: false`, so builds without the field keep compressing.
27
+ */
28
+ function readServerCompress(serverDir) {
29
+ const manifestPath = path.join(serverDir, "vinext-server.json");
30
+ return readJsonFile(manifestPath)?.compress !== false;
31
+ }
32
+ /**
23
33
  * Read every server output root that contains a deployable response-stage
24
34
  * graph. Paths are stored relative to the project root so build artifacts stay
25
35
  * relocatable, then resolved for post-build sidecar updates.
@@ -31,4 +41,4 @@ function readServerRuntimeOutputDirs(serverDir, root) {
31
41
  return manifest.runtimeOutputDirs.filter((outputDir) => typeof outputDir === "string").map((outputDir) => path.resolve(root, outputDir));
32
42
  }
33
43
  //#endregion
34
- export { readPrerenderSecret, readServerRuntimeOutputDirs };
44
+ export { readPrerenderSecret, readServerCompress, readServerRuntimeOutputDirs };