vinext 0.0.46 → 0.0.48

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 (351) hide show
  1. package/README.md +8 -6
  2. package/dist/build/layout-classification.js +3 -1
  3. package/dist/build/layout-classification.js.map +1 -1
  4. package/dist/build/prerender.d.ts +2 -1
  5. package/dist/build/prerender.js +80 -24
  6. package/dist/build/prerender.js.map +1 -1
  7. package/dist/build/report.d.ts +9 -5
  8. package/dist/build/report.js +17 -7
  9. package/dist/build/report.js.map +1 -1
  10. package/dist/build/route-classification-injector.d.ts +35 -0
  11. package/dist/build/route-classification-injector.js +61 -0
  12. package/dist/build/route-classification-injector.js.map +1 -0
  13. package/dist/build/route-classification-manifest.d.ts +1 -1
  14. package/dist/build/run-prerender.d.ts +5 -0
  15. package/dist/build/run-prerender.js +4 -1
  16. package/dist/build/run-prerender.js.map +1 -1
  17. package/dist/build/server-manifest.js +2 -7
  18. package/dist/build/server-manifest.js.map +1 -1
  19. package/dist/build/standalone.js +3 -5
  20. package/dist/build/standalone.js.map +1 -1
  21. package/dist/build/static-export.d.ts +1 -1
  22. package/dist/check.js +45 -29
  23. package/dist/check.js.map +1 -1
  24. package/dist/cli-args.d.ts +33 -0
  25. package/dist/cli-args.js +121 -0
  26. package/dist/cli-args.js.map +1 -0
  27. package/dist/cli.js +11 -20
  28. package/dist/cli.js.map +1 -1
  29. package/dist/cloudflare/kv-cache-handler.js +29 -9
  30. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  31. package/dist/config/config-matchers.js +46 -37
  32. package/dist/config/config-matchers.js.map +1 -1
  33. package/dist/config/next-config.d.ts +4 -2
  34. package/dist/config/next-config.js +3 -0
  35. package/dist/config/next-config.js.map +1 -1
  36. package/dist/deploy.d.ts +18 -2
  37. package/dist/deploy.js +47 -4
  38. package/dist/deploy.js.map +1 -1
  39. package/dist/entries/app-rsc-entry.d.ts +4 -3
  40. package/dist/entries/app-rsc-entry.js +379 -858
  41. package/dist/entries/app-rsc-entry.js.map +1 -1
  42. package/dist/entries/app-rsc-manifest.d.ts +1 -1
  43. package/dist/entries/app-rsc-manifest.js +6 -1
  44. package/dist/entries/app-rsc-manifest.js.map +1 -1
  45. package/dist/entries/pages-client-entry.js +3 -2
  46. package/dist/entries/pages-client-entry.js.map +1 -1
  47. package/dist/entries/pages-server-entry.js +19 -61
  48. package/dist/entries/pages-server-entry.js.map +1 -1
  49. package/dist/entries/runtime-entry-module.d.ts +12 -3
  50. package/dist/entries/runtime-entry-module.js +15 -4
  51. package/dist/entries/runtime-entry-module.js.map +1 -1
  52. package/dist/index.js +40 -58
  53. package/dist/index.js.map +1 -1
  54. package/dist/plugins/fonts.js +54 -32
  55. package/dist/plugins/fonts.js.map +1 -1
  56. package/dist/plugins/og-assets.js +15 -16
  57. package/dist/plugins/og-assets.js.map +1 -1
  58. package/dist/plugins/rsc-client-shim-excludes.d.ts +2 -1
  59. package/dist/plugins/rsc-client-shim-excludes.js +11 -1
  60. package/dist/plugins/rsc-client-shim-excludes.js.map +1 -1
  61. package/dist/routing/app-route-graph.d.ts +195 -0
  62. package/dist/routing/app-route-graph.js +1022 -0
  63. package/dist/routing/app-route-graph.js.map +1 -0
  64. package/dist/routing/app-router.d.ts +14 -88
  65. package/dist/routing/app-router.js +21 -712
  66. package/dist/routing/app-router.js.map +1 -1
  67. package/dist/routing/file-matcher.d.ts +3 -1
  68. package/dist/routing/file-matcher.js +6 -1
  69. package/dist/routing/file-matcher.js.map +1 -1
  70. package/dist/routing/pages-router.js +10 -19
  71. package/dist/routing/pages-router.js.map +1 -1
  72. package/dist/routing/route-matching.d.ts +28 -0
  73. package/dist/routing/route-matching.js +44 -0
  74. package/dist/routing/route-matching.js.map +1 -0
  75. package/dist/routing/route-pattern.js +4 -1
  76. package/dist/routing/route-pattern.js.map +1 -1
  77. package/dist/routing/route-trie.d.ts +8 -0
  78. package/dist/routing/route-trie.js +12 -1
  79. package/dist/routing/route-trie.js.map +1 -1
  80. package/dist/routing/route-validation.js +3 -4
  81. package/dist/routing/route-validation.js.map +1 -1
  82. package/dist/routing/utils.d.ts +8 -1
  83. package/dist/routing/utils.js +25 -2
  84. package/dist/routing/utils.js.map +1 -1
  85. package/dist/server/app-browser-entry.js +145 -294
  86. package/dist/server/app-browser-entry.js.map +1 -1
  87. package/dist/server/app-browser-error.d.ts +3 -4
  88. package/dist/server/app-browser-error.js +8 -4
  89. package/dist/server/app-browser-error.js.map +1 -1
  90. package/dist/server/app-browser-navigation-controller.d.ts +75 -0
  91. package/dist/server/app-browser-navigation-controller.js +290 -0
  92. package/dist/server/app-browser-navigation-controller.js.map +1 -0
  93. package/dist/server/app-browser-state.d.ts +33 -15
  94. package/dist/server/app-browser-state.js +52 -59
  95. package/dist/server/app-browser-state.js.map +1 -1
  96. package/dist/server/app-browser-visible-commit.d.ts +68 -0
  97. package/dist/server/app-browser-visible-commit.js +182 -0
  98. package/dist/server/app-browser-visible-commit.js.map +1 -0
  99. package/dist/server/app-client-reference-preloader.d.ts +15 -0
  100. package/dist/server/app-client-reference-preloader.js +46 -0
  101. package/dist/server/app-client-reference-preloader.js.map +1 -0
  102. package/dist/server/app-elements-wire.d.ts +130 -0
  103. package/dist/server/app-elements-wire.js +205 -0
  104. package/dist/server/app-elements-wire.js.map +1 -0
  105. package/dist/server/app-elements.d.ts +2 -84
  106. package/dist/server/app-elements.js +4 -107
  107. package/dist/server/app-elements.js.map +1 -1
  108. package/dist/server/app-fallback-renderer.d.ts +57 -0
  109. package/dist/server/app-fallback-renderer.js +79 -0
  110. package/dist/server/app-fallback-renderer.js.map +1 -0
  111. package/dist/server/app-hook-warning-suppression.d.ts +7 -0
  112. package/dist/server/app-hook-warning-suppression.js +12 -0
  113. package/dist/server/app-hook-warning-suppression.js.map +1 -0
  114. package/dist/server/app-middleware.d.ts +2 -1
  115. package/dist/server/app-middleware.js +34 -11
  116. package/dist/server/app-middleware.js.map +1 -1
  117. package/dist/server/app-mounted-slots-header.d.ts +17 -0
  118. package/dist/server/app-mounted-slots-header.js +21 -0
  119. package/dist/server/app-mounted-slots-header.js.map +1 -0
  120. package/dist/server/app-page-boundary-render.d.ts +3 -3
  121. package/dist/server/app-page-boundary-render.js +8 -5
  122. package/dist/server/app-page-boundary-render.js.map +1 -1
  123. package/dist/server/app-page-boundary.js +2 -1
  124. package/dist/server/app-page-boundary.js.map +1 -1
  125. package/dist/server/app-page-cache.d.ts +19 -4
  126. package/dist/server/app-page-cache.js +60 -22
  127. package/dist/server/app-page-cache.js.map +1 -1
  128. package/dist/server/app-page-dispatch.d.ts +9 -5
  129. package/dist/server/app-page-dispatch.js +41 -17
  130. package/dist/server/app-page-dispatch.js.map +1 -1
  131. package/dist/server/app-page-element-builder.d.ts +61 -0
  132. package/dist/server/app-page-element-builder.js +142 -0
  133. package/dist/server/app-page-element-builder.js.map +1 -0
  134. package/dist/server/app-page-execution.d.ts +23 -5
  135. package/dist/server/app-page-execution.js +39 -24
  136. package/dist/server/app-page-execution.js.map +1 -1
  137. package/dist/server/app-page-head.js +2 -1
  138. package/dist/server/app-page-head.js.map +1 -1
  139. package/dist/server/app-page-method.js +2 -5
  140. package/dist/server/app-page-method.js.map +1 -1
  141. package/dist/server/app-page-params.d.ts +2 -1
  142. package/dist/server/app-page-params.js +3 -3
  143. package/dist/server/app-page-params.js.map +1 -1
  144. package/dist/server/app-page-probe.d.ts +1 -1
  145. package/dist/server/app-page-probe.js +5 -1
  146. package/dist/server/app-page-probe.js.map +1 -1
  147. package/dist/server/app-page-render.d.ts +6 -2
  148. package/dist/server/app-page-render.js +118 -30
  149. package/dist/server/app-page-render.js.map +1 -1
  150. package/dist/server/app-page-request.d.ts +19 -5
  151. package/dist/server/app-page-request.js +49 -7
  152. package/dist/server/app-page-request.js.map +1 -1
  153. package/dist/server/app-page-response.d.ts +1 -0
  154. package/dist/server/app-page-response.js +6 -9
  155. package/dist/server/app-page-response.js.map +1 -1
  156. package/dist/server/app-page-route-wiring.d.ts +20 -4
  157. package/dist/server/app-page-route-wiring.js +15 -12
  158. package/dist/server/app-page-route-wiring.js.map +1 -1
  159. package/dist/server/app-page-stream.d.ts +7 -0
  160. package/dist/server/app-page-stream.js +9 -2
  161. package/dist/server/app-page-stream.js.map +1 -1
  162. package/dist/server/app-post-middleware-context.d.ts +16 -0
  163. package/dist/server/app-post-middleware-context.js +28 -0
  164. package/dist/server/app-post-middleware-context.js.map +1 -0
  165. package/dist/server/app-prerender-endpoints.js +3 -2
  166. package/dist/server/app-prerender-endpoints.js.map +1 -1
  167. package/dist/server/app-request-context.d.ts +22 -0
  168. package/dist/server/app-request-context.js +30 -0
  169. package/dist/server/app-request-context.js.map +1 -0
  170. package/dist/server/app-route-handler-cache.d.ts +1 -0
  171. package/dist/server/app-route-handler-cache.js +7 -2
  172. package/dist/server/app-route-handler-cache.js.map +1 -1
  173. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  174. package/dist/server/app-route-handler-dispatch.js +8 -5
  175. package/dist/server/app-route-handler-dispatch.js.map +1 -1
  176. package/dist/server/app-route-handler-execution.d.ts +2 -1
  177. package/dist/server/app-route-handler-execution.js +2 -2
  178. package/dist/server/app-route-handler-execution.js.map +1 -1
  179. package/dist/server/app-route-handler-policy.js +13 -13
  180. package/dist/server/app-route-handler-policy.js.map +1 -1
  181. package/dist/server/app-route-handler-response.d.ts +4 -2
  182. package/dist/server/app-route-handler-response.js +9 -7
  183. package/dist/server/app-route-handler-response.js.map +1 -1
  184. package/dist/server/app-route-handler-runtime.d.ts +9 -1
  185. package/dist/server/app-route-handler-runtime.js +11 -1
  186. package/dist/server/app-route-handler-runtime.js.map +1 -1
  187. package/dist/server/app-router-entry.js +9 -4
  188. package/dist/server/app-router-entry.js.map +1 -1
  189. package/dist/server/app-rsc-cache-busting.d.ts +34 -0
  190. package/dist/server/app-rsc-cache-busting.js +137 -0
  191. package/dist/server/app-rsc-cache-busting.js.map +1 -0
  192. package/dist/server/app-rsc-error-handler.d.ts +21 -0
  193. package/dist/server/app-rsc-error-handler.js +30 -0
  194. package/dist/server/app-rsc-error-handler.js.map +1 -0
  195. package/dist/server/app-rsc-handler.d.ts +117 -0
  196. package/dist/server/app-rsc-handler.js +271 -0
  197. package/dist/server/app-rsc-handler.js.map +1 -0
  198. package/dist/server/app-rsc-request-normalization.d.ts +42 -0
  199. package/dist/server/app-rsc-request-normalization.js +67 -0
  200. package/dist/server/app-rsc-request-normalization.js.map +1 -0
  201. package/dist/server/app-rsc-response-finalizer.d.ts +30 -0
  202. package/dist/server/app-rsc-response-finalizer.js +38 -0
  203. package/dist/server/app-rsc-response-finalizer.js.map +1 -0
  204. package/dist/server/app-rsc-route-matching.js +8 -4
  205. package/dist/server/app-rsc-route-matching.js.map +1 -1
  206. package/dist/server/app-segment-config.d.ts +33 -0
  207. package/dist/server/app-segment-config.js +90 -0
  208. package/dist/server/app-segment-config.js.map +1 -0
  209. package/dist/server/app-server-action-execution.d.ts +2 -0
  210. package/dist/server/app-server-action-execution.js +45 -51
  211. package/dist/server/app-server-action-execution.js.map +1 -1
  212. package/dist/server/app-ssr-entry.js +21 -20
  213. package/dist/server/app-ssr-entry.js.map +1 -1
  214. package/dist/server/artifact-compatibility.d.ts +44 -0
  215. package/dist/server/artifact-compatibility.js +82 -0
  216. package/dist/server/artifact-compatibility.js.map +1 -0
  217. package/dist/server/cache-control.d.ts +24 -0
  218. package/dist/server/cache-control.js +33 -0
  219. package/dist/server/cache-control.js.map +1 -0
  220. package/dist/server/cache-proof.d.ts +200 -0
  221. package/dist/server/cache-proof.js +342 -0
  222. package/dist/server/cache-proof.js.map +1 -0
  223. package/dist/server/dev-error-overlay-store.d.ts +23 -0
  224. package/dist/server/dev-error-overlay-store.js +67 -0
  225. package/dist/server/dev-error-overlay-store.js.map +1 -0
  226. package/dist/server/dev-error-overlay.d.ts +15 -0
  227. package/dist/server/dev-error-overlay.js +548 -0
  228. package/dist/server/dev-error-overlay.js.map +1 -0
  229. package/dist/server/dev-origin-check.js +8 -4
  230. package/dist/server/dev-origin-check.js.map +1 -1
  231. package/dist/server/dev-server.js +1 -6
  232. package/dist/server/dev-server.js.map +1 -1
  233. package/dist/server/http-error-responses.d.ts +67 -0
  234. package/dist/server/http-error-responses.js +77 -0
  235. package/dist/server/http-error-responses.js.map +1 -0
  236. package/dist/server/image-optimization.js +2 -1
  237. package/dist/server/image-optimization.js.map +1 -1
  238. package/dist/server/instrumentation-runtime.d.ts +44 -0
  239. package/dist/server/instrumentation-runtime.js +29 -0
  240. package/dist/server/instrumentation-runtime.js.map +1 -0
  241. package/dist/server/isr-cache.d.ts +2 -7
  242. package/dist/server/isr-cache.js +7 -10
  243. package/dist/server/isr-cache.js.map +1 -1
  244. package/dist/server/metadata-route-response.js +6 -5
  245. package/dist/server/metadata-route-response.js.map +1 -1
  246. package/dist/server/metadata-routes.d.ts +1 -0
  247. package/dist/server/metadata-routes.js +6 -0
  248. package/dist/server/metadata-routes.js.map +1 -1
  249. package/dist/server/middleware-matcher.js +2 -2
  250. package/dist/server/middleware-matcher.js.map +1 -1
  251. package/dist/server/middleware-response-headers.js +21 -0
  252. package/dist/server/middleware-response-headers.js.map +1 -1
  253. package/dist/server/middleware-runtime.js +3 -3
  254. package/dist/server/middleware-runtime.js.map +1 -1
  255. package/dist/server/navigation-trace.d.ts +33 -0
  256. package/dist/server/navigation-trace.js +35 -0
  257. package/dist/server/navigation-trace.js.map +1 -0
  258. package/dist/server/next-error-digest.d.ts +44 -0
  259. package/dist/server/next-error-digest.js +40 -0
  260. package/dist/server/next-error-digest.js.map +1 -0
  261. package/dist/server/pages-api-route.js +2 -1
  262. package/dist/server/pages-api-route.js.map +1 -1
  263. package/dist/server/pages-node-compat.js +4 -16
  264. package/dist/server/pages-node-compat.js.map +1 -1
  265. package/dist/server/pages-page-data.d.ts +2 -1
  266. package/dist/server/pages-page-data.js +6 -5
  267. package/dist/server/pages-page-data.js.map +1 -1
  268. package/dist/server/pages-page-response.d.ts +3 -8
  269. package/dist/server/pages-page-response.js +46 -15
  270. package/dist/server/pages-page-response.js.map +1 -1
  271. package/dist/server/prod-server.d.ts +6 -0
  272. package/dist/server/prod-server.js +28 -21
  273. package/dist/server/prod-server.js.map +1 -1
  274. package/dist/server/request-pipeline.d.ts +42 -1
  275. package/dist/server/request-pipeline.js +97 -17
  276. package/dist/server/request-pipeline.js.map +1 -1
  277. package/dist/server/rsc-stream-hints.d.ts +3 -1
  278. package/dist/server/rsc-stream-hints.js +4 -1
  279. package/dist/server/rsc-stream-hints.js.map +1 -1
  280. package/dist/server/seed-cache.js +19 -8
  281. package/dist/server/seed-cache.js.map +1 -1
  282. package/dist/shims/cache-runtime.d.ts +2 -2
  283. package/dist/shims/cache-runtime.js +31 -17
  284. package/dist/shims/cache-runtime.js.map +1 -1
  285. package/dist/shims/cache.d.ts +15 -3
  286. package/dist/shims/cache.js +45 -20
  287. package/dist/shims/cache.js.map +1 -1
  288. package/dist/shims/error-boundary.d.ts +17 -1
  289. package/dist/shims/error-boundary.js +31 -1
  290. package/dist/shims/error-boundary.js.map +1 -1
  291. package/dist/shims/fetch-cache.d.ts +4 -1
  292. package/dist/shims/fetch-cache.js +57 -16
  293. package/dist/shims/fetch-cache.js.map +1 -1
  294. package/dist/shims/head-state.js +2 -3
  295. package/dist/shims/head-state.js.map +1 -1
  296. package/dist/shims/headers.js +4 -44
  297. package/dist/shims/headers.js.map +1 -1
  298. package/dist/shims/i18n-state.js +2 -3
  299. package/dist/shims/i18n-state.js.map +1 -1
  300. package/dist/shims/image.js +93 -5
  301. package/dist/shims/image.js.map +1 -1
  302. package/dist/shims/internal/als-registry.d.ts +15 -0
  303. package/dist/shims/internal/als-registry.js +55 -0
  304. package/dist/shims/internal/als-registry.js.map +1 -0
  305. package/dist/shims/internal/cookie-serialize.d.ts +46 -0
  306. package/dist/shims/internal/cookie-serialize.js +51 -0
  307. package/dist/shims/internal/cookie-serialize.js.map +1 -0
  308. package/dist/shims/link.js +31 -26
  309. package/dist/shims/link.js.map +1 -1
  310. package/dist/shims/metadata.d.ts +26 -1
  311. package/dist/shims/metadata.js +94 -4
  312. package/dist/shims/metadata.js.map +1 -1
  313. package/dist/shims/navigation-state.js +2 -3
  314. package/dist/shims/navigation-state.js.map +1 -1
  315. package/dist/shims/navigation.d.ts +2 -7
  316. package/dist/shims/navigation.js +44 -36
  317. package/dist/shims/navigation.js.map +1 -1
  318. package/dist/shims/request-context.js +2 -4
  319. package/dist/shims/request-context.js.map +1 -1
  320. package/dist/shims/request-state-types.d.ts +1 -1
  321. package/dist/shims/router-state.js +2 -3
  322. package/dist/shims/router-state.js.map +1 -1
  323. package/dist/shims/router.js +2 -2
  324. package/dist/shims/router.js.map +1 -1
  325. package/dist/shims/server.js +5 -30
  326. package/dist/shims/server.js.map +1 -1
  327. package/dist/shims/slot.d.ts +1 -1
  328. package/dist/shims/slot.js +5 -4
  329. package/dist/shims/slot.js.map +1 -1
  330. package/dist/shims/thenable-params.d.ts +5 -2
  331. package/dist/shims/thenable-params.js +26 -6
  332. package/dist/shims/thenable-params.js.map +1 -1
  333. package/dist/shims/unified-request-context.d.ts +1 -1
  334. package/dist/shims/unified-request-context.js +3 -14
  335. package/dist/shims/unified-request-context.js.map +1 -1
  336. package/dist/shims/use-merged-ref.d.ts +7 -0
  337. package/dist/shims/use-merged-ref.js +40 -0
  338. package/dist/shims/use-merged-ref.js.map +1 -0
  339. package/dist/utils/base-path.d.ts +7 -1
  340. package/dist/utils/base-path.js +12 -1
  341. package/dist/utils/base-path.js.map +1 -1
  342. package/dist/utils/cache-control-metadata.d.ts +6 -0
  343. package/dist/utils/cache-control-metadata.js +16 -0
  344. package/dist/utils/cache-control-metadata.js.map +1 -0
  345. package/dist/utils/safe-json-file.d.ts +18 -0
  346. package/dist/utils/safe-json-file.js +25 -0
  347. package/dist/utils/safe-json-file.js.map +1 -0
  348. package/dist/utils/text-stream.d.ts +29 -0
  349. package/dist/utils/text-stream.js +66 -0
  350. package/dist/utils/text-stream.js.map +1 -0
  351. package/package.json +5 -5
