vinext 0.0.30 → 0.0.31

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 (435) hide show
  1. package/README.md +12 -6
  2. package/dist/build/prerender.d.ts +188 -0
  3. package/dist/build/prerender.js +675 -0
  4. package/dist/build/prerender.js.map +1 -0
  5. package/dist/build/report.d.ts +45 -46
  6. package/dist/build/report.js +247 -276
  7. package/dist/build/report.js.map +1 -1
  8. package/dist/build/run-prerender.d.ts +62 -0
  9. package/dist/build/run-prerender.js +183 -0
  10. package/dist/build/run-prerender.js.map +1 -0
  11. package/dist/build/server-manifest.d.ts +19 -0
  12. package/dist/build/server-manifest.js +29 -0
  13. package/dist/build/server-manifest.js.map +1 -0
  14. package/dist/build/static-export.d.ts +51 -66
  15. package/dist/build/static-export.js +51 -545
  16. package/dist/build/static-export.js.map +1 -1
  17. package/dist/check.d.ts +26 -24
  18. package/dist/check.js +591 -571
  19. package/dist/check.js.map +1 -1
  20. package/dist/cli.d.ts +1 -15
  21. package/dist/cli.js +430 -491
  22. package/dist/cli.js.map +1 -1
  23. package/dist/client/entry.d.ts +1 -2
  24. package/dist/client/entry.js +49 -62
  25. package/dist/client/entry.js.map +1 -1
  26. package/dist/client/validate-module-path.d.ts +4 -1
  27. package/dist/client/validate-module-path.js +23 -28
  28. package/dist/client/validate-module-path.js.map +1 -1
  29. package/dist/client/vinext-next-data.d.ts +15 -20
  30. package/dist/client/vinext-next-data.js +0 -1
  31. package/dist/cloudflare/index.d.ts +3 -8
  32. package/dist/cloudflare/index.js +3 -8
  33. package/dist/cloudflare/kv-cache-handler.d.ts +95 -105
  34. package/dist/cloudflare/kv-cache-handler.js +354 -380
  35. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  36. package/dist/cloudflare/tpr.d.ts +36 -34
  37. package/dist/cloudflare/tpr.js +460 -603
  38. package/dist/cloudflare/tpr.js.map +1 -1
  39. package/dist/config/config-matchers.d.ts +31 -40
  40. package/dist/config/config-matchers.js +727 -936
  41. package/dist/config/config-matchers.js.map +1 -1
  42. package/dist/config/dotenv.d.ts +18 -11
  43. package/dist/config/dotenv.js +79 -84
  44. package/dist/config/dotenv.js.map +1 -1
  45. package/dist/config/next-config.d.ts +156 -146
  46. package/dist/config/next-config.js +374 -464
  47. package/dist/config/next-config.js.map +1 -1
  48. package/dist/deploy.d.ts +87 -96
  49. package/dist/deploy.js +490 -628
  50. package/dist/deploy.js.map +1 -1
  51. package/dist/entries/app-browser-entry.d.ts +4 -1
  52. package/dist/entries/app-browser-entry.js +12 -8
  53. package/dist/entries/app-browser-entry.js.map +1 -1
  54. package/dist/entries/app-rsc-entry.d.ts +33 -20
  55. package/dist/entries/app-rsc-entry.js +442 -211
  56. package/dist/entries/app-rsc-entry.js.map +1 -1
  57. package/dist/entries/app-ssr-entry.d.ts +9 -1
  58. package/dist/entries/app-ssr-entry.js +61 -28
  59. package/dist/entries/app-ssr-entry.js.map +1 -1
  60. package/dist/entries/pages-client-entry.d.ts +6 -2
  61. package/dist/entries/pages-client-entry.js +30 -33
  62. package/dist/entries/pages-client-entry.js.map +1 -1
  63. package/dist/entries/pages-entry-helpers.d.ts +5 -1
  64. package/dist/entries/pages-entry-helpers.js +17 -14
  65. package/dist/entries/pages-entry-helpers.js.map +1 -1
  66. package/dist/entries/pages-server-entry.d.ts +6 -2
  67. package/dist/entries/pages-server-entry.js +84 -113
  68. package/dist/entries/pages-server-entry.js.map +1 -1
  69. package/dist/index.d.ts +82 -62
  70. package/dist/index.js +2172 -3133
  71. package/dist/index.js.map +1 -1
  72. package/dist/init.d.ts +40 -37
  73. package/dist/init.js +201 -258
  74. package/dist/init.js.map +1 -1
  75. package/dist/plugins/async-hooks-stub.d.ts +7 -3
  76. package/dist/plugins/async-hooks-stub.js +39 -42
  77. package/dist/plugins/async-hooks-stub.js.map +1 -1
  78. package/dist/plugins/client-reference-dedup.d.ts +7 -3
  79. package/dist/plugins/client-reference-dedup.js +63 -88
  80. package/dist/plugins/client-reference-dedup.js.map +1 -1
  81. package/dist/routing/app-router.d.ts +100 -96
  82. package/dist/routing/app-router.js +560 -670
  83. package/dist/routing/app-router.js.map +1 -1
  84. package/dist/routing/file-matcher.d.ts +18 -15
  85. package/dist/routing/file-matcher.js +65 -65
  86. package/dist/routing/file-matcher.js.map +1 -1
  87. package/dist/routing/pages-router.d.ts +23 -24
  88. package/dist/routing/pages-router.js +147 -172
  89. package/dist/routing/pages-router.js.map +1 -1
  90. package/dist/routing/route-trie.d.ts +23 -20
  91. package/dist/routing/route-trie.js +131 -151
  92. package/dist/routing/route-trie.js.map +1 -1
  93. package/dist/routing/route-validation.d.ts +5 -2
  94. package/dist/routing/route-validation.js +98 -130
  95. package/dist/routing/route-validation.js.map +1 -1
  96. package/dist/routing/utils.d.ts +10 -7
  97. package/dist/routing/utils.js +75 -111
  98. package/dist/routing/utils.js.map +1 -1
  99. package/dist/server/api-handler.d.ts +8 -13
  100. package/dist/server/api-handler.js +161 -193
  101. package/dist/server/api-handler.js.map +1 -1
  102. package/dist/server/app-router-entry.d.ts +6 -16
  103. package/dist/server/app-router-entry.js +26 -54
  104. package/dist/server/app-router-entry.js.map +1 -1
  105. package/dist/server/dev-module-runner.d.ts +11 -64
  106. package/dist/server/dev-module-runner.js +89 -101
  107. package/dist/server/dev-module-runner.js.map +1 -1
  108. package/dist/server/dev-origin-check.d.ts +12 -10
  109. package/dist/server/dev-origin-check.js +98 -108
  110. package/dist/server/dev-origin-check.js.map +1 -1
  111. package/dist/server/dev-server.d.ts +17 -14
  112. package/dist/server/dev-server.js +542 -869
  113. package/dist/server/dev-server.js.map +1 -1
  114. package/dist/server/html.d.ts +4 -1
  115. package/dist/server/html.js +25 -26
  116. package/dist/server/html.js.map +1 -1
  117. package/dist/server/image-optimization.d.ts +31 -28
  118. package/dist/server/image-optimization.js +181 -210
  119. package/dist/server/image-optimization.js.map +1 -1
  120. package/dist/server/instrumentation.d.ts +25 -22
  121. package/dist/server/instrumentation.js +110 -122
  122. package/dist/server/instrumentation.js.map +1 -1
  123. package/dist/server/isr-cache.d.ts +16 -26
  124. package/dist/server/isr-cache.js +106 -128
  125. package/dist/server/isr-cache.js.map +1 -1
  126. package/dist/server/metadata-routes.d.ts +85 -88
  127. package/dist/server/metadata-routes.js +270 -317
  128. package/dist/server/metadata-routes.js.map +1 -1
  129. package/dist/server/middleware-codegen.d.ts +7 -4
  130. package/dist/server/middleware-codegen.js +61 -61
  131. package/dist/server/middleware-codegen.js.map +1 -1
  132. package/dist/server/middleware-request-headers.d.ts +8 -6
  133. package/dist/server/middleware-request-headers.js +47 -65
  134. package/dist/server/middleware-request-headers.js.map +1 -1
  135. package/dist/server/middleware.d.ts +31 -47
  136. package/dist/server/middleware.js +273 -404
  137. package/dist/server/middleware.js.map +1 -1
  138. package/dist/server/normalize-path.d.ts +4 -1
  139. package/dist/server/normalize-path.js +33 -47
  140. package/dist/server/normalize-path.js.map +1 -1
  141. package/dist/server/pages-i18n.d.ts +38 -30
  142. package/dist/server/pages-i18n.js +112 -139
  143. package/dist/server/pages-i18n.js.map +1 -1
  144. package/dist/server/prod-server.d.ts +19 -31
  145. package/dist/server/prod-server.js +714 -945
  146. package/dist/server/prod-server.js.map +1 -1
  147. package/dist/server/request-log.d.ts +18 -12
  148. package/dist/server/request-log.js +45 -52
  149. package/dist/server/request-log.js.map +1 -1
  150. package/dist/server/request-pipeline.d.ts +9 -17
  151. package/dist/server/request-pipeline.js +133 -184
  152. package/dist/server/request-pipeline.js.map +1 -1
  153. package/dist/server/worker-utils.d.ts +4 -1
  154. package/dist/server/worker-utils.js +31 -37
  155. package/dist/server/worker-utils.js.map +1 -1
  156. package/dist/shims/amp.d.ts +5 -2
  157. package/dist/shims/amp.js +19 -15
  158. package/dist/shims/amp.js.map +1 -1
  159. package/dist/shims/app.d.ts +8 -10
  160. package/dist/shims/app.js +0 -1
  161. package/dist/shims/cache-runtime.d.ts +20 -45
  162. package/dist/shims/cache-runtime.js +271 -422
  163. package/dist/shims/cache-runtime.js.map +1 -1
  164. package/dist/shims/cache.d.ts +130 -121
  165. package/dist/shims/cache.js +339 -427
  166. package/dist/shims/cache.js.map +1 -1
  167. package/dist/shims/client-only.d.ts +1 -18
  168. package/dist/shims/client-only.js +0 -17
  169. package/dist/shims/compat-router.d.ts +4 -1
  170. package/dist/shims/compat-router.js +23 -19
  171. package/dist/shims/compat-router.js.map +1 -1
  172. package/dist/shims/config.d.ts +7 -5
  173. package/dist/shims/config.js +16 -23
  174. package/dist/shims/config.js.map +1 -1
  175. package/dist/shims/constants.d.ts +119 -118
  176. package/dist/shims/constants.js +159 -164
  177. package/dist/shims/constants.js.map +1 -1
  178. package/dist/shims/document.d.ts +20 -16
  179. package/dist/shims/document.js +41 -22
  180. package/dist/shims/document.js.map +1 -1
  181. package/dist/shims/dynamic.d.ts +13 -22
  182. package/dist/shims/dynamic.js +122 -136
  183. package/dist/shims/dynamic.js.map +1 -1
  184. package/dist/shims/error-boundary.d.ts +22 -15
  185. package/dist/shims/error-boundary.js +81 -79
  186. package/dist/shims/error-boundary.js.map +1 -1
  187. package/dist/shims/error.d.ts +11 -12
  188. package/dist/shims/error.js +35 -39
  189. package/dist/shims/error.js.map +1 -1
  190. package/dist/shims/fetch-cache.d.ts +16 -14
  191. package/dist/shims/fetch-cache.js +437 -645
  192. package/dist/shims/fetch-cache.js.map +1 -1
  193. package/dist/shims/font-google-base.d.ts +28 -26
  194. package/dist/shims/font-google-base.js +238 -325
  195. package/dist/shims/font-google-base.js.map +1 -1
  196. package/dist/shims/font-google.d.ts +3 -3
  197. package/dist/shims/font-google.generated.d.ts +1928 -1924
  198. package/dist/shims/font-google.generated.js +1928 -2133
  199. package/dist/shims/font-google.generated.js.map +1 -1
  200. package/dist/shims/font-google.js +3 -3
  201. package/dist/shims/font-local.d.ts +28 -26
  202. package/dist/shims/font-local.js +204 -260
  203. package/dist/shims/font-local.js.map +1 -1
  204. package/dist/shims/form.d.ts +13 -27
  205. package/dist/shims/form.js +128 -180
  206. package/dist/shims/form.js.map +1 -1
  207. package/dist/shims/head-state.d.ts +8 -13
  208. package/dist/shims/head-state.js +25 -42
  209. package/dist/shims/head-state.js.map +1 -1
  210. package/dist/shims/head.d.ts +16 -20
  211. package/dist/shims/head.js +172 -250
  212. package/dist/shims/head.js.map +1 -1
  213. package/dist/shims/headers.d.ts +84 -78
  214. package/dist/shims/headers.js +447 -575
  215. package/dist/shims/headers.js.map +1 -1
  216. package/dist/shims/i18n-context.d.ts +16 -20
  217. package/dist/shims/i18n-context.js +35 -48
  218. package/dist/shims/i18n-context.js.map +1 -1
  219. package/dist/shims/i18n-state.d.ts +8 -14
  220. package/dist/shims/i18n-state.js +34 -42
  221. package/dist/shims/i18n-state.js.map +1 -1
  222. package/dist/shims/image-config.d.ts +11 -8
  223. package/dist/shims/image-config.js +50 -83
  224. package/dist/shims/image-config.js.map +1 -1
  225. package/dist/shims/image.d.ts +37 -46
  226. package/dist/shims/image.js +283 -308
  227. package/dist/shims/image.js.map +1 -1
  228. package/dist/shims/internal/api-utils.d.ts +7 -4
  229. package/dist/shims/internal/api-utils.js +0 -6
  230. package/dist/shims/internal/app-router-context.d.ts +22 -17
  231. package/dist/shims/internal/app-router-context.js +17 -13
  232. package/dist/shims/internal/app-router-context.js.map +1 -1
  233. package/dist/shims/internal/cookies.d.ts +2 -9
  234. package/dist/shims/internal/cookies.js +2 -9
  235. package/dist/shims/internal/parse-cookie-header.d.ts +4 -1
  236. package/dist/shims/internal/parse-cookie-header.js +29 -29
  237. package/dist/shims/internal/parse-cookie-header.js.map +1 -1
  238. package/dist/shims/internal/router-context.d.ts +6 -1
  239. package/dist/shims/internal/router-context.js +11 -7
  240. package/dist/shims/internal/router-context.js.map +1 -1
  241. package/dist/shims/internal/utils.d.ts +40 -37
  242. package/dist/shims/internal/utils.js +24 -30
  243. package/dist/shims/internal/utils.js.map +1 -1
  244. package/dist/shims/internal/work-unit-async-storage.d.ts +6 -10
  245. package/dist/shims/internal/work-unit-async-storage.js +14 -11
  246. package/dist/shims/internal/work-unit-async-storage.js.map +1 -1
  247. package/dist/shims/layout-segment-context.d.ts +11 -14
  248. package/dist/shims/layout-segment-context.js +24 -23
  249. package/dist/shims/layout-segment-context.js.map +1 -1
  250. package/dist/shims/legacy-image.d.ts +39 -46
  251. package/dist/shims/legacy-image.js +47 -42
  252. package/dist/shims/legacy-image.js.map +1 -1
  253. package/dist/shims/link.d.ts +32 -36
  254. package/dist/shims/link.js +255 -391
  255. package/dist/shims/link.js.map +1 -1
  256. package/dist/shims/metadata.d.ts +210 -202
  257. package/dist/shims/metadata.js +545 -546
  258. package/dist/shims/metadata.js.map +1 -1
  259. package/dist/shims/navigation-state.d.ts +10 -18
  260. package/dist/shims/navigation-state.js +66 -74
  261. package/dist/shims/navigation-state.js.map +1 -1
  262. package/dist/shims/navigation.d.ts +59 -63
  263. package/dist/shims/navigation.js +505 -704
  264. package/dist/shims/navigation.js.map +1 -1
  265. package/dist/shims/og.d.ts +2 -20
  266. package/dist/shims/og.js +2 -19
  267. package/dist/shims/readonly-url-search-params.d.ts +8 -5
  268. package/dist/shims/readonly-url-search-params.js +26 -22
  269. package/dist/shims/readonly-url-search-params.js.map +1 -1
  270. package/dist/shims/request-context.d.ts +8 -5
  271. package/dist/shims/request-context.js +50 -60
  272. package/dist/shims/request-context.js.map +1 -1
  273. package/dist/shims/request-state-types.d.ts +11 -11
  274. package/dist/shims/request-state-types.js +0 -1
  275. package/dist/shims/router-state.d.ts +13 -10
  276. package/dist/shims/router-state.js +34 -43
  277. package/dist/shims/router-state.js.map +1 -1
  278. package/dist/shims/router.d.ts +81 -85
  279. package/dist/shims/router.js +506 -628
  280. package/dist/shims/router.js.map +1 -1
  281. package/dist/shims/script.d.ts +39 -48
  282. package/dist/shims/script.js +107 -160
  283. package/dist/shims/script.js.map +1 -1
  284. package/dist/shims/server-only.d.ts +1 -19
  285. package/dist/shims/server-only.js +0 -18
  286. package/dist/shims/server.d.ts +175 -164
  287. package/dist/shims/server.js +462 -478
  288. package/dist/shims/server.js.map +1 -1
  289. package/dist/shims/unified-request-context.d.ts +20 -20
  290. package/dist/shims/unified-request-context.js +81 -99
  291. package/dist/shims/unified-request-context.js.map +1 -1
  292. package/dist/shims/url-safety.d.ts +4 -1
  293. package/dist/shims/url-safety.js +15 -11
  294. package/dist/shims/url-safety.js.map +1 -1
  295. package/dist/shims/url-utils.d.ts +8 -5
  296. package/dist/shims/url-utils.js +62 -93
  297. package/dist/shims/url-utils.js.map +1 -1
  298. package/dist/shims/web-vitals.d.ts +10 -8
  299. package/dist/shims/web-vitals.js +9 -15
  300. package/dist/shims/web-vitals.js.map +1 -1
  301. package/dist/utils/base-path.d.ts +5 -2
  302. package/dist/utils/base-path.js +21 -19
  303. package/dist/utils/base-path.js.map +1 -1
  304. package/dist/utils/domain-locale.d.ts +17 -9
  305. package/dist/utils/domain-locale.js +36 -56
  306. package/dist/utils/domain-locale.js.map +1 -1
  307. package/dist/utils/hash.d.ts +4 -1
  308. package/dist/utils/hash.js +19 -17
  309. package/dist/utils/hash.js.map +1 -1
  310. package/dist/utils/manifest-paths.d.ts +6 -3
  311. package/dist/utils/manifest-paths.js +15 -16
  312. package/dist/utils/manifest-paths.js.map +1 -1
  313. package/dist/utils/project.d.ts +13 -11
  314. package/dist/utils/project.js +169 -216
  315. package/dist/utils/project.js.map +1 -1
  316. package/dist/utils/query.d.ts +8 -6
  317. package/dist/utils/query.js +57 -67
  318. package/dist/utils/query.js.map +1 -1
  319. package/package.json +10 -9
  320. package/dist/build/report.d.ts.map +0 -1
  321. package/dist/build/static-export.d.ts.map +0 -1
  322. package/dist/check.d.ts.map +0 -1
  323. package/dist/cli.d.ts.map +0 -1
  324. package/dist/client/entry.d.ts.map +0 -1
  325. package/dist/client/validate-module-path.d.ts.map +0 -1
  326. package/dist/client/vinext-next-data.d.ts.map +0 -1
  327. package/dist/client/vinext-next-data.js.map +0 -1
  328. package/dist/cloudflare/index.d.ts.map +0 -1
  329. package/dist/cloudflare/index.js.map +0 -1
  330. package/dist/cloudflare/kv-cache-handler.d.ts.map +0 -1
  331. package/dist/cloudflare/tpr.d.ts.map +0 -1
  332. package/dist/config/config-matchers.d.ts.map +0 -1
  333. package/dist/config/dotenv.d.ts.map +0 -1
  334. package/dist/config/next-config.d.ts.map +0 -1
  335. package/dist/deploy.d.ts.map +0 -1
  336. package/dist/entries/app-browser-entry.d.ts.map +0 -1
  337. package/dist/entries/app-rsc-entry.d.ts.map +0 -1
  338. package/dist/entries/app-ssr-entry.d.ts.map +0 -1
  339. package/dist/entries/pages-client-entry.d.ts.map +0 -1
  340. package/dist/entries/pages-entry-helpers.d.ts.map +0 -1
  341. package/dist/entries/pages-server-entry.d.ts.map +0 -1
  342. package/dist/index.d.ts.map +0 -1
  343. package/dist/init.d.ts.map +0 -1
  344. package/dist/plugins/async-hooks-stub.d.ts.map +0 -1
  345. package/dist/plugins/client-reference-dedup.d.ts.map +0 -1
  346. package/dist/routing/app-router.d.ts.map +0 -1
  347. package/dist/routing/file-matcher.d.ts.map +0 -1
  348. package/dist/routing/pages-router.d.ts.map +0 -1
  349. package/dist/routing/route-trie.d.ts.map +0 -1
  350. package/dist/routing/route-validation.d.ts.map +0 -1
  351. package/dist/routing/utils.d.ts.map +0 -1
  352. package/dist/server/api-handler.d.ts.map +0 -1
  353. package/dist/server/app-router-entry.d.ts.map +0 -1
  354. package/dist/server/dev-module-runner.d.ts.map +0 -1
  355. package/dist/server/dev-origin-check.d.ts.map +0 -1
  356. package/dist/server/dev-server.d.ts.map +0 -1
  357. package/dist/server/html.d.ts.map +0 -1
  358. package/dist/server/image-optimization.d.ts.map +0 -1
  359. package/dist/server/instrumentation.d.ts.map +0 -1
  360. package/dist/server/isr-cache.d.ts.map +0 -1
  361. package/dist/server/metadata-routes.d.ts.map +0 -1
  362. package/dist/server/middleware-codegen.d.ts.map +0 -1
  363. package/dist/server/middleware-request-headers.d.ts.map +0 -1
  364. package/dist/server/middleware.d.ts.map +0 -1
  365. package/dist/server/normalize-path.d.ts.map +0 -1
  366. package/dist/server/pages-i18n.d.ts.map +0 -1
  367. package/dist/server/prod-server.d.ts.map +0 -1
  368. package/dist/server/request-log.d.ts.map +0 -1
  369. package/dist/server/request-pipeline.d.ts.map +0 -1
  370. package/dist/server/worker-utils.d.ts.map +0 -1
  371. package/dist/shims/amp.d.ts.map +0 -1
  372. package/dist/shims/app.d.ts.map +0 -1
  373. package/dist/shims/app.js.map +0 -1
  374. package/dist/shims/cache-runtime.d.ts.map +0 -1
  375. package/dist/shims/cache.d.ts.map +0 -1
  376. package/dist/shims/client-only.d.ts.map +0 -1
  377. package/dist/shims/client-only.js.map +0 -1
  378. package/dist/shims/compat-router.d.ts.map +0 -1
  379. package/dist/shims/config.d.ts.map +0 -1
  380. package/dist/shims/constants.d.ts.map +0 -1
  381. package/dist/shims/document.d.ts.map +0 -1
  382. package/dist/shims/dynamic.d.ts.map +0 -1
  383. package/dist/shims/error-boundary.d.ts.map +0 -1
  384. package/dist/shims/error.d.ts.map +0 -1
  385. package/dist/shims/fetch-cache.d.ts.map +0 -1
  386. package/dist/shims/font-google-base.d.ts.map +0 -1
  387. package/dist/shims/font-google.d.ts.map +0 -1
  388. package/dist/shims/font-google.generated.d.ts.map +0 -1
  389. package/dist/shims/font-google.js.map +0 -1
  390. package/dist/shims/font-local.d.ts.map +0 -1
  391. package/dist/shims/form.d.ts.map +0 -1
  392. package/dist/shims/head-state.d.ts.map +0 -1
  393. package/dist/shims/head.d.ts.map +0 -1
  394. package/dist/shims/headers.d.ts.map +0 -1
  395. package/dist/shims/i18n-context.d.ts.map +0 -1
  396. package/dist/shims/i18n-state.d.ts.map +0 -1
  397. package/dist/shims/image-config.d.ts.map +0 -1
  398. package/dist/shims/image.d.ts.map +0 -1
  399. package/dist/shims/internal/api-utils.d.ts.map +0 -1
  400. package/dist/shims/internal/api-utils.js.map +0 -1
  401. package/dist/shims/internal/app-router-context.d.ts.map +0 -1
  402. package/dist/shims/internal/cookies.d.ts.map +0 -1
  403. package/dist/shims/internal/cookies.js.map +0 -1
  404. package/dist/shims/internal/parse-cookie-header.d.ts.map +0 -1
  405. package/dist/shims/internal/router-context.d.ts.map +0 -1
  406. package/dist/shims/internal/utils.d.ts.map +0 -1
  407. package/dist/shims/internal/work-unit-async-storage.d.ts.map +0 -1
  408. package/dist/shims/layout-segment-context.d.ts.map +0 -1
  409. package/dist/shims/legacy-image.d.ts.map +0 -1
  410. package/dist/shims/link.d.ts.map +0 -1
  411. package/dist/shims/metadata.d.ts.map +0 -1
  412. package/dist/shims/navigation-state.d.ts.map +0 -1
  413. package/dist/shims/navigation.d.ts.map +0 -1
  414. package/dist/shims/og.d.ts.map +0 -1
  415. package/dist/shims/og.js.map +0 -1
  416. package/dist/shims/readonly-url-search-params.d.ts.map +0 -1
  417. package/dist/shims/request-context.d.ts.map +0 -1
  418. package/dist/shims/request-state-types.d.ts.map +0 -1
  419. package/dist/shims/request-state-types.js.map +0 -1
  420. package/dist/shims/router-state.d.ts.map +0 -1
  421. package/dist/shims/router.d.ts.map +0 -1
  422. package/dist/shims/script.d.ts.map +0 -1
  423. package/dist/shims/server-only.d.ts.map +0 -1
  424. package/dist/shims/server-only.js.map +0 -1
  425. package/dist/shims/server.d.ts.map +0 -1
  426. package/dist/shims/unified-request-context.d.ts.map +0 -1
  427. package/dist/shims/url-safety.d.ts.map +0 -1
  428. package/dist/shims/url-utils.d.ts.map +0 -1
  429. package/dist/shims/web-vitals.d.ts.map +0 -1
  430. package/dist/utils/base-path.d.ts.map +0 -1
  431. package/dist/utils/domain-locale.d.ts.map +0 -1
  432. package/dist/utils/hash.d.ts.map +0 -1
  433. package/dist/utils/manifest-paths.d.ts.map +0 -1
  434. package/dist/utils/project.d.ts.map +0 -1
  435. package/dist/utils/query.d.ts.map +0 -1