package/README.md CHANGED
@@ -4,7 +4,7 @@ The Next.js API surface, reimplemented on Vite.
4
4
 
5
5
  > **Read the announcement:** [How we rebuilt Next.js with AI in one week](https://blog.cloudflare.com/vinext/)
6
6
 
7
- > 🚧 **Experimental — under heavy development.** This project is an experiment in AI-driven software development. The vast majority of the code, tests, and documentation were written by AI (Claude Code). Humans direct architecture, priorities, and design decisions, but have not reviewed most of the code line-by-line. Treat this accordingly — there will be bugs, rough edges, and things that don't work. Use at your own risk.
7
+ > 🚧 **Experimental — under heavy development.** This project is an experiment in AI-driven software development. The vast majority of the code, tests, and documentation are written by AI, with humans steering throughout: setting architecture and priorities, making design decisions, reviewing changes, triaging complex problems, and shipping fixes. There may be bugs, rough edges, or things that don't work. Use at your own risk.
8
8
 
9
9
  ## Quick start
10
10
 
@@ -124,7 +124,7 @@ Use `--force` to overwrite an existing `vite.config.ts`, or `--skip-check` to sk
124
124
 
125
125
  Vite has become the default build tool for modern web frameworks — fast HMR, a clean plugin API, native ESM, and a growing ecosystem. With [`@vitejs/plugin-rsc`](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc) adding React Server Components support, it's now possible to build a full RSC framework on Vite.
126
126
 
127
- 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 — about 94% of the API surface works.
127
+ 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
128
 
129
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).
130
130
 