@@ -1,998 +1,767 @@
1
- /**
2
- * Production server for vinext.
3
- *
4
- * Serves the built output from `vinext build`. Handles:
5
- * - Static asset serving from client build output
6
- * - Pages Router: SSR rendering + API route handling
7
- * - App Router: RSC/SSR rendering, route handlers, server actions
8
- * - Gzip/Brotli compression for text-based responses
9
- * - Streaming SSR for App Router
10
- *
11
- * Build output for Pages Router:
12
- * - dist/client/ — static assets (JS, CSS, images) + .vite/ssr-manifest.json
13
- * - dist/server/entry.js — SSR entry point (virtual:vinext-server-entry)
14
- *
15
- * Build output for App Router:
16
- * - dist/client/ — static assets (JS, CSS, images)
17
- * - dist/server/index.js — RSC entry (default export: handler(Request) → Response)
18
- * - dist/server/ssr/index.js — SSR entry (imported by RSC entry at runtime)
19
- */
20
- import { createServer } from "node:http";
21
- import { Readable, pipeline } from "node:stream";
22
- import { pathToFileURL } from "node:url";
23
- import fs from "node:fs";
24
- import path from "node:path";
25
- import zlib from "node:zlib";
26
- import { matchRedirect, matchRewrite, matchHeaders, requestContextFromRequest, applyMiddlewareRequestHeaders, isExternalUrl, proxyExternalRequest, sanitizeDestination, } from "../config/config-matchers.js";
27
- import { IMAGE_OPTIMIZATION_PATH, IMAGE_CONTENT_SECURITY_POLICY, parseImageParams, isSafeImageContentType, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, } from "./image-optimization.js";
1
+ import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
2
+ import { applyMiddlewareRequestHeaders, isExternalUrl, matchHeaders, matchRedirect, matchRewrite, proxyExternalRequest, requestContextFromRequest, sanitizeDestination } from "../config/config-matchers.js";
28
3
  import { normalizePath } from "./normalize-path.js";
29
4
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
30
- import { computeLazyChunks } from "../index.js";
31
5
  import { manifestFileWithBase } from "../utils/manifest-paths.js";
32
- import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
6
+ import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
7
+ import { readPrerenderSecret } from "../build/server-manifest.js";
8
+ import { computeLazyChunks } from "../index.js";
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import { pathToFileURL } from "node:url";
12
+ import { createServer } from "node:http";
13
+ import { Readable, pipeline } from "node:stream";
14
+ import zlib from "node:zlib";
15
+ //#region src/server/prod-server.ts
16
+ /**
17
+ * Production server for vinext.
18
+ *
19
+ * Serves the built output from `vinext build`. Handles:
20
+ * - Static asset serving from client build output
21
+ * - Pages Router: SSR rendering + API route handling
22
+ * - App Router: RSC/SSR rendering, route handlers, server actions
23
+ * - Gzip/Brotli compression for text-based responses
24
+ * - Streaming SSR for App Router
25
+ *
26
+ * Build output for Pages Router:
27
+ * - dist/client/ — static assets (JS, CSS, images) + .vite/ssr-manifest.json
28
+ * - dist/server/entry.js — SSR entry point (virtual:vinext-server-entry)
29
+ *
30
+ * Build output for App Router:
31
+ * - dist/client/ — static assets (JS, CSS, images)
32
+ * - dist/server/index.js — RSC entry (default export: handler(Request) → Response)
33
+ * - dist/server/ssr/index.js — SSR entry (imported by RSC entry at runtime)
34
+ */
33
35
  /** Convert a Node.js IncomingMessage into a ReadableStream for Web Request body. */
34
36
  function readNodeStream(req) {
35
- return new ReadableStream({
36
- start(controller) {
37
- req.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk)));
38
- req.on("end", () => controller.close());
39
- req.on("error", (err) => controller.error(err));
40
- },
41
- });
37
+ return new ReadableStream({ start(controller) {
38
+ req.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk)));
39
+ req.on("end", () => controller.close());
40
+ req.on("error", (err) => controller.error(err));
41
+ } });
42
42
  }
43
43
  /** Content types that benefit from compression. */
44
44
  const COMPRESSIBLE_TYPES = new Set([
45
- "text/html",
46
- "text/css",
47
- "text/plain",
48
- "text/xml",
49
- "text/javascript",
50
- "application/javascript",
51
- "application/json",
52
- "application/xml",
53
- "application/xhtml+xml",
54
- "application/rss+xml",
55
- "application/atom+xml",
56
- "image/svg+xml",
57
- "application/manifest+json",
58
- "application/wasm",
45
+ "text/html",
46
+ "text/css",
47
+ "text/plain",
48
+ "text/xml",
49
+ "text/javascript",
50
+ "application/javascript",
51
+ "application/json",
52
+ "application/xml",
53
+ "application/xhtml+xml",
54
+ "application/rss+xml",
55
+ "application/atom+xml",
56
+ "image/svg+xml",
57
+ "application/manifest+json",
58
+ "application/wasm"
59
59
  ]);
60
60
  /** Minimum size threshold for compression (in bytes). Below this, compression overhead isn't worth it. */
61
61
  const COMPRESS_THRESHOLD = 1024;
62
62
  /**
63
- * Parse the Accept-Encoding header and return the best supported encoding.
64
- * Preference order: br > gzip > deflate > identity.
65
- */
63
+ * Parse the Accept-Encoding header and return the best supported encoding.
64
+ * Preference order: br > gzip > deflate > identity.
65
+ */
66
66
  function negotiateEncoding(req) {
67
- const accept = req.headers["accept-encoding"];
68
- if (!accept || typeof accept !== "string")
69
- return null;
70
- const lower = accept.toLowerCase();
71
- if (lower.includes("br"))
72
- return "br";
73
- if (lower.includes("gzip"))
74
- return "gzip";
75
- if (lower.includes("deflate"))
76
- return "deflate";
77
- return null;
67
+ const accept = req.headers["accept-encoding"];
68
+ if (!accept || typeof accept !== "string") return null;
69
+ const lower = accept.toLowerCase();
70
+ if (lower.includes("br")) return "br";
71
+ if (lower.includes("gzip")) return "gzip";
72
+ if (lower.includes("deflate")) return "deflate";
73
+ return null;
78
74
  }
79
75
  /**
80
- * Create a compression stream for the given encoding.
81
- */
76
+ * Create a compression stream for the given encoding.
77
+ */
82
78
  function createCompressor(encoding) {
83
- switch (encoding) {
84
- case "br":
85
- return zlib.createBrotliCompress({
86
- params: {
87
- [zlib.constants.BROTLI_PARAM_QUALITY]: 4, // Fast compression (1-11, 4 is a good balance)
88
- },
89
- });
90
- case "gzip":
91
- return zlib.createGzip({ level: 6 }); // Default level, good balance
92
- case "deflate":
93
- return zlib.createDeflate({ level: 6 });
94
- }
79
+ switch (encoding) {
80
+ case "br": return zlib.createBrotliCompress({ params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 4 } });
81
+ case "gzip": return zlib.createGzip({ level: 6 });
82
+ case "deflate": return zlib.createDeflate({ level: 6 });
83
+ }
95
84
  }
96
85
  /**
97
- * Merge middleware headers and a Web Response's headers into a single
98
- * record suitable for Node.js `res.writeHead()`. Uses `getSetCookie()`
99
- * to preserve multiple Set-Cookie values instead of flattening them.
100
- */
86
+ * Merge middleware headers and a Web Response's headers into a single
87
+ * record suitable for Node.js `res.writeHead()`. Uses `getSetCookie()`
88
+ * to preserve multiple Set-Cookie values instead of flattening them.
89
+ */
101
90
  function mergeResponseHeaders(middlewareHeaders, response) {
102
- const merged = { ...middlewareHeaders };
103
- // Copy all non-Set-Cookie headers from the response (response wins on conflict)
104
- // Headers.forEach() always yields lowercase keys
105
- response.headers.forEach((v, k) => {
106
- if (k === "set-cookie")
107
- return;
108
- merged[k] = v;
109
- });
110
- // Preserve multiple Set-Cookie headers using getSetCookie()
111
- const responseCookies = response.headers.getSetCookie?.() ?? [];
112
- if (responseCookies.length > 0) {
113
- const existing = merged["set-cookie"];
114
- const mwCookies = existing ? (Array.isArray(existing) ? existing : [existing]) : [];
115
- merged["set-cookie"] = [...mwCookies, ...responseCookies];
116
- }
117
- return merged;
91
+ const merged = { ...middlewareHeaders };
92
+ response.headers.forEach((v, k) => {
93
+ if (k === "set-cookie") return;
94
+ merged[k] = v;
95
+ });
96
+ const responseCookies = response.headers.getSetCookie?.() ?? [];
97
+ if (responseCookies.length > 0) {
98
+ const existing = merged["set-cookie"];
99
+ merged["set-cookie"] = [...existing ? Array.isArray(existing) ? existing : [existing] : [], ...responseCookies];
100
+ }
101
+ return merged;
118
102
  }
119
103
  /**
120
- * Send a compressed response if the content type is compressible and the
121
- * client supports compression. Otherwise send uncompressed.
122
- */
123
- function sendCompressed(req, res, body, contentType, statusCode, extraHeaders = {}, compress = true, statusText = undefined) {
124
- const buf = typeof body === "string" ? Buffer.from(body) : body;
125
- const baseType = contentType.split(";")[0].trim();
126
- const encoding = compress ? negotiateEncoding(req) : null;
127
- const writeHead = (headers) => {
128
- if (statusText) {
129
- res.writeHead(statusCode, statusText, headers);
130
- }
131
- else {
132
- res.writeHead(statusCode, headers);
133
- }
134
- };
135
- if (encoding && COMPRESSIBLE_TYPES.has(baseType) && buf.length >= COMPRESS_THRESHOLD) {
136
- const compressor = createCompressor(encoding);
137
- // Merge Accept-Encoding into existing Vary header from extraHeaders instead
138
- // of overwriting. Preserves Vary values set by the App Router for content
139
- // negotiation (e.g. "RSC, Accept").
140
- const rawVary = extraHeaders["Vary"] ?? extraHeaders["vary"];
141
- const existingVary = Array.isArray(rawVary) ? rawVary.join(", ") : rawVary;
142
- let varyValue;
143
- if (existingVary) {
144
- const existing = existingVary.toLowerCase();
145
- varyValue = existing.includes("accept-encoding")
146
- ? existingVary
147
- : existingVary + ", Accept-Encoding";
148
- }
149
- else {
150
- varyValue = "Accept-Encoding";
151
- }
152
- writeHead({
153
- ...extraHeaders,
154
- "Content-Type": contentType,
155
- "Content-Encoding": encoding,
156
- Vary: varyValue,
157
- });
158
- compressor.end(buf);
159
- pipeline(compressor, res, () => {
160
- /* ignore pipeline errors on closed connections */
161
- });
162
- }
163
- else {
164
- // Strip any pre-existing content-length (from the Web Response constructor)
165
- // before setting our own — avoids duplicate Content-Length headers.
166
- const { "content-length": _cl, "Content-Length": _CL, ...headersWithoutLength } = extraHeaders;
167
- writeHead({
168
- ...headersWithoutLength,
169
- "Content-Type": contentType,
170
- "Content-Length": String(buf.length),
171
- });
172
- res.end(buf);
173
- }
104
+ * Send a compressed response if the content type is compressible and the
105
+ * client supports compression. Otherwise send uncompressed.
106
+ */
107
+ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders = {}, compress = true, statusText = void 0) {
108
+ const buf = typeof body === "string" ? Buffer.from(body) : body;
109
+ const baseType = contentType.split(";")[0].trim();
110
+ const encoding = compress ? negotiateEncoding(req) : null;
111
+ const writeHead = (headers) => {
112
+ if (statusText) res.writeHead(statusCode, statusText, headers);
113
+ else res.writeHead(statusCode, headers);
114
+ };
115
+ if (encoding && COMPRESSIBLE_TYPES.has(baseType) && buf.length >= 1024) {
116
+ const compressor = createCompressor(encoding);
117
+ const rawVary = extraHeaders["Vary"] ?? extraHeaders["vary"];
118
+ const existingVary = Array.isArray(rawVary) ? rawVary.join(", ") : rawVary;
119
+ let varyValue;
120
+ if (existingVary) varyValue = existingVary.toLowerCase().includes("accept-encoding") ? existingVary : existingVary + ", Accept-Encoding";
121
+ else varyValue = "Accept-Encoding";
122
+ writeHead({
123
+ ...extraHeaders,
124
+ "Content-Type": contentType,
125
+ "Content-Encoding": encoding,
126
+ Vary: varyValue
127
+ });
128
+ compressor.end(buf);
129
+ pipeline(compressor, res, () => {});
130
+ } else {
131
+ const { "content-length": _cl, "Content-Length": _CL, ...headersWithoutLength } = extraHeaders;
132
+ writeHead({
133
+ ...headersWithoutLength,
134
+ "Content-Type": contentType,
135
+ "Content-Length": String(buf.length)
136
+ });
137
+ res.end(buf);
138
+ }
174
139
  }
175
140
  /** Content-type lookup for static assets. */