@@ -161,7 +161,7 @@ Yes. Next.js supports [self-hosting](https://nextjs.org/docs/app/building-your-a
161
161
  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.
162
162
 
163
163
  **Who is reviewing this code?**
164
- Mostly nobody. This is an experiment in seeing how far AI-driven development can go. The test suite is the primary quality gate not human code review. Contributions and code review are welcome.
164
+ 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.
165
165
 
166
166
  **Why Vite?**
167
167
  Vite is an excellent build tool with a rich plugin ecosystem, first-class ESM support, and fast HMR. The [`@vitejs/plugin-rsc`](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc) plugin adds React Server Components support with multi-environment builds. This project is an experiment to see how much of the Next.js developer experience can be replicated on top of Vite's infrastructure.
@@ -544,7 +544,7 @@ The `CacheHandler` interface matches Next.js 16's shape, so community adapters s
544
544
 
545
545
  ## What's NOT supported (and won't be)
546
546
 
547
- These are intentional exclusions:
547
+ These are intentional exclusions. For things that are missing today but on the roadmap, see [Known limitations](#known-limitations) below.
548
548
 
549
549
  - **Vercel-specific features** — `@vercel/og` edge runtime, Vercel Analytics integration, Vercel KV/Blob/Postgres bindings. Use platform equivalents.
550
550
  - **AMP** — Deprecated since Next.js 13. `useAmp()` returns `false`.
@@ -556,6 +556,8 @@ These are intentional exclusions:
556
556
 
557
557
  ## Known limitations
558
558
 
559
+ These are gaps we'd like to close — distinct from the [intentional exclusions](#whats-not-supported-and-wont-be) above.
560
+
559
561
  - **Image optimization doesn't happen at build time.** Remote images work via `@unpic/react` (auto-detects 28 CDN providers). Local images are routed through a `/_vinext/image` endpoint that can resize and transcode on Cloudflare Workers (via the Images binding) in production, but no build-time optimization or static resizing occurs.
560
562
  - **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.
561
563
  - **Route segment config** — `runtime` and `preferredRegion` are ignored (everything runs in the same environment).
@@ -693,7 +695,7 @@ pnpm install
693
695
  pnpm run build
694
696
  ```
695
697
 
696
- This builds the vinext package to `packages/vinext/dist/` via `vp pack`. For active development, use `pnpm --filter vinext run dev` to run `vp pack --watch`.
698
+ This builds the vinext package to `packages/vinext/dist/`. For active development, use `pnpm --filter vinext run dev` to rebuild on changes.
697
699
 
698
700
  To use it against an external Next.js app, link the built package:
699
701
 
@@ -712,7 +714,7 @@ Or add it to your `package.json` as a file dependency:
712
714
  }
713
715
  ```
714
716
 
715
- vinext has peer dependencies on `react ^19.2.5`, `react-dom ^19.2.5`, `react-server-dom-webpack ^19.2.5`, and `vite ^7.0.0 || ^8.0.0`. Then replace `next` with `vinext` in your scripts and run as normal.
717
+ vinext has peer dependencies on `react ^19.2.6`, `react-dom ^19.2.6`, `react-server-dom-webpack ^19.2.6`, and `vite ^7.0.0 || ^8.0.0`. Then replace `next` with `vinext` in your scripts and run as normal.
716
718
 
717
719
  ## Contributing
718
720
 
@@ -1,3 +1,5 @@
1
+ import { AppElementsWire } from "../server/app-elements-wire.js";
2
+ import "../server/app-elements.js";
1
3
  import { classifyLayoutSegmentConfig } from "./report.js";
2
4
  import { createAppPageTreePath } from "../server/app-page-route-wiring.js";
3
5
  //#region src/build/layout-classification.ts
@@ -74,7 +76,7 @@ function isStaticModuleGraphResult(graphResult) {
74
76
  function classifyAllRouteLayouts(routes, dynamicShimPaths, moduleInfo) {
75
77
  const result = /* @__PURE__ */ new Map();
76
78
  for (const route of routes) for (const layout of route.layouts) {
77
- const layoutId = `layout:${createAppPageTreePath(route.routeSegments, layout.treePosition)}`;
79
+ const layoutId = AppElementsWire.encodeLayoutId(createAppPageTreePath(route.routeSegments, layout.treePosition));
78
80
  if (result.has(layoutId)) continue;
79
81
  if (layout.segmentConfig) {
80
82
  const configResult = classifyLayoutSegmentConfig(layout.segmentConfig.code);
@@ -1 +1 @@
1
- {"version":3,"file":"layout-classification.js","names":[],"sources":["../../src/build/layout-classification.ts"],"sourcesContent":["/**\n * Layout classification — determines whether each layout in an App Router\n * route tree is static or dynamic via two complementary detection layers:\n *\n * Layer 1: Segment config (`export const dynamic`, `export const revalidate`)\n * Layer 2: Module graph traversal (checks for transitive dynamic shim imports)\n *\n * Layer 3 (probe-based runtime detection) is handled separately in\n * `app-page-execution.ts` at request time.\n *\n * Every result is carried as a `LayoutBuildClassification` tagged variant so\n * operators can trace which layer produced a decision via the structured\n * `ClassificationReason` sidecar without that metadata leaking onto the wire.\n */\n\nimport { classifyLayoutSegmentConfig } from \"./report.js\";\nimport { createAppPageTreePath } from \"../server/app-page-route-wiring.js\";\nimport type {\n ClassificationReason,\n LayoutBuildClassification,\n ModuleGraphStaticReason,\n} from \"./layout-classification-types.js\";\n\ntype ModuleGraphClassification = \"static\" | \"needs-probe\";\n\ntype ModuleGraphClassificationResult = {\n result: ModuleGraphClassification;\n /** First dynamic shim module ID encountered during BFS, when any. */\n firstShimMatch?: string;\n};\n\nexport type ModuleInfoProvider = {\n getModuleInfo(id: string): {\n importedIds: string[];\n dynamicImportedIds: string[];\n } | null;\n};\n\ntype LayoutEntry = {\n /** Rollup/Vite module ID for the layout file. */\n moduleId: string;\n /** Directory depth from the app root, used to build the stable layout ID. */\n treePosition: number;\n /** Segment config source code extracted at build time, or null when absent. */\n segmentConfig?: { code: string } | null;\n};\n\ntype RouteForClassification = {\n layouts: readonly LayoutEntry[];\n routeSegments: string[];\n};\n\n/**\n * BFS traversal of a layout's dependency tree. If any transitive import\n * resolves to a dynamic shim path (headers, cache, server), the layout\n * cannot be proven static at build time and needs a runtime probe.\n *\n * The returned object carries the classification plus the first matching\n * shim module ID (when any). Operators use the shim ID via the debug\n * channel to trace why a layout was flagged for probing.\n */\nexport function classifyLayoutByModuleGraph(\n layoutModuleId: string,\n dynamicShimPaths: ReadonlySet<string>,\n moduleInfo: ModuleInfoProvider,\n): ModuleGraphClassificationResult {\n const visited = new Set<string>();\n const queue: string[] = [layoutModuleId];\n let head = 0;\n\n while (head < queue.length) {\n const currentId = queue[head++]!;\n\n if (visited.has(currentId)) continue;\n visited.add(currentId);\n\n if (dynamicShimPaths.has(currentId)) {\n return { result: \"needs-probe\", firstShimMatch: currentId };\n }\n\n const info = moduleInfo.getModuleInfo(currentId);\n if (!info) continue;\n\n for (const importedId of info.importedIds) {\n if (!visited.has(importedId)) queue.push(importedId);\n }\n for (const dynamicId of info.dynamicImportedIds) {\n if (!visited.has(dynamicId)) queue.push(dynamicId);\n }\n }\n\n return { result: \"static\" };\n}\n\nexport function moduleGraphReason(\n graphResult: ModuleGraphClassificationResult & { result: \"static\" },\n): ModuleGraphStaticReason;\nexport function moduleGraphReason(\n graphResult: ModuleGraphClassificationResult,\n): ClassificationReason;\nexport function moduleGraphReason(\n graphResult: ModuleGraphClassificationResult,\n): ClassificationReason {\n if (graphResult.firstShimMatch === undefined) {\n return { layer: \"module-graph\", result: graphResult.result };\n }\n return {\n layer: \"module-graph\",\n result: graphResult.result,\n firstShimMatch: graphResult.firstShimMatch,\n };\n}\n\nexport function isStaticModuleGraphResult(\n graphResult: ModuleGraphClassificationResult,\n): graphResult is ModuleGraphClassificationResult & { result: \"static\" } {\n return graphResult.result === \"static\";\n}\n\n/**\n * Classifies all layouts across all routes using a two-layer strategy:\n *\n * 1. Segment config (Layer 1) — short-circuits to \"static\" or \"dynamic\"\n * 2. Module graph (Layer 2) — BFS for dynamic shim imports → \"static\" or \"needs-probe\"\n *\n * Shared layouts (same file appearing in multiple routes) are classified once\n * and deduplicated by layout ID.\n *\n * @internal Not called by production code. The `generateBundle` hook in\n * `index.ts` calls `classifyLayoutByModuleGraph` directly and composes\n * via the numeric-index manifest in `route-classification-manifest.ts`.\n * Used only by `tests/layout-classification.test.ts`.\n */\nexport function classifyAllRouteLayouts(\n routes: readonly RouteForClassification[],\n dynamicShimPaths: ReadonlySet<string>,\n moduleInfo: ModuleInfoProvider,\n): Map<string, LayoutBuildClassification> {\n const result = new Map<string, LayoutBuildClassification>();\n\n for (const route of routes) {\n for (const layout of route.layouts) {\n const layoutId = `layout:${createAppPageTreePath(route.routeSegments, layout.treePosition)}`;\n\n if (result.has(layoutId)) continue;\n\n // Layer 1: segment config\n if (layout.segmentConfig) {\n const configResult = classifyLayoutSegmentConfig(layout.segmentConfig.code);\n if (configResult.kind !== \"absent\") {\n result.set(layoutId, configResult);\n continue;\n }\n }\n\n // Layer 2: module graph\n const graphResult = classifyLayoutByModuleGraph(\n layout.moduleId,\n dynamicShimPaths,\n moduleInfo,\n );\n const reason = moduleGraphReason(graphResult);\n result.set(layoutId, { kind: graphResult.result, reason });\n }\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,SAAgB,4BACd,gBACA,kBACA,YACiC;CACjC,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,QAAkB,CAAC,eAAe;CACxC,IAAI,OAAO;AAEX,QAAO,OAAO,MAAM,QAAQ;EAC1B,MAAM,YAAY,MAAM;AAExB,MAAI,QAAQ,IAAI,UAAU,CAAE;AAC5B,UAAQ,IAAI,UAAU;AAEtB,MAAI,iBAAiB,IAAI,UAAU,CACjC,QAAO;GAAE,QAAQ;GAAe,gBAAgB;GAAW;EAG7D,MAAM,OAAO,WAAW,cAAc,UAAU;AAChD,MAAI,CAAC,KAAM;AAEX,OAAK,MAAM,cAAc,KAAK,YAC5B,KAAI,CAAC,QAAQ,IAAI,WAAW,CAAE,OAAM,KAAK,WAAW;AAEtD,OAAK,MAAM,aAAa,KAAK,mBAC3B,KAAI,CAAC,QAAQ,IAAI,UAAU,CAAE,OAAM,KAAK,UAAU;;AAItD,QAAO,EAAE,QAAQ,UAAU;;AAS7B,SAAgB,kBACd,aACsB;AACtB,KAAI,YAAY,mBAAmB,KAAA,EACjC,QAAO;EAAE,OAAO;EAAgB,QAAQ,YAAY;EAAQ;AAE9D,QAAO;EACL,OAAO;EACP,QAAQ,YAAY;EACpB,gBAAgB,YAAY;EAC7B;;AAGH,SAAgB,0BACd,aACuE;AACvE,QAAO,YAAY,WAAW;;;;;;;;;;;;;;;;AAiBhC,SAAgB,wBACd,QACA,kBACA,YACwC;CACxC,MAAM,yBAAS,IAAI,KAAwC;AAE3D,MAAK,MAAM,SAAS,OAClB,MAAK,MAAM,UAAU,MAAM,SAAS;EAClC,MAAM,WAAW,UAAU,sBAAsB,MAAM,eAAe,OAAO,aAAa;AAE1F,MAAI,OAAO,IAAI,SAAS,CAAE;AAG1B,MAAI,OAAO,eAAe;GACxB,MAAM,eAAe,4BAA4B,OAAO,cAAc,KAAK;AAC3E,OAAI,aAAa,SAAS,UAAU;AAClC,WAAO,IAAI,UAAU,aAAa;AAClC;;;EAKJ,MAAM,cAAc,4BAClB,OAAO,UACP,kBACA,WACD;EACD,MAAM,SAAS,kBAAkB,YAAY;AAC7C,SAAO,IAAI,UAAU;GAAE,MAAM,YAAY;GAAQ;GAAQ,CAAC;;AAI9D,QAAO"}
1
+ {"version":3,"file":"layout-classification.js","names":[],"sources":["../../src/build/layout-classification.ts"],"sourcesContent":["/**\n * Layout classification — determines whether each layout in an App Router\n * route tree is static or dynamic via two complementary detection layers:\n *\n * Layer 1: Segment config (`export const dynamic`, `export const revalidate`)\n * Layer 2: Module graph traversal (checks for transitive dynamic shim imports)\n *\n * Layer 3 (probe-based runtime detection) is handled separately in\n * `app-page-execution.ts` at request time.\n *\n * Every result is carried as a `LayoutBuildClassification` tagged variant so\n * operators can trace which layer produced a decision via the structured\n * `ClassificationReason` sidecar without that metadata leaking onto the wire.\n */\n\nimport { classifyLayoutSegmentConfig } from \"./report.js\";\nimport { AppElementsWire } from \"../server/app-elements.js\";\nimport { createAppPageTreePath } from \"../server/app-page-route-wiring.js\";\nimport type {\n ClassificationReason,\n LayoutBuildClassification,\n ModuleGraphStaticReason,\n} from \"./layout-classification-types.js\";\n\ntype ModuleGraphClassification = \"static\" | \"needs-probe\";\n\ntype ModuleGraphClassificationResult = {\n result: ModuleGraphClassification;\n /** First dynamic shim module ID encountered during BFS, when any. */\n firstShimMatch?: string;\n};\n\nexport type ModuleInfoProvider = {\n getModuleInfo(id: string): {\n importedIds: string[];\n dynamicImportedIds: string[];\n } | null;\n};\n\ntype LayoutEntry = {\n /** Rollup/Vite module ID for the layout file. */\n moduleId: string;\n /** Directory depth from the app root, used to build the stable layout ID. */\n treePosition: number;\n /** Segment config source code extracted at build time, or null when absent. */\n segmentConfig?: { code: string } | null;\n};\n\ntype RouteForClassification = {\n layouts: readonly LayoutEntry[];\n routeSegments: string[];\n};\n\n/**\n * BFS traversal of a layout's dependency tree. If any transitive import\n * resolves to a dynamic shim path (headers, cache, server), the layout\n * cannot be proven static at build time and needs a runtime probe.\n *\n * The returned object carries the classification plus the first matching\n * shim module ID (when any). Operators use the shim ID via the debug\n * channel to trace why a layout was flagged for probing.\n */\nexport function classifyLayoutByModuleGraph(\n layoutModuleId: string,\n dynamicShimPaths: ReadonlySet<string>,\n moduleInfo: ModuleInfoProvider,\n): ModuleGraphClassificationResult {\n const visited = new Set<string>();\n const queue: string[] = [layoutModuleId];\n let head = 0;\n\n while (head < queue.length) {\n const currentId = queue[head++]!;\n\n if (visited.has(currentId)) continue;\n visited.add(currentId);\n\n if (dynamicShimPaths.has(currentId)) {\n return { result: \"needs-probe\", firstShimMatch: currentId };\n }\n\n const info = moduleInfo.getModuleInfo(currentId);\n if (!info) continue;\n\n for (const importedId of info.importedIds) {\n if (!visited.has(importedId)) queue.push(importedId);\n }\n for (const dynamicId of info.dynamicImportedIds) {\n if (!visited.has(dynamicId)) queue.push(dynamicId);\n }\n }\n\n return { result: \"static\" };\n}\n\nexport function moduleGraphReason(\n graphResult: ModuleGraphClassificationResult & { result: \"static\" },\n): ModuleGraphStaticReason;\nexport function moduleGraphReason(\n graphResult: ModuleGraphClassificationResult,\n): ClassificationReason;\nexport function moduleGraphReason(\n graphResult: ModuleGraphClassificationResult,\n): ClassificationReason {\n if (graphResult.firstShimMatch === undefined) {\n return { layer: \"module-graph\", result: graphResult.result };\n }\n return {\n layer: \"module-graph\",\n result: graphResult.result,\n firstShimMatch: graphResult.firstShimMatch,\n };\n}\n\nexport function isStaticModuleGraphResult(\n graphResult: ModuleGraphClassificationResult,\n): graphResult is ModuleGraphClassificationResult & { result: \"static\" } {\n return graphResult.result === \"static\";\n}\n\n/**\n * Classifies all layouts across all routes using a two-layer strategy:\n *\n * 1. Segment config (Layer 1) — short-circuits to \"static\" or \"dynamic\"\n * 2. Module graph (Layer 2) — BFS for dynamic shim imports → \"static\" or \"needs-probe\"\n *\n * Shared layouts (same file appearing in multiple routes) are classified once\n * and deduplicated by layout ID.\n *\n * @internal Not called by production code. The `generateBundle` hook in\n * `index.ts` calls `classifyLayoutByModuleGraph` directly and composes\n * via the numeric-index manifest in `route-classification-manifest.ts`.\n * Used only by `tests/layout-classification.test.ts`.\n */\nexport function classifyAllRouteLayouts(\n routes: readonly RouteForClassification[],\n dynamicShimPaths: ReadonlySet<string>,\n moduleInfo: ModuleInfoProvider,\n): Map<string, LayoutBuildClassification> {\n const result = new Map<string, LayoutBuildClassification>();\n\n for (const route of routes) {\n for (const layout of route.layouts) {\n const layoutId = AppElementsWire.encodeLayoutId(\n createAppPageTreePath(route.routeSegments, layout.treePosition),\n );\n\n if (result.has(layoutId)) continue;\n\n // Layer 1: segment config\n if (layout.segmentConfig) {\n const configResult = classifyLayoutSegmentConfig(layout.segmentConfig.code);\n if (configResult.kind !== \"absent\") {\n result.set(layoutId, configResult);\n continue;\n }\n }\n\n // Layer 2: module graph\n const graphResult = classifyLayoutByModuleGraph(\n layout.moduleId,\n dynamicShimPaths,\n moduleInfo,\n );\n const reason = moduleGraphReason(graphResult);\n result.set(layoutId, { kind: graphResult.result, reason });\n }\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA,SAAgB,4BACd,gBACA,kBACA,YACiC;CACjC,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,QAAkB,CAAC,eAAe;CACxC,IAAI,OAAO;AAEX,QAAO,OAAO,MAAM,QAAQ;EAC1B,MAAM,YAAY,MAAM;AAExB,MAAI,QAAQ,IAAI,UAAU,CAAE;AAC5B,UAAQ,IAAI,UAAU;AAEtB,MAAI,iBAAiB,IAAI,UAAU,CACjC,QAAO;GAAE,QAAQ;GAAe,gBAAgB;GAAW;EAG7D,MAAM,OAAO,WAAW,cAAc,UAAU;AAChD,MAAI,CAAC,KAAM;AAEX,OAAK,MAAM,cAAc,KAAK,YAC5B,KAAI,CAAC,QAAQ,IAAI,WAAW,CAAE,OAAM,KAAK,WAAW;AAEtD,OAAK,MAAM,aAAa,KAAK,mBAC3B,KAAI,CAAC,QAAQ,IAAI,UAAU,CAAE,OAAM,KAAK,UAAU;;AAItD,QAAO,EAAE,QAAQ,UAAU;;AAS7B,SAAgB,kBACd,aACsB;AACtB,KAAI,YAAY,mBAAmB,KAAA,EACjC,QAAO;EAAE,OAAO;EAAgB,QAAQ,YAAY;EAAQ;AAE9D,QAAO;EACL,OAAO;EACP,QAAQ,YAAY;EACpB,gBAAgB,YAAY;EAC7B;;AAGH,SAAgB,0BACd,aACuE;AACvE,QAAO,YAAY,WAAW;;;;;;;;;;;;;;;;AAiBhC,SAAgB,wBACd,QACA,kBACA,YACwC;CACxC,MAAM,yBAAS,IAAI,KAAwC;AAE3D,MAAK,MAAM,SAAS,OAClB,MAAK,MAAM,UAAU,MAAM,SAAS;EAClC,MAAM,WAAW,gBAAgB,eAC/B,sBAAsB,MAAM,eAAe,OAAO,aAAa,CAChE;AAED,MAAI,OAAO,IAAI,SAAS,CAAE;AAG1B,MAAI,OAAO,eAAe;GACxB,MAAM,eAAe,4BAA4B,OAAO,cAAc,KAAK;AAC3E,OAAI,aAAa,SAAS,UAAU;AAClC,WAAO,IAAI,UAAU,aAAa;AAClC;;;EAKJ,MAAM,cAAc,4BAClB,OAAO,UACP,kBACA,WACD;EACD,MAAM,SAAS,kBAAkB,YAAY;AAC7C,SAAO,IAAI,UAAU;GAAE,MAAM,YAAY;GAAQ;GAAQ,CAAC;;AAI9D,QAAO"}
@@ -1,5 +1,5 @@
1
1
  import { Route } from "../routing/pages-router.js";
2
- import { AppRoute } from "../routing/app-router.js";
2
+ import { AppRoute } from "../routing/app-route-graph.js";
3
3
  import { ResolvedNextConfig } from "../config/next-config.js";
4
4
  import { readPrerenderSecret } from "./server-manifest.js";
5
5
  import { Server } from "node:http";
@@ -13,6 +13,7 @@ type PrerenderRouteResult = {
13
13
  status: "rendered";
14
14
  outputFiles: string[];
15
15
  revalidate: number | false;
16
+ expire?: number;
16
17
  /**
17
18
  * The concrete prerendered URL path, e.g. `/blog/hello-world`.
18
19
  * Only present when the route is dynamic and `path` differs from `route`.
@@ -1,7 +1,7 @@
1
- import { createValidFileMatcher } from "../routing/file-matcher.js";
1
+ import { createValidFileMatcher, findFileWithExtensions } from "../routing/file-matcher.js";
2
2
  import { headersContextFromRequest, runWithHeadersContext } from "../shims/headers.js";
3
- import { NoOpCacheHandler, getCacheHandler, setCacheHandler } from "../shims/cache.js";
4
- import { classifyAppRoute, classifyPagesRoute } from "./report.js";
3
+ import { NoOpCacheHandler, _consumeRequestScopedCacheLife, getCacheHandler, setCacheHandler } from "../shims/cache.js";
4
+ import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath } from "./report.js";
5
5
  import { readPrerenderSecret } from "./server-manifest.js";
6
6
  import { startProdServer } from "../server/prod-server.js";
7
7
  import fs from "node:fs";
@@ -49,9 +49,6 @@ async function runWithConcurrency(items, concurrency, fn) {
49
49
  await Promise.all(workers);
50
50
  return results;
51
51
  }
52
- function findFileWithExtensions(basePath, matcher) {
53
- return matcher.dottedExtensions.some((ext) => fs.existsSync(basePath + ext));
54
- }
55
52
  /**
56
53
  * Build a URL path from a route pattern and params.
57
54
  * "/posts/:id" + { id: "42" } → "/posts/42"
@@ -164,7 +161,8 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
164
161
  port: 0,
165
162
  host: "127.0.0.1",
166
163
  outDir: path.dirname(path.dirname(pagesBundlePath)),
167
- noCompression: true
164
+ noCompression: true,
165
+ purpose: "prerender"
168
166
  });
169
167
  ownedProdServerHandle = srv;
170
168
  return srv;
@@ -287,6 +285,7 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
287
285
  status: "rendered",
288
286
  outputFiles,
289
287
  revalidate,
288
+ ...typeof revalidate === "number" ? { expire: config.expireTime } : {},
290
289
  router: "pages",
291
290
  ...urlPath !== route.pattern ? { path: urlPath } : {}
292
291
  };
@@ -374,7 +373,8 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
374
373
  port: 0,
375
374
  host: "127.0.0.1",
376
375
  outDir: path.dirname(serverDir),
377
- noCompression: true
376
+ noCompression: true,
377
+ purpose: "prerender"
378
378
  });
379
379
  ownedProdServerHandle = srv;
380
380
  return srv;
@@ -423,7 +423,8 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
423
423
  const routeIndex = new Map(routes.map((r) => [r.pattern, r]));
424
424
  const urlsToRender = [];
425
425
  for (const route of routes) {
426
- if (route.routePath && !route.pagePath) {
426
+ const renderEntryPath = getAppRouteRenderEntryPath(route);
427
+ if (!renderEntryPath && route.routePath) {
427
428
  results.push({
428
429
  route: route.pattern,
429
430
  status: "skipped",
@@ -431,8 +432,8 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
431
432
  });
432
433
  continue;
433
434
  }
434
- if (!route.pagePath) continue;
435
- const { type, revalidate: classifiedRevalidate } = classifyAppRoute(route.pagePath, route.routePath, route.isDynamic);
435
+ if (!renderEntryPath) continue;
436
+ const { type, revalidate: classifiedRevalidate } = classifyAppRoute(renderEntryPath, route.routePath, route.isDynamic);
436
437
  if (type === "api") {
437
438
  results.push({
438
439
  route: route.pattern,
@@ -547,8 +548,29 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
547
548
  async function renderUrl({ urlPath, routePattern, revalidate, isSpeculative }) {
548
549
  try {
549
550
  const htmlRequest = new Request(`http://localhost${urlPath}`);
550
- const htmlRes = await runWithHeadersContext(headersContextFromRequest(htmlRequest), () => rscHandler(htmlRequest));
551
- if (!htmlRes.ok) {
551
+ const htmlRender = await runWithHeadersContext(headersContextFromRequest(htmlRequest), async () => {
552
+ const response = await rscHandler(htmlRequest);
553
+ const cacheControl = response.headers.get("cache-control") ?? "";
554
+ if (!response.ok || isSpeculative && cacheControl.includes("no-store")) {
555
+ await response.body?.cancel();
556
+ return {
557
+ cacheControl,
558
+ html: null,
559
+ ok: response.ok,
560
+ requestCacheLife: null,
561
+ status: response.status
562
+ };
563
+ }
564
+ return {
565
+ cacheControl,
566
+ html: await response.text(),
567
+ ok: true,
568
+ requestCacheLife: _consumeRequestScopedCacheLife(),
569
+ status: response.status
570
+ };
571
+ });
572
+ const htmlCacheControl = htmlRender.cacheControl;
573
+ if (!htmlRender.ok) {
552
574
  if (isSpeculative) return {
553
575
  route: routePattern,
554
576
  status: "skipped",
@@ -557,20 +579,22 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
557
579
  return {
558
580
  route: routePattern,
559
581
  status: "error",
560
- error: `RSC handler returned ${htmlRes.status}`
582
+ error: `RSC handler returned ${htmlRender.status}`
561
583
  };
562
584
  }
563
585
  if (isSpeculative) {
564
- if ((htmlRes.headers.get("cache-control") ?? "").includes("no-store")) {
565
- await htmlRes.body?.cancel();
566
- return {
567
- route: routePattern,
568
- status: "skipped",
569
- reason: "dynamic"
570
- };
571
- }
586
+ if (htmlCacheControl.includes("no-store")) return {
587
+ route: routePattern,
588
+ status: "skipped",
589
+ reason: "dynamic"
590
+ };
572
591
  }
573
- const html = await htmlRes.text();
592
+ if (htmlRender.html === null) return {
593
+ route: routePattern,
594
+ status: "error",
595
+ error: "RSC handler returned no prerender HTML"
596
+ };
597
+ const html = htmlRender.html;
574
598
  const rscRequest = new Request(`http://localhost${urlPath}`, { headers: {
575
599
  Accept: "text/x-component",
576
600
  RSC: "1"
@@ -590,11 +614,14 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
590
614
  fs.writeFileSync(rscFullPath, rscData, "utf-8");
591
615
  outputFiles.push(rscOutputPath);
592
616
  }
617
+ const renderedCacheControl = resolveRenderedCacheControl(htmlRender.requestCacheLife ?? {}, htmlCacheControl, config.expireTime);
618
+ const renderedRevalidate = typeof revalidate === "number" ? renderedCacheControl.revalidate === void 0 ? revalidate : Math.min(revalidate, renderedCacheControl.revalidate) : renderedCacheControl.revalidate ?? revalidate;
593
619
  return {
594
620
  route: routePattern,
595
621
  status: "rendered",
596
622
  outputFiles,
597
- revalidate,
623
+ revalidate: renderedRevalidate,
624
+ ...typeof renderedRevalidate === "number" ? { expire: renderedCacheControl.expire } : {},
598
625
  router: "app",
599
626
  ...urlPath !== routePattern ? { path: urlPath } : {}
600
627
  };
@@ -663,6 +690,34 @@ function getRscOutputPath(urlPath) {
663
690
  if (urlPath === "/") return "index.rsc";
664
691
  return urlPath.replace(/^\//, "") + ".rsc";
665
692
  }
693
+ function resolveRenderedCacheControl(requestCacheLife, cacheControl, fallbackExpireSeconds) {
694
+ const sMaxage = parseCacheControlSeconds(cacheControl, "s-maxage");
695
+ const staleWhileRevalidate = parseCacheControlSeconds(cacheControl, "stale-while-revalidate");
696
+ const revalidate = requestCacheLife.revalidate ?? (staleWhileRevalidate === void 0 ? void 0 : sMaxage);
697
+ return {
698
+ expire: requestCacheLife.expire ?? resolveRenderedExpireSeconds({
699
+ fallbackExpireSeconds,
700
+ sMaxage,
701
+ staleWhileRevalidate
702
+ }),
703
+ ...revalidate === void 0 ? {} : { revalidate }
704
+ };
705
+ }
706
+ function resolveRenderedExpireSeconds(options) {
707
+ const { fallbackExpireSeconds, sMaxage, staleWhileRevalidate } = options;
708
+ if (sMaxage === void 0 || staleWhileRevalidate === void 0) return fallbackExpireSeconds;
709
+ return sMaxage + staleWhileRevalidate;
710
+ }
711
+ function parseCacheControlSeconds(cacheControl, directive) {
712
+ for (const part of cacheControl.split(",")) {
713
+ const [rawName, rawValue] = part.trim().split("=", 2);
714
+ if (rawName.trim().toLowerCase() !== directive) continue;
715
+ if (rawValue === void 0) return void 0;
716
+ const value = Number(rawValue.trim());
717
+ if (!Number.isFinite(value) || value < 0) return void 0;
718
+ return value;
719
+ }
720
+ }
666
721
  /**
667
722
  * Write `vinext-prerender.json` to `outDir`.
668
723
  *
@@ -677,6 +732,7 @@ function writePrerenderIndex(routes, outDir, options) {
677
732
  route: r.route,
678
733
  status: r.status,
679
734
  revalidate: r.revalidate,
735
+ ...typeof r.revalidate === "number" ? { expire: r.expire } : {},
680
736
  router: r.router,
681
737
  ...r.path ? { path: r.path } : {}
682
738
  };