176
141
  const CONTENT_TYPES = {
177
- ".js": "application/javascript",
178
- ".mjs": "application/javascript",
179
- ".css": "text/css",
180
- ".html": "text/html",
181
- ".json": "application/json",
182
- ".png": "image/png",
183
- ".jpg": "image/jpeg",
184
- ".jpeg": "image/jpeg",
185
- ".gif": "image/gif",
186
- ".svg": "image/svg+xml",
187
- ".ico": "image/x-icon",
188
- ".woff": "font/woff",
189
- ".woff2": "font/woff2",
190
- ".ttf": "font/ttf",
191
- ".eot": "application/vnd.ms-fontobject",
192
- ".webp": "image/webp",
193
- ".avif": "image/avif",
194
- ".map": "application/json",
142
+ ".js": "application/javascript",
143
+ ".mjs": "application/javascript",
144
+ ".css": "text/css",
145
+ ".html": "text/html",
146
+ ".json": "application/json",
147
+ ".png": "image/png",
148
+ ".jpg": "image/jpeg",
149
+ ".jpeg": "image/jpeg",
150
+ ".gif": "image/gif",
151
+ ".svg": "image/svg+xml",
152
+ ".ico": "image/x-icon",
153
+ ".woff": "font/woff",
154
+ ".woff2": "font/woff2",
155
+ ".ttf": "font/ttf",
156
+ ".eot": "application/vnd.ms-fontobject",
157
+ ".webp": "image/webp",
158
+ ".avif": "image/avif",
159
+ ".map": "application/json",
160
+ ".rsc": "text/x-component"
195
161
  };
196
162
  /**
197
- * Try to serve a static file from the client build directory.
198
- * Returns true if the file was served, false otherwise.
199
- */
163
+ * Try to serve a static file from the client build directory.
164
+ * Returns true if the file was served, false otherwise.
165
+ */
200
166
  function tryServeStatic(req, res, clientDir, pathname, compress, extraHeaders) {
201
- // Resolve the path and guard against directory traversal (e.g. /../../../etc/passwd)
202
- const resolvedClient = path.resolve(clientDir);
203
- let decodedPathname;
204
- try {
205
- decodedPathname = decodeURIComponent(pathname);
206
- }
207
- catch {
208
- return false;
209
- }
210
- // Block access to internal build metadata directories. The .vite/
211
- // directory contains manifests and other build artifacts that should
212
- // not be publicly served. Check after decoding to catch encoded
213
- // variants like /%2Evite/manifest.json.
214
- if (decodedPathname.startsWith("/.vite/") || decodedPathname === "/.vite") {
215
- return false;
216
- }
217
- const staticFile = path.resolve(clientDir, "." + decodedPathname);
218
- if (!staticFile.startsWith(resolvedClient + path.sep) && staticFile !== resolvedClient) {
219
- return false;
220
- }
221
- if (pathname === "/" || !fs.existsSync(staticFile) || !fs.statSync(staticFile).isFile()) {
222
- return false;
223
- }
224
- const ext = path.extname(staticFile);
225
- const ct = CONTENT_TYPES[ext] ?? "application/octet-stream";
226
- const isHashed = pathname.startsWith("/assets/");
227
- const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
228
- const baseHeaders = {
229
- "Content-Type": ct,
230
- "Cache-Control": cacheControl,
231
- ...extraHeaders,
232
- };
233
- const baseType = ct.split(";")[0].trim();
234
- if (compress && COMPRESSIBLE_TYPES.has(baseType)) {
235
- const encoding = negotiateEncoding(req);
236
- if (encoding) {
237
- const fileStream = fs.createReadStream(staticFile);
238
- const compressor = createCompressor(encoding);
239
- res.writeHead(200, {
240
- ...baseHeaders,
241
- "Content-Encoding": encoding,
242
- Vary: "Accept-Encoding",
243
- });
244
- pipeline(fileStream, compressor, res, () => {
245
- /* ignore */
246
- });
247
- return true;
248
- }
249
- }
250
- res.writeHead(200, baseHeaders);
251
- fs.createReadStream(staticFile).pipe(res);
252
- return true;
167
+ const resolvedClient = path.resolve(clientDir);
168
+ let decodedPathname;
169
+ try {
170
+ decodedPathname = decodeURIComponent(pathname);
171
+ } catch {
172
+ return false;
173
+ }
174
+ if (decodedPathname.startsWith("/.vite/") || decodedPathname === "/.vite") return false;
175
+ const staticFile = path.resolve(clientDir, "." + decodedPathname);
176
+ if (!staticFile.startsWith(resolvedClient + path.sep) && staticFile !== resolvedClient) return false;
177
+ let resolvedStaticFile = staticFile;
178
+ if (pathname === "/") return false;
179
+ if (!fs.existsSync(resolvedStaticFile) || !fs.statSync(resolvedStaticFile).isFile()) {
180
+ const htmlFallback = staticFile + ".html";
181
+ if (fs.existsSync(htmlFallback) && fs.statSync(htmlFallback).isFile()) resolvedStaticFile = htmlFallback;
182
+ else {
183
+ const indexFallback = path.join(staticFile, "index.html");
184
+ if (fs.existsSync(indexFallback) && fs.statSync(indexFallback).isFile()) resolvedStaticFile = indexFallback;
185
+ else return false;
186
+ }
187
+ }
188
+ const ct = CONTENT_TYPES[path.extname(resolvedStaticFile)] ?? "application/octet-stream";
189
+ const cacheControl = pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "public, max-age=3600";
190
+ const baseHeaders = {
191
+ "Content-Type": ct,
192
+ "Cache-Control": cacheControl,
193
+ ...extraHeaders
194
+ };
195
+ const baseType = ct.split(";")[0].trim();
196
+ if (compress && COMPRESSIBLE_TYPES.has(baseType)) {
197
+ const encoding = negotiateEncoding(req);
198
+ if (encoding) {
199
+ const fileStream = fs.createReadStream(resolvedStaticFile);
200
+ const compressor = createCompressor(encoding);
201
+ res.writeHead(200, {
202
+ ...baseHeaders,
203
+ "Content-Encoding": encoding,
204
+ Vary: "Accept-Encoding"
205
+ });
206
+ pipeline(fileStream, compressor, res, () => {});
207
+ return true;
208
+ }
209
+ }
210
+ res.writeHead(200, baseHeaders);
211
+ fs.createReadStream(resolvedStaticFile).pipe(res);
212
+ return true;
253
213
  }
254
214
  /**
255
- * Resolve the host for a request, ignoring X-Forwarded-Host to prevent
256
- * host header poisoning attacks (open redirects, cache poisoning).
257
- *
258
- * X-Forwarded-Host is only trusted when the VINEXT_TRUSTED_HOSTS env var
259
- * lists the forwarded host value. Without this, an attacker can send
260
- * X-Forwarded-Host: evil.com and poison any redirect that resolves
261
- * against request.url.
262
- *
263
- * On Cloudflare Workers, X-Forwarded-Host is always set by Cloudflare
264
- * itself, so this is only a concern for the Node.js prod-server.
265
- */
215
+ * Resolve the host for a request, ignoring X-Forwarded-Host to prevent
216
+ * host header poisoning attacks (open redirects, cache poisoning).
217
+ *
218
+ * X-Forwarded-Host is only trusted when the VINEXT_TRUSTED_HOSTS env var
219
+ * lists the forwarded host value. Without this, an attacker can send
220
+ * X-Forwarded-Host: evil.com and poison any redirect that resolves
221
+ * against request.url.
222
+ *
223
+ * On Cloudflare Workers, X-Forwarded-Host is always set by Cloudflare
224
+ * itself, so this is only a concern for the Node.js prod-server.
225
+ */
266
226
  function resolveHost(req, fallback) {
267
- const rawForwarded = req.headers["x-forwarded-host"];
268
- const hostHeader = req.headers.host;
269
- if (rawForwarded) {
270
- // X-Forwarded-Host can be comma-separated when passing through
271
- // multiple proxies — take only the first (client-facing) value.
272
- const forwardedHost = rawForwarded.split(",")[0].trim().toLowerCase();
273
- if (forwardedHost && trustedHosts.has(forwardedHost)) {
274
- return forwardedHost;
275
- }
276
- }
277
- return hostHeader || fallback;
227
+ const rawForwarded = req.headers["x-forwarded-host"];
228
+ const hostHeader = req.headers.host;
229
+ if (rawForwarded) {
230
+ const forwardedHost = rawForwarded.split(",")[0].trim().toLowerCase();
231
+ if (forwardedHost && trustedHosts.has(forwardedHost)) return forwardedHost;
232
+ }
233
+ return hostHeader || fallback;
278
234
  }
279
235
  /** Hosts that are allowed as X-Forwarded-Host values (stored lowercase). */
280
- const trustedHosts = new Set((process.env.VINEXT_TRUSTED_HOSTS ?? "")
281
- .split(",")
282
- .map((h) => h.trim().toLowerCase())
283
- .filter(Boolean));
236
+ const trustedHosts = new Set((process.env.VINEXT_TRUSTED_HOSTS ?? "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean));
284
237
  /**
285
- * Whether to trust X-Forwarded-Proto from upstream proxies.
286
- * Enabled when VINEXT_TRUST_PROXY=1 or when VINEXT_TRUSTED_HOSTS is set
287
- * (having trusted hosts implies a trusted proxy).
288
- */
238
+ * Whether to trust X-Forwarded-Proto from upstream proxies.
239
+ * Enabled when VINEXT_TRUST_PROXY=1 or when VINEXT_TRUSTED_HOSTS is set
240
+ * (having trusted hosts implies a trusted proxy).
241
+ */
289
242
  const trustProxy = process.env.VINEXT_TRUST_PROXY === "1" || trustedHosts.size > 0;
290
243
  /**
291
- * Convert a Node.js IncomingMessage to a Web Request object.
292
- *
293
- * When `urlOverride` is provided, it is used as the path + query string
294
- * instead of `req.url`. This avoids redundant path normalization when the
295
- * caller has already decoded and normalized the pathname (e.g. the App
296
- * Router prod server normalizes before static-asset lookup, and can pass
297
- * the result here so the downstream RSC handler doesn't re-normalize).
298
- */
244
+ * Convert a Node.js IncomingMessage to a Web Request object.
245
+ *
246
+ * When `urlOverride` is provided, it is used as the path + query string
247
+ * instead of `req.url`. This avoids redundant path normalization when the
248
+ * caller has already decoded and normalized the pathname (e.g. the App
249
+ * Router prod server normalizes before static-asset lookup, and can pass
250
+ * the result here so the downstream RSC handler doesn't re-normalize).
251
+ */
299
252
  function nodeToWebRequest(req, urlOverride) {
300
- const rawProto = trustProxy
301
- ? req.headers["x-forwarded-proto"]?.split(",")[0]?.trim()
302
- : undefined;
303
- const proto = rawProto === "https" || rawProto === "http" ? rawProto : "http";
304
- const host = resolveHost(req, "localhost");
305
- const origin = `${proto}://${host}`;
306
- const url = new URL(urlOverride ?? req.url ?? "/", origin);
307
- const headers = new Headers();
308
- for (const [key, value] of Object.entries(req.headers)) {
309
- if (value === undefined)
310
- continue;
311
- if (Array.isArray(value)) {
312
- for (const v of value)
313
- headers.append(key, v);
314
- }
315
- else {
316
- headers.set(key, value);
317
- }
318
- }
319
- const method = req.method ?? "GET";
320
- const hasBody = method !== "GET" && method !== "HEAD";
321
- const init = {
322
- method,
323
- headers,
324
- };
325
- if (hasBody) {
326
- // Convert Node.js readable stream to Web ReadableStream for request body.
327
- // Readable.toWeb() is available since Node.js 17.
328
- init.body = Readable.toWeb(req);
329
- init.duplex = "half"; // Required for streaming request bodies
330
- }
331
- return new Request(url, init);
253
+ const rawProto = trustProxy ? req.headers["x-forwarded-proto"]?.split(",")[0]?.trim() : void 0;
254
+ const origin = `${rawProto === "https" || rawProto === "http" ? rawProto : "http"}://${resolveHost(req, "localhost")}`;
255
+ const url = new URL(urlOverride ?? req.url ?? "/", origin);
256
+ const headers = new Headers();
257
+ for (const [key, value] of Object.entries(req.headers)) {
258
+ if (value === void 0) continue;
259
+ if (Array.isArray(value)) for (const v of value) headers.append(key, v);
260
+ else headers.set(key, value);
261
+ }
262
+ const method = req.method ?? "GET";
263
+ const hasBody = method !== "GET" && method !== "HEAD";
264
+ const init = {
265
+ method,
266
+ headers
267
+ };
268
+ if (hasBody) {
269
+ init.body = Readable.toWeb(req);
270
+ init.duplex = "half";
271
+ }
272
+ return new Request(url, init);
332
273
  }
333
274
  /**
334
- * Stream a Web Response back to a Node.js ServerResponse.
335
- * Supports streaming compression for SSR responses.
336
- */
275
+ * Stream a Web Response back to a Node.js ServerResponse.
276
+ * Supports streaming compression for SSR responses.
277
+ */
337
278
  async function sendWebResponse(webResponse, req, res, compress) {
338
- const status = webResponse.status;
339
- const statusText = webResponse.statusText || undefined;
340
- const writeHead = (headers) => {
341
- if (statusText) {
342
- res.writeHead(status, statusText, headers);
343
- }
344
- else {
345
- res.writeHead(status, headers);
346
- }
347
- };
348
- // Collect headers, handling multi-value headers (e.g. Set-Cookie)
349
- const nodeHeaders = {};
350
- webResponse.headers.forEach((value, key) => {
351
- const existing = nodeHeaders[key];
352
- if (existing !== undefined) {
353
- nodeHeaders[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
354
- }
355
- else {
356
- nodeHeaders[key] = value;
357
- }
358
- });
359
- if (!webResponse.body) {
360
- writeHead(nodeHeaders);
361
- res.end();
362
- return;
363
- }
364
- // Check if we should compress the response.
365
- // Skip if the upstream already compressed (avoid double-compression).
366
- const alreadyEncoded = webResponse.headers.has("content-encoding");
367
- const contentType = webResponse.headers.get("content-type") ?? "";
368
- const baseType = contentType.split(";")[0].trim();
369
- const encoding = compress && !alreadyEncoded ? negotiateEncoding(req) : null;
370
- const shouldCompress = !!(encoding && COMPRESSIBLE_TYPES.has(baseType));
371
- if (shouldCompress) {
372
- delete nodeHeaders["content-length"];
373
- delete nodeHeaders["Content-Length"];
374
- nodeHeaders["Content-Encoding"] = encoding;
375
- // Merge Accept-Encoding into existing Vary header (e.g. "RSC, Accept") instead
376
- // of overwriting. This prevents stripping the Vary values that the App Router
377
- // sets for content negotiation (RSC stream vs HTML).
378
- const existingVary = nodeHeaders["Vary"] ?? nodeHeaders["vary"];
379
- if (existingVary) {
380
- const existing = String(existingVary).toLowerCase();
381
- if (!existing.includes("accept-encoding")) {
382
- nodeHeaders["Vary"] = existingVary + ", Accept-Encoding";
383
- }
384
- }
385
- else {
386
- nodeHeaders["Vary"] = "Accept-Encoding";
387
- }
388
- }
389
- writeHead(nodeHeaders);
390
- // HEAD requests: send headers only, skip the body
391
- if (req.method === "HEAD") {
392
- res.end();
393
- return;
394
- }
395
- // Convert Web ReadableStream to Node.js Readable and pipe to response.
396
- // Readable.fromWeb() is available since Node.js 17.
397
- const nodeStream = Readable.fromWeb(webResponse.body);
398
- if (shouldCompress) {
399
- const compressor = createCompressor(encoding);
400
- pipeline(nodeStream, compressor, res, () => {
401
- /* ignore pipeline errors on closed connections */
402
- });
403
- }
404
- else {
405
- pipeline(nodeStream, res, () => {
406
- /* ignore pipeline errors on closed connections */
407
- });
408
- }
279
+ const status = webResponse.status;
280
+ const statusText = webResponse.statusText || void 0;
281
+ const writeHead = (headers) => {
282
+ if (statusText) res.writeHead(status, statusText, headers);
283
+ else res.writeHead(status, headers);
284
+ };
285
+ const nodeHeaders = {};
286
+ webResponse.headers.forEach((value, key) => {
287
+ const existing = nodeHeaders[key];
288
+ if (existing !== void 0) nodeHeaders[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
289
+ else nodeHeaders[key] = value;
290
+ });
291
+ if (!webResponse.body) {
292
+ writeHead(nodeHeaders);
293
+ res.end();
294
+ return;
295
+ }
296
+ const alreadyEncoded = webResponse.headers.has("content-encoding");
297
+ const baseType = (webResponse.headers.get("content-type") ?? "").split(";")[0].trim();
298
+ const encoding = compress && !alreadyEncoded ? negotiateEncoding(req) : null;
299
+ const shouldCompress = !!(encoding && COMPRESSIBLE_TYPES.has(baseType));
300
+ if (shouldCompress) {
301
+ delete nodeHeaders["content-length"];
302
+ delete nodeHeaders["Content-Length"];
303
+ nodeHeaders["Content-Encoding"] = encoding;
304
+ const existingVary = nodeHeaders["Vary"] ?? nodeHeaders["vary"];
305
+ if (existingVary) {
306
+ if (!String(existingVary).toLowerCase().includes("accept-encoding")) nodeHeaders["Vary"] = existingVary + ", Accept-Encoding";
307
+ } else nodeHeaders["Vary"] = "Accept-Encoding";
308
+ }
309
+ writeHead(nodeHeaders);
310
+ if (req.method === "HEAD") {
311
+ res.end();
312
+ return;
313
+ }
314
+ const nodeStream = Readable.fromWeb(webResponse.body);
315
+ if (shouldCompress) pipeline(nodeStream, createCompressor(encoding), res, () => {});
316
+ else pipeline(nodeStream, res, () => {});
409
317
  }
410
318
  /**
411
- * Start the production server.
412
- *
413
- * Automatically detects whether the build is App Router (dist/server/index.js) or
414
- * Pages Router (dist/server/entry.js) and configures the appropriate handler.
415
- */
416
- export async function startProdServer(options = {}) {
417
- const { port = process.env.PORT ? parseInt(process.env.PORT) : 3000, host = "0.0.0.0", outDir = path.resolve("dist"), noCompression = false, } = options;
418
- const compress = !noCompression;
419
- // Always resolve outDir to absolute to ensure dynamic import() works
420
- const resolvedOutDir = path.resolve(outDir);
421
- const clientDir = path.join(resolvedOutDir, "client");
422
- // Detect build type
423
- const rscEntryPath = path.join(resolvedOutDir, "server", "index.js");
424
- const serverEntryPath = path.join(resolvedOutDir, "server", "entry.js");
425
- const isAppRouter = fs.existsSync(rscEntryPath);
426
- if (!isAppRouter && !fs.existsSync(serverEntryPath)) {
427
- console.error(`[vinext] No build output found in ${outDir}`);
428
- console.error("Run `vinext build` first.");
429
- process.exit(1);
430
- }
431
- if (isAppRouter) {
432
- return startAppRouterServer({ port, host, clientDir, rscEntryPath, compress });
433
- }
434
- return startPagesRouterServer({ port, host, clientDir, serverEntryPath, compress });
319
+ * Start the production server.
320
+ *
321
+ * Automatically detects whether the build is App Router (dist/server/index.js) or
322
+ * Pages Router (dist/server/entry.js) and configures the appropriate handler.
323
+ */
324
+ async function startProdServer(options = {}) {
325
+ const { port = process.env.PORT ? parseInt(process.env.PORT) : 3e3, host = "0.0.0.0", outDir = path.resolve("dist"), noCompression = false } = options;
326
+ const compress = !noCompression;
327
+ const resolvedOutDir = path.resolve(outDir);
328
+ const clientDir = path.join(resolvedOutDir, "client");
329
+ const rscEntryPath = path.join(resolvedOutDir, "server", "index.js");
330
+ const serverEntryPath = path.join(resolvedOutDir, "server", "entry.js");
331
+ const isAppRouter = fs.existsSync(rscEntryPath);
332
+ if (!isAppRouter && !fs.existsSync(serverEntryPath)) {
333
+ console.error(`[vinext] No build output found in ${outDir}`);
334
+ console.error("Run `vinext build` first.");
335
+ process.exit(1);
336
+ }
337
+ if (isAppRouter) return startAppRouterServer({
338
+ port,
339
+ host,
340
+ clientDir,
341
+ rscEntryPath,
342
+ compress
343
+ });
344
+ return startPagesRouterServer({
345
+ port,
346
+ host,
347
+ clientDir,
348
+ serverEntryPath,
349
+ compress
350
+ });
435
351
  }
436
352
  function createNodeExecutionContext() {
437
- return {
438
- waitUntil(promise) {
439
- // Node doesn't provide a Workers lifecycle, but we still attach a
440
- // rejection handler so background waitUntil work doesn't surface as an
441
- // unhandled rejection when a Worker-style entry is used with vinext start.
442
- void Promise.resolve(promise).catch(() => { });
443
- },
444
- passThroughOnException() { },
445
- };
353
+ return {
354
+ waitUntil(promise) {
355
+ Promise.resolve(promise).catch(() => {});
356
+ },
357
+ passThroughOnException() {}
358
+ };
446
359
  }
447
360
  function resolveAppRouterHandler(entry) {
448
- if (typeof entry === "function") {
449
- return (request) => Promise.resolve(entry(request));
450
- }
451
- if (entry && typeof entry === "object" && "fetch" in entry) {
452
- const workerEntry = entry;
453
- if (typeof workerEntry.fetch === "function") {
454
- return (request) => Promise.resolve(workerEntry.fetch(request, undefined, createNodeExecutionContext()));
455
- }
456
- }
457
- console.error("[vinext] App Router entry must export either a default handler function or a Worker-style default export with fetch()");
458
- process.exit(1);
361
+ if (typeof entry === "function") return (request) => Promise.resolve(entry(request));
362
+ if (entry && typeof entry === "object" && "fetch" in entry) {
363
+ const workerEntry = entry;
364
+ if (typeof workerEntry.fetch === "function") return (request) => Promise.resolve(workerEntry.fetch(request, void 0, createNodeExecutionContext()));
365
+ }
366
+ console.error("[vinext] App Router entry must export either a default handler function or a Worker-style default export with fetch()");
367
+ process.exit(1);
459
368
  }
460
369
  /**
461
- * Start the App Router production server.
462
- *
463
- * The App Router entry (dist/server/index.js) can export either:
464
- * - a default handler function: handler(request: Request) → Promise<Response>
465
- * - a Worker-style object: { fetch(request, env, ctx) → Promise<Response> }
466
- *
467
- * This handler already does everything: route matching, RSC rendering,
468
- * SSR HTML generation (via import("./ssr/index.js")), route handlers,
469
- * server actions, ISR caching, 404s, redirects, etc.
470
- *
471
- * The production server's job is simply to:
472
- * 1. Serve static assets from dist/client/
473
- * 2. Convert Node.js IncomingMessage → Web Request
474
- * 3. Call the RSC handler
475
- * 4. Stream the Web Response back (with optional compression)
476
- */
370
+ * Start the App Router production server.
371
+ *
372
+ * The App Router entry (dist/server/index.js) can export either:
373
+ * - a default handler function: handler(request: Request) → Promise<Response>
374
+ * - a Worker-style object: { fetch(request, env, ctx) → Promise<Response> }
375
+ *
376
+ * This handler already does everything: route matching, RSC rendering,
377
+ * SSR HTML generation (via import("./ssr/index.js")), route handlers,
378
+ * server actions, ISR caching, 404s, redirects, etc.
379
+ *
380
+ * The production server's job is simply to:
381
+ * 1. Serve static assets from dist/client/
382
+ * 2. Convert Node.js IncomingMessage → Web Request
383
+ * 3. Call the RSC handler
384
+ * 4. Stream the Web Response back (with optional compression)
385
+ */
477
386
  async function startAppRouterServer(options) {
478
- const { port, host, clientDir, rscEntryPath, compress } = options;
479
- // Load image config written at build time by vinext:image-config plugin.
480
- // This provides SVG/security header settings for the image optimization endpoint.
481
- let imageConfig;
482
- const imageConfigPath = path.join(path.dirname(rscEntryPath), "image-config.json");
483
- if (fs.existsSync(imageConfigPath)) {
484
- try {
485
- imageConfig = JSON.parse(fs.readFileSync(imageConfigPath, "utf-8"));
486
- }
487
- catch {
488
- /* ignore parse errors */
489
- }
490
- }
491
- // Import the RSC handler (use file:// URL for reliable dynamic import)
492
- const rscModule = await import(pathToFileURL(rscEntryPath).href);
493
- const rscHandler = resolveAppRouterHandler(rscModule.default);
494
- const server = createServer(async (req, res) => {
495
- const rawUrl = req.url ?? "/";
496
- // Normalize backslashes (browsers treat /\ as //), then decode and normalize path.
497
- const rawPathname = rawUrl.split("?")[0].replaceAll("\\", "/");
498
- let pathname;
499
- try {
500
- pathname = normalizePath(normalizePathnameForRouteMatchStrict(rawPathname));
501
- }
502
- catch {
503
- // Malformed percent-encoding (e.g. /%E0%A4%A) return 400 instead of crashing.
504
- res.writeHead(400);
505
- res.end("Bad Request");
506
- return;
507
- }
508
- // Guard against protocol-relative URL open redirect attacks.
509
- // Check rawPathname before normalizePath collapses //.
510
- if (rawPathname.startsWith("//")) {
511
- res.writeHead(404);
512
- res.end("404 Not Found");
513
- return;
514
- }
515
- // Serve static assets from client build
516
- if (pathname !== "/" && tryServeStatic(req, res, clientDir, pathname, compress)) {
517
- return;
518
- }
519
- // Image optimization passthrough (Node.js prod server has no Images binding;
520
- // serves the original file with cache headers and security headers)
521
- if (pathname === IMAGE_OPTIMIZATION_PATH) {
522
- const parsedUrl = new URL(rawUrl, "http://localhost");
523
- const defaultAllowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES];
524
- const params = parseImageParams(parsedUrl, defaultAllowedWidths);
525
- if (!params) {
526
- res.writeHead(400);
527
- res.end("Bad Request");
528
- return;
529
- }
530
- // Block SVG and other unsafe content types by checking the file extension.
531
- // SVG is only allowed when dangerouslyAllowSVG is enabled in next.config.js.
532
- const ext = path.extname(params.imageUrl).toLowerCase();
533
- const ct = CONTENT_TYPES[ext] ?? "application/octet-stream";
534
- if (!isSafeImageContentType(ct, imageConfig?.dangerouslyAllowSVG)) {
535
- res.writeHead(400);
536
- res.end("The requested resource is not an allowed image type");
537
- return;
538
- }
539
- // Serve the original image with CSP and security headers
540
- const imageSecurityHeaders = {
541
- "Content-Security-Policy": imageConfig?.contentSecurityPolicy ?? IMAGE_CONTENT_SECURITY_POLICY,
542
- "X-Content-Type-Options": "nosniff",
543
- "Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline",
544
- };
545
- if (tryServeStatic(req, res, clientDir, params.imageUrl, false, imageSecurityHeaders)) {
546
- return;
547
- }
548
- res.writeHead(404);
549
- res.end("Image not found");
550
- return;
551
- }
552
- try {
553
- // Build the normalized URL (pathname + original query string) so the
554
- // RSC handler receives an already-canonical path and doesn't need to
555
- // re-normalize. This deduplicates the normalizePath work done above.
556
- const qs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
557
- const normalizedUrl = pathname + qs;
558
- // Convert Node.js request to Web Request and call the RSC handler
559
- const request = nodeToWebRequest(req, normalizedUrl);
560
- const response = await rscHandler(request);
561
- // Stream the Web Response back to the Node.js response
562
- await sendWebResponse(response, req, res, compress);
563
- }
564
- catch (e) {
565
- console.error("[vinext] Server error:", e);
566
- if (!res.headersSent) {
567
- res.writeHead(500);
568
- res.end("Internal Server Error");
569
- }
570
- }
571
- });
572
- await new Promise((resolve) => {
573
- server.listen(port, host, () => {
574
- const addr = server.address();
575
- const actualPort = typeof addr === "object" && addr ? addr.port : port;
576
- console.log(`[vinext] Production server running at http://${host}:${actualPort}`);
577
- resolve();
578
- });
579
- });
580
- return server;
387
+ const { port, host, clientDir, rscEntryPath, compress } = options;
388
+ let imageConfig;
389
+ const imageConfigPath = path.join(path.dirname(rscEntryPath), "image-config.json");
390
+ if (fs.existsSync(imageConfigPath)) try {
391
+ imageConfig = JSON.parse(fs.readFileSync(imageConfigPath, "utf-8"));
392
+ } catch {}
393
+ const prerenderSecret = readPrerenderSecret(path.dirname(rscEntryPath));
394
+ const rscMtime = fs.statSync(rscEntryPath).mtimeMs;
395
+ const rscHandler = resolveAppRouterHandler((await import(`${pathToFileURL(rscEntryPath).href}?t=${rscMtime}`)).default);
396
+ const server = createServer(async (req, res) => {
397
+ const rawUrl = req.url ?? "/";
398
+ const rawPathname = rawUrl.split("?")[0].replaceAll("\\", "/");
399
+ let pathname;
400
+ try {
401
+ pathname = normalizePath(normalizePathnameForRouteMatchStrict(rawPathname));
402
+ } catch {
403
+ res.writeHead(400);
404
+ res.end("Bad Request");
405
+ return;
406
+ }
407
+ if (rawPathname.startsWith("//")) {
408
+ res.writeHead(404);
409
+ res.end("404 Not Found");
410
+ return;
411
+ }
412
+ if (pathname === "/__vinext/prerender/static-params" || pathname === "/__vinext/prerender/pages-static-paths") {
413
+ const secret = req.headers["x-vinext-prerender-secret"];
414
+ if (!prerenderSecret || secret !== prerenderSecret) {
415
+ res.writeHead(403);
416
+ res.end("Forbidden");
417
+ return;
418
+ }
419
+ }
420
+ if (pathname !== "/" && tryServeStatic(req, res, clientDir, pathname, compress)) return;
421
+ if (pathname === "/_vinext/image") {
422
+ const params = parseImageParams(new URL(rawUrl, "http://localhost"), [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES]);
423
+ if (!params) {
424
+ res.writeHead(400);
425
+ res.end("Bad Request");
426
+ return;
427
+ }
428
+ if (!isSafeImageContentType(CONTENT_TYPES[path.extname(params.imageUrl).toLowerCase()] ?? "application/octet-stream", imageConfig?.dangerouslyAllowSVG)) {
429
+ res.writeHead(400);
430
+ res.end("The requested resource is not an allowed image type");
431
+ return;
432
+ }
433
+ const imageSecurityHeaders = {
434
+ "Content-Security-Policy": imageConfig?.contentSecurityPolicy ?? "script-src 'none'; frame-src 'none'; sandbox;",
435
+ "X-Content-Type-Options": "nosniff",
436
+ "Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
437
+ };
438
+ if (tryServeStatic(req, res, clientDir, params.imageUrl, false, imageSecurityHeaders)) return;
439
+ res.writeHead(404);
440
+ res.end("Image not found");
441
+ return;
442
+ }
443
+ try {
444
+ const qs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
445
+ await sendWebResponse(await rscHandler(nodeToWebRequest(req, pathname + qs)), req, res, compress);
446
+ } catch (e) {
447
+ console.error("[vinext] Server error:", e);
448
+ if (!res.headersSent) {
449
+ res.writeHead(500);
450
+ res.end("Internal Server Error");
451
+ }
452
+ }
453
+ });
454
+ await new Promise((resolve) => {
455
+ server.listen(port, host, () => {
456
+ const addr = server.address();
457
+ const actualPort = typeof addr === "object" && addr ? addr.port : port;
458
+ console.log(`[vinext] Production server running at http://${host}:${actualPort}`);
459
+ resolve();
460
+ });
461
+ });
462
+ const addr = server.address();
463
+ return {
464
+ server,
465
+ port: typeof addr === "object" && addr ? addr.port : port
466
+ };
581
467
  }
582
468
  /**
583
- * Start the Pages Router production server.
584
- *
585
- * Uses the server entry (dist/server/entry.js) which exports:
586
- * - renderPage(request, url, manifest) — SSR rendering (Web Request → Response)
587
- * - handleApiRoute(request, url) — API route handling (Web Request → Response)
588
- * - runMiddleware(request, ctx?) — middleware execution (ctx optional; pass for ctx.waitUntil() on Workers)
589
- * - vinextConfig — embedded next.config.js settings
590
- */
469
+ * Start the Pages Router production server.
470
+ *
471
+ * Uses the server entry (dist/server/entry.js) which exports:
472
+ * - renderPage(request, url, manifest) — SSR rendering (Web Request → Response)
473
+ * - handleApiRoute(request, url) — API route handling (Web Request → Response)
474
+ * - runMiddleware(request, ctx?) — middleware execution (ctx optional; pass for ctx.waitUntil() on Workers)
475
+ * - vinextConfig — embedded next.config.js settings
476
+ */
591
477
  async function startPagesRouterServer(options) {
592
- const { port, host, clientDir, serverEntryPath, compress } = options;
593
- // Import the server entry module (use file:// URL for reliable dynamic import)
594
- const serverEntry = await import(pathToFileURL(serverEntryPath).href);
595
- const { renderPage, handleApiRoute: handleApi, runMiddleware, vinextConfig } = serverEntry;
596
- // Extract config values (embedded at build time in the server entry)
597
- const basePath = vinextConfig?.basePath ?? "";
598
- const assetBase = basePath ? `${basePath}/` : "/";
599
- const trailingSlash = vinextConfig?.trailingSlash ?? false;
600
- const configRedirects = vinextConfig?.redirects ?? [];
601
- const configRewrites = vinextConfig?.rewrites ?? {
602
- beforeFiles: [],
603
- afterFiles: [],
604
- fallback: [],
605
- };
606
- const configHeaders = vinextConfig?.headers ?? [];
607
- // Compute allowed image widths from config (union of deviceSizes + imageSizes)
608
- const allowedImageWidths = [
609
- ...(vinextConfig?.images?.deviceSizes ?? DEFAULT_DEVICE_SIZES),
610
- ...(vinextConfig?.images?.imageSizes ?? DEFAULT_IMAGE_SIZES),
611
- ];
612
- // Extract image security config for SVG handling and security headers
613
- const pagesImageConfig = vinextConfig?.images
614
- ? {
615
- dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
616
- contentDispositionType: vinextConfig.images.contentDispositionType,
617
- contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy,
618
- }
619
- : undefined;
620
- // Load the SSR manifest (maps module URLs to client asset URLs)
621
- let ssrManifest = {};
622
- const manifestPath = path.join(clientDir, ".vite", "ssr-manifest.json");
623
- if (fs.existsSync(manifestPath)) {
624
- ssrManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
625
- }
626
- // Load the build manifest to compute lazy chunks — chunks only reachable via
627
- // dynamic imports (React.lazy, next/dynamic). These should not be
628
- // modulepreloaded since they are fetched on demand.
629
- const buildManifestPath = path.join(clientDir, ".vite", "manifest.json");
630
- if (fs.existsSync(buildManifestPath)) {
631
- try {
632
- const buildManifest = JSON.parse(fs.readFileSync(buildManifestPath, "utf-8"));
633
- const lazyChunks = computeLazyChunks(buildManifest).map((file) => manifestFileWithBase(file, assetBase));
634
- if (lazyChunks.length > 0) {
635
- globalThis.__VINEXT_LAZY_CHUNKS__ = lazyChunks;
636
- }
637
- }
638
- catch {
639
- /* ignore parse errors */
640
- }
641
- }
642
- const server = createServer(async (req, res) => {
643
- const rawUrl = req.url ?? "/";
644
- // Normalize backslashes (browsers treat /\ as //), then decode and normalize path.
645
- // Rebuild `url` from the decoded pathname + original query string so all
646
- // downstream consumers (resolvedUrl, resolvedPathname, config matchers)
647
- // always work with the decoded, canonical path.
648
- const rawPagesPathname = rawUrl.split("?")[0].replaceAll("\\", "/");
649
- const rawQs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
650
- let pathname;
651
- try {
652
- pathname = normalizePath(normalizePathnameForRouteMatchStrict(rawPagesPathname));
653
- }
654
- catch {
655
- // Malformed percent-encoding (e.g. /%E0%A4%A) — return 400 instead of crashing.
656
- res.writeHead(400);
657
- res.end("Bad Request");
658
- return;
659
- }
660
- let url = pathname + rawQs;
661
- // Guard against protocol-relative URL open redirect attacks.
662
- // Check rawPagesPathname before normalizePath collapses //.
663
- if (rawPagesPathname.startsWith("//")) {
664
- res.writeHead(404);
665
- res.end("404 Not Found");
666
- return;
667
- }
668
- // ── 1. Static assets ──────────────────────────────────────────
669
- // Serve static files from client build. When basePath is configured,
670
- // Vite's `base` config ensures assets are under basePath/assets/.
671
- // We check both with and without basePath.
672
- const staticLookupPath = stripBasePath(pathname, basePath);
673
- if (staticLookupPath !== "/" &&
674
- !staticLookupPath.startsWith("/api/") &&
675
- tryServeStatic(req, res, clientDir, staticLookupPath, compress)) {
676
- return;
677
- }
678
- // ── Image optimization passthrough ──────────────────────────────
679
- if (pathname === IMAGE_OPTIMIZATION_PATH || staticLookupPath === IMAGE_OPTIMIZATION_PATH) {
680
- const parsedUrl = new URL(rawUrl, "http://localhost");
681
- const params = parseImageParams(parsedUrl, allowedImageWidths);
682
- if (!params) {
683
- res.writeHead(400);
684
- res.end("Bad Request");
685
- return;
686
- }
687
- // Block SVG and other unsafe content types.
688
- // SVG is only allowed when dangerouslyAllowSVG is enabled.
689
- const ext = path.extname(params.imageUrl).toLowerCase();
690
- const ct = CONTENT_TYPES[ext] ?? "application/octet-stream";
691
- if (!isSafeImageContentType(ct, pagesImageConfig?.dangerouslyAllowSVG)) {
692
- res.writeHead(400);
693
- res.end("The requested resource is not an allowed image type");
694
- return;
695
- }
696
- const imageSecurityHeaders = {
697
- "Content-Security-Policy": pagesImageConfig?.contentSecurityPolicy ?? IMAGE_CONTENT_SECURITY_POLICY,
698
- "X-Content-Type-Options": "nosniff",
699
- "Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline",
700
- };
701
- if (tryServeStatic(req, res, clientDir, params.imageUrl, false, imageSecurityHeaders)) {
702
- return;
703
- }
704
- res.writeHead(404);
705
- res.end("Image not found");
706
- return;
707
- }
708
- try {
709
- // ── 2. Strip basePath ─────────────────────────────────────────
710
- {
711
- const stripped = stripBasePath(pathname, basePath);
712
- if (stripped !== pathname) {
713
- const qs = url.includes("?") ? url.slice(url.indexOf("?")) : "";
714
- url = stripped + qs;
715
- pathname = stripped;
716
- }
717
- }
718
- // ── 3. Trailing slash normalization ───────────────────────────
719
- if (pathname !== "/" && pathname !== "/api" && !pathname.startsWith("/api/")) {
720
- const hasTrailing = pathname.endsWith("/");
721
- if (trailingSlash && !hasTrailing) {
722
- const qs = url.includes("?") ? url.slice(url.indexOf("?")) : "";
723
- res.writeHead(308, { Location: basePath + pathname + "/" + qs });
724
- res.end();
725
- return;
726
- }
727
- else if (!trailingSlash && hasTrailing) {
728
- const qs = url.includes("?") ? url.slice(url.indexOf("?")) : "";
729
- res.writeHead(308, { Location: basePath + pathname.replace(/\/+$/, "") + qs });
730
- res.end();
731
- return;
732
- }
733
- }
734
- // Convert Node.js req to Web Request for the server entry
735
- const rawProtocol = trustProxy
736
- ? req.headers["x-forwarded-proto"]?.split(",")[0]?.trim()
737
- : undefined;
738
- const protocol = rawProtocol === "https" || rawProtocol === "http" ? rawProtocol : "http";
739
- const hostHeader = resolveHost(req, `${host}:${port}`);
740
- const reqHeaders = Object.entries(req.headers).reduce((h, [k, v]) => {
741
- if (v)
742
- h.set(k, Array.isArray(v) ? v.join(", ") : v);
743
- return h;
744
- }, new Headers());
745
- const method = req.method ?? "GET";
746
- const hasBody = method !== "GET" && method !== "HEAD";
747
- let webRequest = new Request(`${protocol}://${hostHeader}${url}`, {
748
- method,
749
- headers: reqHeaders,
750
- body: hasBody ? readNodeStream(req) : undefined,
751
- // @ts-expect-error duplex needed for streaming request bodies
752
- duplex: hasBody ? "half" : undefined,
753
- });
754
- // Build request context for pre-middleware config matching. Redirects
755
- // run before middleware in Next.js. Header match conditions also use the
756
- // original request snapshot even though header merging happens later so
757
- // middleware response headers can still take precedence.
758
- // beforeFiles, afterFiles, and fallback all run after middleware per the
759
- // Next.js execution order, so they use postMwReqCtx below.
760
- const reqCtx = requestContextFromRequest(webRequest);
761
- // ── 4. Apply redirects from next.config.js ────────────────────
762
- if (configRedirects.length) {
763
- const redirect = matchRedirect(pathname, configRedirects, reqCtx);
764
- if (redirect) {
765
- // Guard against double-prefixing: only add basePath if destination
766
- // doesn't already start with it.
767
- // Sanitize the final destination to prevent protocol-relative URL open redirects.
768
- const dest = sanitizeDestination(basePath &&
769
- !isExternalUrl(redirect.destination) &&
770
- !hasBasePath(redirect.destination, basePath)
771
- ? basePath + redirect.destination
772
- : redirect.destination);
773
- res.writeHead(redirect.permanent ? 308 : 307, { Location: dest });
774
- res.end();
775
- return;
776
- }
777
- }
778
- // ── 5. Run middleware ─────────────────────────────────────────
779
- let resolvedUrl = url;
780
- const middlewareHeaders = {};
781
- let middlewareRewriteStatus;
782
- if (typeof runMiddleware === "function") {
783
- const result = await runMiddleware(webRequest, undefined);
784
- if (!result.continue) {
785
- if (result.redirectUrl) {
786
- const redirectHeaders = {
787
- Location: result.redirectUrl,
788
- };
789
- if (result.responseHeaders) {
790
- for (const [key, value] of result.responseHeaders) {
791
- const existing = redirectHeaders[key];
792
- if (existing === undefined) {
793
- redirectHeaders[key] = value;
794
- }
795
- else if (Array.isArray(existing)) {
796
- existing.push(value);
797
- }
798
- else {
799
- redirectHeaders[key] = [existing, value];
800
- }
801
- }
802
- }
803
- res.writeHead(result.redirectStatus ?? 307, redirectHeaders);
804
- res.end();
805
- return;
806
- }
807
- if (result.response) {
808
- // Use arrayBuffer() to handle binary response bodies correctly
809
- const body = Buffer.from(await result.response.arrayBuffer());
810
- // Preserve multi-value headers (especially Set-Cookie) by
811
- // using getSetCookie() for cookies and forEach for the rest.
812
- const respHeaders = {};
813
- result.response.headers.forEach((value, key) => {
814
- if (key === "set-cookie")
815
- return; // handled below
816
- respHeaders[key] = value;
817
- });
818
- const setCookies = result.response.headers.getSetCookie?.() ?? [];
819
- if (setCookies.length > 0)
820
- respHeaders["set-cookie"] = setCookies;
821
- if (result.response.statusText) {
822
- res.writeHead(result.response.status, result.response.statusText, respHeaders);
823
- }
824
- else {
825
- res.writeHead(result.response.status, respHeaders);
826
- }
827
- res.end(body);
828
- return;
829
- }
830
- }
831
- // Collect middleware response headers to merge into final response.
832
- // Use an array for Set-Cookie to preserve multiple values.
833
- if (result.responseHeaders) {
834
- for (const [key, value] of result.responseHeaders) {
835
- if (key === "set-cookie") {
836
- const existing = middlewareHeaders[key];
837
- if (Array.isArray(existing)) {
838
- existing.push(value);
839
- }
840
- else if (existing) {
841
- middlewareHeaders[key] = [existing, value];
842
- }
843
- else {
844
- middlewareHeaders[key] = [value];
845
- }
846
- }
847
- else {
848
- middlewareHeaders[key] = value;
849
- }
850
- }
851
- }
852
- // Apply middleware rewrite
853
- if (result.rewriteUrl) {
854
- resolvedUrl = result.rewriteUrl;
855
- }
856
- // Apply custom status code from middleware rewrite
857
- // (e.g. NextResponse.rewrite(url, { status: 403 }))
858
- middlewareRewriteStatus = result.rewriteStatus;
859
- }
860
- // Unpack x-middleware-request-* headers into the actual request and strip
861
- // all x-middleware-* internal signals. Rebuilds postMwReqCtx for use by
862
- // beforeFiles, afterFiles, and fallback config rules (which run after
863
- // middleware per the Next.js execution order).
864
- const { postMwReqCtx, request: postMwReq } = applyMiddlewareRequestHeaders(middlewareHeaders, webRequest);
865
- webRequest = postMwReq;
866
- // Config header matching must keep using the original normalized pathname
867
- // even if middleware rewrites the downstream route/render target.
868
- let resolvedPathname = resolvedUrl.split("?")[0];
869
- // ── 6. Apply custom headers from next.config.js ───────────────
870
- // Config headers are additive for multi-value headers (Vary,
871
- // Set-Cookie) and override for everything else. Set-Cookie values
872
- // are stored as arrays (RFC 6265 forbids comma-joining cookies).
873
- // Middleware headers take precedence: skip config keys already set
874
- // by middleware so middleware always wins for the same key.
875
- if (configHeaders.length) {
876
- const matched = matchHeaders(pathname, configHeaders, reqCtx);
877
- for (const h of matched) {
878
- const lk = h.key.toLowerCase();
879
- if (lk === "set-cookie") {
880
- const existing = middlewareHeaders[lk];
881
- if (Array.isArray(existing)) {
882
- existing.push(h.value);
883
- }
884
- else if (existing) {
885
- middlewareHeaders[lk] = [existing, h.value];
886
- }
887
- else {
888
- middlewareHeaders[lk] = [h.value];
889
- }
890
- }
891
- else if (lk === "vary" && middlewareHeaders[lk]) {
892
- middlewareHeaders[lk] += ", " + h.value;
893
- }
894
- else if (!(lk in middlewareHeaders)) {
895
- // Middleware headers take precedence: only set if middleware
896
- // did not already place this key on the response.
897
- middlewareHeaders[lk] = h.value;
898
- }
899
- }
900
- }
901
- // ── 7. Apply beforeFiles rewrites from next.config.js ─────────
902
- if (configRewrites.beforeFiles?.length) {
903
- const rewritten = matchRewrite(resolvedPathname, configRewrites.beforeFiles, postMwReqCtx);
904
- if (rewritten) {
905
- if (isExternalUrl(rewritten)) {
906
- const proxyResponse = await proxyExternalRequest(webRequest, rewritten);
907
- await sendWebResponse(proxyResponse, req, res, compress);
908
- return;
909
- }
910
- resolvedUrl = rewritten;
911
- resolvedPathname = rewritten.split("?")[0];
912
- }
913
- }
914
- // ── 8. API routes ─────────────────────────────────────────────
915
- if (resolvedPathname.startsWith("/api/") || resolvedPathname === "/api") {
916
- let response;
917
- if (typeof handleApi === "function") {
918
- response = await handleApi(webRequest, resolvedUrl);
919
- }
920
- else {
921
- response = new Response("404 - API route not found", { status: 404 });
922
- }
923
- // Merge middleware + config headers into the response
924
- const responseBody = Buffer.from(await response.arrayBuffer());
925
- // API routes may return arbitrary data (JSON, binary, etc.), so
926
- // default to application/octet-stream rather than text/html when
927
- // the handler doesn't set an explicit Content-Type.
928
- const ct = response.headers.get("content-type") ?? "application/octet-stream";
929
- const responseHeaders = mergeResponseHeaders(middlewareHeaders, response);
930
- const finalStatus = middlewareRewriteStatus ?? response.status;
931
- const finalStatusText = finalStatus === response.status ? response.statusText || undefined : undefined;
932
- sendCompressed(req, res, responseBody, ct, finalStatus, responseHeaders, compress, finalStatusText);
933
- return;
934
- }
935
- // ── 9. Apply afterFiles rewrites from next.config.js ──────────
936
- if (configRewrites.afterFiles?.length) {
937
- const rewritten = matchRewrite(resolvedPathname, configRewrites.afterFiles, postMwReqCtx);
938
- if (rewritten) {
939
- if (isExternalUrl(rewritten)) {
940
- const proxyResponse = await proxyExternalRequest(webRequest, rewritten);
941
- await sendWebResponse(proxyResponse, req, res, compress);
942
- return;
943
- }
944
- resolvedUrl = rewritten;
945
- resolvedPathname = rewritten.split("?")[0];
946
- }
947
- }
948
- // ── 10. SSR page rendering ────────────────────────────────────
949
- let response;
950
- if (typeof renderPage === "function") {
951
- response = await renderPage(webRequest, resolvedUrl, ssrManifest);
952
- // ── 11. Fallback rewrites (if SSR returned 404) ─────────────
953
- if (response && response.status === 404 && configRewrites.fallback?.length) {
954
- const fallbackRewrite = matchRewrite(resolvedPathname, configRewrites.fallback, postMwReqCtx);
955
- if (fallbackRewrite) {
956
- if (isExternalUrl(fallbackRewrite)) {
957
- const proxyResponse = await proxyExternalRequest(webRequest, fallbackRewrite);
958
- await sendWebResponse(proxyResponse, req, res, compress);
959
- return;
960
- }
961
- response = await renderPage(webRequest, fallbackRewrite, ssrManifest);
962
- }
963
- }
964
- }
965
- if (!response) {
966
- res.writeHead(404);
967
- res.end("404 - Not found");
968
- return;
969
- }
970
- // Merge middleware + config headers into the response
971
- const responseBody = Buffer.from(await response.arrayBuffer());
972
- const ct = response.headers.get("content-type") ?? "text/html";
973
- const responseHeaders = mergeResponseHeaders(middlewareHeaders, response);
974
- const finalStatus = middlewareRewriteStatus ?? response.status;
975
- const finalStatusText = finalStatus === response.status ? response.statusText || undefined : undefined;
976
- sendCompressed(req, res, responseBody, ct, finalStatus, responseHeaders, compress, finalStatusText);
977
- }
978
- catch (e) {
979
- console.error("[vinext] Server error:", e);
980
- if (!res.headersSent) {
981
- res.writeHead(500);
982
- res.end("Internal Server Error");
983
- }
984
- }
985
- });
986
- await new Promise((resolve) => {
987
- server.listen(port, host, () => {
988
- const addr = server.address();
989
- const actualPort = typeof addr === "object" && addr ? addr.port : port;
990
- console.log(`[vinext] Production server running at http://${host}:${actualPort}`);
991
- resolve();
992
- });
993
- });
994
- return server;
478
+ const { port, host, clientDir, serverEntryPath, compress } = options;
479
+ const serverMtime = fs.statSync(serverEntryPath).mtimeMs;
480
+ const serverEntry = await import(`${pathToFileURL(serverEntryPath).href}?t=${serverMtime}`);
481
+ const { renderPage, handleApiRoute: handleApi, runMiddleware, vinextConfig } = serverEntry;
482
+ const prerenderSecret = readPrerenderSecret(path.dirname(serverEntryPath));
483
+ const basePath = vinextConfig?.basePath ?? "";
484
+ const assetBase = basePath ? `${basePath}/` : "/";
485
+ const trailingSlash = vinextConfig?.trailingSlash ?? false;
486
+ const configRedirects = vinextConfig?.redirects ?? [];
487
+ const configRewrites = vinextConfig?.rewrites ?? {
488
+ beforeFiles: [],
489
+ afterFiles: [],
490
+ fallback: []
491
+ };
492
+ const configHeaders = vinextConfig?.headers ?? [];
493
+ const allowedImageWidths = [...vinextConfig?.images?.deviceSizes ?? DEFAULT_DEVICE_SIZES, ...vinextConfig?.images?.imageSizes ?? DEFAULT_IMAGE_SIZES];
494
+ const pagesImageConfig = vinextConfig?.images ? {
495
+ dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
496
+ contentDispositionType: vinextConfig.images.contentDispositionType,
497
+ contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
498
+ } : void 0;
499
+ let ssrManifest = {};
500
+ const manifestPath = path.join(clientDir, ".vite", "ssr-manifest.json");
501
+ if (fs.existsSync(manifestPath)) ssrManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
502
+ const buildManifestPath = path.join(clientDir, ".vite", "manifest.json");
503
+ if (fs.existsSync(buildManifestPath)) try {
504
+ const lazyChunks = computeLazyChunks(JSON.parse(fs.readFileSync(buildManifestPath, "utf-8"))).map((file) => manifestFileWithBase(file, assetBase));
505
+ if (lazyChunks.length > 0) globalThis.__VINEXT_LAZY_CHUNKS__ = lazyChunks;
506
+ } catch {}
507
+ const server = createServer(async (req, res) => {
508
+ const rawUrl = req.url ?? "/";
509
+ const rawPagesPathname = rawUrl.split("?")[0].replaceAll("\\", "/");
510
+ const rawQs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
511
+ let pathname;
512
+ try {
513
+ pathname = normalizePath(normalizePathnameForRouteMatchStrict(rawPagesPathname));
514
+ } catch {
515
+ res.writeHead(400);
516
+ res.end("Bad Request");
517
+ return;
518
+ }
519
+ let url = pathname + rawQs;
520
+ if (rawPagesPathname.startsWith("//")) {
521
+ res.writeHead(404);
522
+ res.end("404 Not Found");
523
+ return;
524
+ }
525
+ if (pathname === "/__vinext/prerender/pages-static-paths") {
526
+ const secret = req.headers["x-vinext-prerender-secret"];
527
+ if (!prerenderSecret || secret !== prerenderSecret) {
528
+ res.writeHead(403);
529
+ res.end("Forbidden");
530
+ return;
531
+ }
532
+ const parsedUrl = new URL(rawUrl, "http://localhost");
533
+ const pattern = parsedUrl.searchParams.get("pattern") ?? "";
534
+ const localesRaw = parsedUrl.searchParams.get("locales");
535
+ const locales = localesRaw ? JSON.parse(localesRaw) : [];
536
+ const defaultLocale = parsedUrl.searchParams.get("defaultLocale") ?? "";
537
+ const fn = (serverEntry.pageRoutes?.find((r) => r.pattern === pattern))?.module?.getStaticPaths;
538
+ if (typeof fn !== "function") {
539
+ res.writeHead(200, { "Content-Type": "application/json" });
540
+ res.end("null");
541
+ return;
542
+ }
543
+ try {
544
+ const result = await fn({
545
+ locales,
546
+ defaultLocale
547
+ });
548
+ res.writeHead(200, { "Content-Type": "application/json" });
549
+ res.end(JSON.stringify(result));
550
+ } catch (e) {
551
+ res.writeHead(500);
552
+ res.end(e.message);
553
+ }
554
+ return;
555
+ }
556
+ const staticLookupPath = stripBasePath(pathname, basePath);
557
+ if (staticLookupPath !== "/" && !staticLookupPath.startsWith("/api/") && tryServeStatic(req, res, clientDir, staticLookupPath, compress)) return;
558
+ if (pathname === "/_vinext/image" || staticLookupPath === "/_vinext/image") {
559
+ const params = parseImageParams(new URL(rawUrl, "http://localhost"), allowedImageWidths);
560
+ if (!params) {
561
+ res.writeHead(400);
562
+ res.end("Bad Request");
563
+ return;
564
+ }
565
+ if (!isSafeImageContentType(CONTENT_TYPES[path.extname(params.imageUrl).toLowerCase()] ?? "application/octet-stream", pagesImageConfig?.dangerouslyAllowSVG)) {
566
+ res.writeHead(400);
567
+ res.end("The requested resource is not an allowed image type");
568
+ return;
569
+ }
570
+ const imageSecurityHeaders = {
571
+ "Content-Security-Policy": pagesImageConfig?.contentSecurityPolicy ?? "script-src 'none'; frame-src 'none'; sandbox;",
572
+ "X-Content-Type-Options": "nosniff",
573
+ "Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
574
+ };
575
+ if (tryServeStatic(req, res, clientDir, params.imageUrl, false, imageSecurityHeaders)) return;
576
+ res.writeHead(404);
577
+ res.end("Image not found");
578
+ return;
579
+ }
580
+ try {
581
+ {
582
+ const stripped = stripBasePath(pathname, basePath);
583
+ if (stripped !== pathname) {
584
+ url = stripped + (url.includes("?") ? url.slice(url.indexOf("?")) : "");
585
+ pathname = stripped;
586
+ }
587
+ }
588
+ if (pathname !== "/" && pathname !== "/api" && !pathname.startsWith("/api/")) {
589
+ const hasTrailing = pathname.endsWith("/");
590
+ if (trailingSlash && !hasTrailing) {
591
+ const qs = url.includes("?") ? url.slice(url.indexOf("?")) : "";
592
+ res.writeHead(308, { Location: basePath + pathname + "/" + qs });
593
+ res.end();
594
+ return;
595
+ } else if (!trailingSlash && hasTrailing) {
596
+ const qs = url.includes("?") ? url.slice(url.indexOf("?")) : "";
597
+ res.writeHead(308, { Location: basePath + pathname.replace(/\/+$/, "") + qs });
598
+ res.end();
599
+ return;
600
+ }
601
+ }
602
+ const rawProtocol = trustProxy ? req.headers["x-forwarded-proto"]?.split(",")[0]?.trim() : void 0;
603
+ const protocol = rawProtocol === "https" || rawProtocol === "http" ? rawProtocol : "http";
604
+ const hostHeader = resolveHost(req, `${host}:${port}`);
605
+ const reqHeaders = Object.entries(req.headers).reduce((h, [k, v]) => {
606
+ if (v) h.set(k, Array.isArray(v) ? v.join(", ") : v);
607
+ return h;
608
+ }, new Headers());
609
+ const method = req.method ?? "GET";
610
+ const hasBody = method !== "GET" && method !== "HEAD";
611
+ let webRequest = new Request(`${protocol}://${hostHeader}${url}`, {
612
+ method,
613
+ headers: reqHeaders,
614
+ body: hasBody ? readNodeStream(req) : void 0,
615
+ duplex: hasBody ? "half" : void 0
616
+ });
617
+ const reqCtx = requestContextFromRequest(webRequest);
618
+ if (configRedirects.length) {
619
+ const redirect = matchRedirect(pathname, configRedirects, reqCtx);
620
+ if (redirect) {
621
+ const dest = sanitizeDestination(basePath && !isExternalUrl(redirect.destination) && !hasBasePath(redirect.destination, basePath) ? basePath + redirect.destination : redirect.destination);
622
+ res.writeHead(redirect.permanent ? 308 : 307, { Location: dest });
623
+ res.end();
624
+ return;
625
+ }
626
+ }
627
+ let resolvedUrl = url;
628
+ const middlewareHeaders = {};
629
+ let middlewareRewriteStatus;
630
+ if (typeof runMiddleware === "function") {
631
+ const result = await runMiddleware(webRequest, void 0);
632
+ if (!result.continue) {
633
+ if (result.redirectUrl) {
634
+ const redirectHeaders = { Location: result.redirectUrl };
635
+ if (result.responseHeaders) for (const [key, value] of result.responseHeaders) {
636
+ const existing = redirectHeaders[key];
637
+ if (existing === void 0) redirectHeaders[key] = value;
638
+ else if (Array.isArray(existing)) existing.push(value);
639
+ else redirectHeaders[key] = [existing, value];
640
+ }
641
+ res.writeHead(result.redirectStatus ?? 307, redirectHeaders);
642
+ res.end();
643
+ return;
644
+ }
645
+ if (result.response) {
646
+ const body = Buffer.from(await result.response.arrayBuffer());
647
+ const respHeaders = {};
648
+ result.response.headers.forEach((value, key) => {
649
+ if (key === "set-cookie") return;
650
+ respHeaders[key] = value;
651
+ });
652
+ const setCookies = result.response.headers.getSetCookie?.() ?? [];
653
+ if (setCookies.length > 0) respHeaders["set-cookie"] = setCookies;
654
+ if (result.response.statusText) res.writeHead(result.response.status, result.response.statusText, respHeaders);
655
+ else res.writeHead(result.response.status, respHeaders);
656
+ res.end(body);
657
+ return;
658
+ }
659
+ }
660
+ if (result.responseHeaders) for (const [key, value] of result.responseHeaders) if (key === "set-cookie") {
661
+ const existing = middlewareHeaders[key];
662
+ if (Array.isArray(existing)) existing.push(value);
663
+ else if (existing) middlewareHeaders[key] = [existing, value];
664
+ else middlewareHeaders[key] = [value];
665
+ } else middlewareHeaders[key] = value;
666
+ if (result.rewriteUrl) resolvedUrl = result.rewriteUrl;
667
+ middlewareRewriteStatus = result.rewriteStatus;
668
+ }
669
+ const { postMwReqCtx, request: postMwReq } = applyMiddlewareRequestHeaders(middlewareHeaders, webRequest);
670
+ webRequest = postMwReq;
671
+ let resolvedPathname = resolvedUrl.split("?")[0];
672
+ if (configHeaders.length) {
673
+ const matched = matchHeaders(pathname, configHeaders, reqCtx);
674
+ for (const h of matched) {
675
+ const lk = h.key.toLowerCase();
676
+ if (lk === "set-cookie") {
677
+ const existing = middlewareHeaders[lk];
678
+ if (Array.isArray(existing)) existing.push(h.value);
679
+ else if (existing) middlewareHeaders[lk] = [existing, h.value];
680
+ else middlewareHeaders[lk] = [h.value];
681
+ } else if (lk === "vary" && middlewareHeaders[lk]) middlewareHeaders[lk] += ", " + h.value;
682
+ else if (!(lk in middlewareHeaders)) middlewareHeaders[lk] = h.value;
683
+ }
684
+ }
685
+ if (configRewrites.beforeFiles?.length) {
686
+ const rewritten = matchRewrite(resolvedPathname, configRewrites.beforeFiles, postMwReqCtx);
687
+ if (rewritten) {
688
+ if (isExternalUrl(rewritten)) {
689
+ await sendWebResponse(await proxyExternalRequest(webRequest, rewritten), req, res, compress);
690
+ return;
691
+ }
692
+ resolvedUrl = rewritten;
693
+ resolvedPathname = rewritten.split("?")[0];
694
+ }
695
+ }
696
+ if (resolvedPathname.startsWith("/api/") || resolvedPathname === "/api") {
697
+ let response;
698
+ if (typeof handleApi === "function") response = await handleApi(webRequest, resolvedUrl);
699
+ else response = new Response("404 - API route not found", { status: 404 });
700
+ const responseBody = Buffer.from(await response.arrayBuffer());
701
+ const ct = response.headers.get("content-type") ?? "application/octet-stream";
702
+ const responseHeaders = mergeResponseHeaders(middlewareHeaders, response);
703
+ const finalStatus = middlewareRewriteStatus ?? response.status;
704
+ sendCompressed(req, res, responseBody, ct, finalStatus, responseHeaders, compress, finalStatus === response.status ? response.statusText || void 0 : void 0);
705
+ return;
706
+ }
707
+ if (configRewrites.afterFiles?.length) {
708
+ const rewritten = matchRewrite(resolvedPathname, configRewrites.afterFiles, postMwReqCtx);
709
+ if (rewritten) {
710
+ if (isExternalUrl(rewritten)) {
711
+ await sendWebResponse(await proxyExternalRequest(webRequest, rewritten), req, res, compress);
712
+ return;
713
+ }
714
+ resolvedUrl = rewritten;
715
+ resolvedPathname = rewritten.split("?")[0];
716
+ }
717
+ }
718
+ let response;
719
+ if (typeof renderPage === "function") {
720
+ response = await renderPage(webRequest, resolvedUrl, ssrManifest);
721
+ if (response && response.status === 404 && configRewrites.fallback?.length) {
722
+ const fallbackRewrite = matchRewrite(resolvedPathname, configRewrites.fallback, postMwReqCtx);
723
+ if (fallbackRewrite) {
724
+ if (isExternalUrl(fallbackRewrite)) {
725
+ await sendWebResponse(await proxyExternalRequest(webRequest, fallbackRewrite), req, res, compress);
726
+ return;
727
+ }
728
+ response = await renderPage(webRequest, fallbackRewrite, ssrManifest);
729
+ }
730
+ }
731
+ }
732
+ if (!response) {
733
+ res.writeHead(404);
734
+ res.end("404 - Not found");
735
+ return;
736
+ }
737
+ const responseBody = Buffer.from(await response.arrayBuffer());
738
+ const ct = response.headers.get("content-type") ?? "text/html";
739
+ const responseHeaders = mergeResponseHeaders(middlewareHeaders, response);
740
+ const finalStatus = middlewareRewriteStatus ?? response.status;
741
+ sendCompressed(req, res, responseBody, ct, finalStatus, responseHeaders, compress, finalStatus === response.status ? response.statusText || void 0 : void 0);
742
+ } catch (e) {
743
+ console.error("[vinext] Server error:", e);
744
+ if (!res.headersSent) {
745
+ res.writeHead(500);
746
+ res.end("Internal Server Error");
747
+ }
748
+ }
749
+ });
750
+ await new Promise((resolve) => {
751
+ server.listen(port, host, () => {
752
+ const addr = server.address();
753
+ const actualPort = typeof addr === "object" && addr ? addr.port : port;
754
+ console.log(`[vinext] Production server running at http://${host}:${actualPort}`);
755
+ resolve();
756
+ });
757
+ });
758
+ const addr = server.address();
759
+ return {
760
+ server,
761
+ port: typeof addr === "object" && addr ? addr.port : port
762
+ };
995
763
  }
996
- // Export helpers for testing
997
- export { sendCompressed, negotiateEncoding, COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, resolveHost, trustedHosts, trustProxy, nodeToWebRequest, mergeResponseHeaders, };
764
+ //#endregion
765
+ export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, mergeResponseHeaders, negotiateEncoding, nodeToWebRequest, resolveHost, sendCompressed, startProdServer, trustProxy, trustedHosts };
766
+
998
767
  //# sourceMappingURL=prod-server.js.map