vinext 0.0.37 → 0.0.39

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 (262) hide show
  1. package/README.md +33 -20
  2. package/dist/build/nitro-route-rules.d.ts +50 -0
  3. package/dist/build/nitro-route-rules.js +81 -0
  4. package/dist/build/nitro-route-rules.js.map +1 -0
  5. package/dist/build/precompress.d.ts +17 -0
  6. package/dist/build/precompress.js +102 -0
  7. package/dist/build/precompress.js.map +1 -0
  8. package/dist/build/prerender.d.ts +27 -22
  9. package/dist/build/prerender.js +17 -17
  10. package/dist/build/prerender.js.map +1 -1
  11. package/dist/build/report.d.ts +3 -4
  12. package/dist/build/report.js.map +1 -1
  13. package/dist/build/run-prerender.d.ts +3 -4
  14. package/dist/build/run-prerender.js.map +1 -1
  15. package/dist/build/standalone.d.ts +32 -0
  16. package/dist/build/standalone.js +199 -0
  17. package/dist/build/standalone.js.map +1 -0
  18. package/dist/build/static-export.d.ts +17 -29
  19. package/dist/build/static-export.js.map +1 -1
  20. package/dist/cache.d.ts +2 -0
  21. package/dist/cache.js +2 -0
  22. package/dist/check.d.ts +4 -4
  23. package/dist/check.js +1 -1
  24. package/dist/check.js.map +1 -1
  25. package/dist/cli.js +37 -26
  26. package/dist/cli.js.map +1 -1
  27. package/dist/client/empty-module.d.ts +1 -0
  28. package/dist/client/empty-module.js +1 -0
  29. package/dist/client/entry.js +2 -0
  30. package/dist/client/entry.js.map +1 -1
  31. package/dist/client/instrumentation-client-state.d.ts +10 -0
  32. package/dist/client/instrumentation-client-state.js +19 -0
  33. package/dist/client/instrumentation-client-state.js.map +1 -0
  34. package/dist/client/instrumentation-client.d.ts +8 -0
  35. package/dist/client/instrumentation-client.js +8 -0
  36. package/dist/client/instrumentation-client.js.map +1 -0
  37. package/dist/client/vinext-next-data.d.ts +5 -8
  38. package/dist/cloudflare/index.js +1 -1
  39. package/dist/cloudflare/kv-cache-handler.d.ts +5 -3
  40. package/dist/cloudflare/kv-cache-handler.js +1 -1
  41. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  42. package/dist/cloudflare/tpr.d.ts +35 -27
  43. package/dist/cloudflare/tpr.js +37 -15
  44. package/dist/cloudflare/tpr.js.map +1 -1
  45. package/dist/config/config-matchers.d.ts +2 -2
  46. package/dist/config/config-matchers.js +1 -1
  47. package/dist/config/config-matchers.js.map +1 -1
  48. package/dist/config/dotenv.d.ts +4 -4
  49. package/dist/config/dotenv.js.map +1 -1
  50. package/dist/config/next-config.d.ts +40 -61
  51. package/dist/config/next-config.js +5 -4
  52. package/dist/config/next-config.js.map +1 -1
  53. package/dist/deploy.d.ts +25 -41
  54. package/dist/deploy.js +10 -4
  55. package/dist/deploy.js.map +1 -1
  56. package/dist/entries/app-rsc-entry.d.ts +6 -10
  57. package/dist/entries/app-rsc-entry.js +31 -28
  58. package/dist/entries/app-rsc-entry.js.map +1 -1
  59. package/dist/entries/pages-client-entry.js +2 -0
  60. package/dist/entries/pages-client-entry.js.map +1 -1
  61. package/dist/entries/pages-server-entry.js +42 -263
  62. package/dist/entries/pages-server-entry.js.map +1 -1
  63. package/dist/entries/runtime-entry-module.d.ts +13 -1
  64. package/dist/entries/runtime-entry-module.js +18 -4
  65. package/dist/entries/runtime-entry-module.js.map +1 -1
  66. package/dist/index.d.ts +33 -41
  67. package/dist/index.js +263 -777
  68. package/dist/index.js.map +1 -1
  69. package/dist/init.d.ts +14 -26
  70. package/dist/init.js +8 -2
  71. package/dist/init.js.map +1 -1
  72. package/dist/plugins/client-reference-dedup.js.map +1 -1
  73. package/dist/plugins/fix-use-server-closure-collision.d.ts +29 -0
  74. package/dist/plugins/fix-use-server-closure-collision.js +204 -0
  75. package/dist/plugins/fix-use-server-closure-collision.js.map +1 -0
  76. package/dist/plugins/fonts.d.ts +56 -0
  77. package/dist/plugins/fonts.js +531 -0
  78. package/dist/plugins/fonts.js.map +1 -0
  79. package/dist/plugins/instrumentation-client.d.ts +7 -0
  80. package/dist/plugins/instrumentation-client.js +29 -0
  81. package/dist/plugins/instrumentation-client.js.map +1 -0
  82. package/dist/plugins/og-assets.d.ts +26 -0
  83. package/dist/plugins/og-assets.js +118 -0
  84. package/dist/plugins/og-assets.js.map +1 -0
  85. package/dist/plugins/optimize-imports.d.ts +2 -2
  86. package/dist/plugins/optimize-imports.js +4 -4
  87. package/dist/plugins/optimize-imports.js.map +1 -1
  88. package/dist/plugins/server-externals-manifest.d.ts +27 -0
  89. package/dist/plugins/server-externals-manifest.js +76 -0
  90. package/dist/plugins/server-externals-manifest.js.map +1 -0
  91. package/dist/routing/app-router.d.ts +29 -55
  92. package/dist/routing/app-router.js.map +1 -1
  93. package/dist/routing/file-matcher.d.ts +2 -2
  94. package/dist/routing/file-matcher.js.map +1 -1
  95. package/dist/routing/pages-router.d.ts +6 -11
  96. package/dist/routing/pages-router.js.map +1 -1
  97. package/dist/routing/route-trie.d.ts +2 -2
  98. package/dist/routing/route-trie.js.map +1 -1
  99. package/dist/server/api-handler.js +6 -23
  100. package/dist/server/api-handler.js.map +1 -1
  101. package/dist/server/app-browser-entry.js +274 -39
  102. package/dist/server/app-browser-entry.js.map +1 -1
  103. package/dist/server/app-browser-stream.d.ts +6 -6
  104. package/dist/server/app-browser-stream.js.map +1 -1
  105. package/dist/server/app-page-boundary-render.d.ts +8 -8
  106. package/dist/server/app-page-boundary-render.js +2 -2
  107. package/dist/server/app-page-boundary-render.js.map +1 -1
  108. package/dist/server/app-page-boundary.d.ts +13 -11
  109. package/dist/server/app-page-boundary.js +1 -1
  110. package/dist/server/app-page-boundary.js.map +1 -1
  111. package/dist/server/app-page-cache.d.ts +10 -10
  112. package/dist/server/app-page-cache.js.map +1 -1
  113. package/dist/server/app-page-execution.d.ts +10 -10
  114. package/dist/server/app-page-execution.js.map +1 -1
  115. package/dist/server/app-page-probe.d.ts +2 -2
  116. package/dist/server/app-page-probe.js.map +1 -1
  117. package/dist/server/app-page-render.d.ts +4 -4
  118. package/dist/server/app-page-render.js.map +1 -1
  119. package/dist/server/app-page-request.d.ts +12 -12
  120. package/dist/server/app-page-request.js.map +1 -1
  121. package/dist/server/app-page-response.d.ts +18 -18
  122. package/dist/server/app-page-response.js.map +1 -1
  123. package/dist/server/app-page-stream.d.ts +18 -18
  124. package/dist/server/app-page-stream.js.map +1 -1
  125. package/dist/server/app-route-handler-cache.d.ts +2 -2
  126. package/dist/server/app-route-handler-cache.js.map +1 -1
  127. package/dist/server/app-route-handler-execution.d.ts +6 -6
  128. package/dist/server/app-route-handler-execution.js.map +1 -1
  129. package/dist/server/app-route-handler-policy.d.ts +8 -8
  130. package/dist/server/app-route-handler-policy.js.map +1 -1
  131. package/dist/server/app-route-handler-response.d.ts +6 -6
  132. package/dist/server/app-route-handler-response.js.map +1 -1
  133. package/dist/server/app-route-handler-runtime.d.ts +4 -4
  134. package/dist/server/app-route-handler-runtime.js.map +1 -1
  135. package/dist/server/app-ssr-entry.d.ts +4 -4
  136. package/dist/server/app-ssr-entry.js.map +1 -1
  137. package/dist/server/app-ssr-stream.d.ts +2 -2
  138. package/dist/server/app-ssr-stream.js +1 -3
  139. package/dist/server/app-ssr-stream.js.map +1 -1
  140. package/dist/server/dev-module-runner.d.ts +2 -2
  141. package/dist/server/dev-module-runner.js.map +1 -1
  142. package/dist/server/dev-server.js +8 -8
  143. package/dist/server/dev-server.js.map +1 -1
  144. package/dist/server/image-optimization.d.ts +7 -12
  145. package/dist/server/image-optimization.js.map +1 -1
  146. package/dist/server/instrumentation.d.ts +13 -13
  147. package/dist/server/instrumentation.js +16 -7
  148. package/dist/server/instrumentation.js.map +1 -1
  149. package/dist/server/isr-cache.d.ts +2 -2
  150. package/dist/server/isr-cache.js.map +1 -1
  151. package/dist/server/metadata-routes.d.ts +14 -19
  152. package/dist/server/metadata-routes.js.map +1 -1
  153. package/dist/server/middleware.d.ts +10 -16
  154. package/dist/server/middleware.js +15 -8
  155. package/dist/server/middleware.js.map +1 -1
  156. package/dist/server/pages-api-route.d.ts +23 -0
  157. package/dist/server/pages-api-route.js +40 -0
  158. package/dist/server/pages-api-route.js.map +1 -0
  159. package/dist/server/pages-i18n.d.ts +4 -4
  160. package/dist/server/pages-i18n.js.map +1 -1
  161. package/dist/server/pages-media-type.d.ts +16 -0
  162. package/dist/server/pages-media-type.js +25 -0
  163. package/dist/server/pages-media-type.js.map +1 -0
  164. package/dist/server/pages-node-compat.d.ts +45 -0
  165. package/dist/server/pages-node-compat.js +148 -0
  166. package/dist/server/pages-node-compat.js.map +1 -0
  167. package/dist/server/pages-page-data.d.ts +22 -22
  168. package/dist/server/pages-page-data.js.map +1 -1
  169. package/dist/server/pages-page-response.d.ts +8 -8
  170. package/dist/server/pages-page-response.js.map +1 -1
  171. package/dist/server/prod-server.d.ts +20 -15
  172. package/dist/server/prod-server.js +171 -53
  173. package/dist/server/prod-server.js.map +1 -1
  174. package/dist/server/seed-cache.js.map +1 -1
  175. package/dist/server/static-file-cache.d.ts +57 -0
  176. package/dist/server/static-file-cache.js +219 -0
  177. package/dist/server/static-file-cache.js.map +1 -0
  178. package/dist/shims/app.d.ts +2 -2
  179. package/dist/shims/cache-for-request.d.ts +58 -0
  180. package/dist/shims/cache-for-request.js +74 -0
  181. package/dist/shims/cache-for-request.js.map +1 -0
  182. package/dist/shims/cache-runtime.d.ts +6 -9
  183. package/dist/shims/cache-runtime.js.map +1 -1
  184. package/dist/shims/cache.d.ts +28 -31
  185. package/dist/shims/cache.js.map +1 -1
  186. package/dist/shims/config.d.ts +2 -2
  187. package/dist/shims/config.js.map +1 -1
  188. package/dist/shims/dynamic.d.ts +2 -2
  189. package/dist/shims/dynamic.js +30 -17
  190. package/dist/shims/dynamic.js.map +1 -1
  191. package/dist/shims/error-boundary.d.ts +7 -7
  192. package/dist/shims/error-boundary.js.map +1 -1
  193. package/dist/shims/error.d.ts +2 -2
  194. package/dist/shims/error.js.map +1 -1
  195. package/dist/shims/fetch-cache.d.ts +4 -4
  196. package/dist/shims/fetch-cache.js.map +1 -1
  197. package/dist/shims/font-google-base.d.ts +4 -4
  198. package/dist/shims/font-google-base.js.map +1 -1
  199. package/dist/shims/font-local.d.ts +6 -6
  200. package/dist/shims/font-local.js.map +1 -1
  201. package/dist/shims/form.d.ts +4 -8
  202. package/dist/shims/form.js +4 -6
  203. package/dist/shims/form.js.map +1 -1
  204. package/dist/shims/head-state.d.ts +2 -2
  205. package/dist/shims/head-state.js.map +1 -1
  206. package/dist/shims/head.d.ts +2 -2
  207. package/dist/shims/head.js +18 -20
  208. package/dist/shims/head.js.map +1 -1
  209. package/dist/shims/headers.d.ts +4 -4
  210. package/dist/shims/headers.js +1 -1
  211. package/dist/shims/headers.js.map +1 -1
  212. package/dist/shims/i18n-context.d.ts +2 -2
  213. package/dist/shims/i18n-context.js.map +1 -1
  214. package/dist/shims/i18n-state.d.ts +2 -2
  215. package/dist/shims/i18n-state.js.map +1 -1
  216. package/dist/shims/image-config.d.ts +2 -2
  217. package/dist/shims/image-config.js.map +1 -1
  218. package/dist/shims/image.d.ts +5 -6
  219. package/dist/shims/image.js +24 -8
  220. package/dist/shims/image.js.map +1 -1
  221. package/dist/shims/internal/app-router-context.d.ts +6 -6
  222. package/dist/shims/internal/app-router-context.js.map +1 -1
  223. package/dist/shims/internal/utils.d.ts +2 -2
  224. package/dist/shims/internal/utils.js.map +1 -1
  225. package/dist/shims/layout-segment-context.d.ts +12 -5
  226. package/dist/shims/layout-segment-context.js +18 -7
  227. package/dist/shims/layout-segment-context.js.map +1 -1
  228. package/dist/shims/legacy-image.d.ts +5 -8
  229. package/dist/shims/legacy-image.js.map +1 -1
  230. package/dist/shims/link.d.ts +21 -31
  231. package/dist/shims/link.js +4 -56
  232. package/dist/shims/link.js.map +1 -1
  233. package/dist/shims/metadata.d.ts +23 -31
  234. package/dist/shims/metadata.js.map +1 -1
  235. package/dist/shims/navigation-state.d.ts +2 -2
  236. package/dist/shims/navigation-state.js.map +1 -1
  237. package/dist/shims/navigation.d.ts +102 -17
  238. package/dist/shims/navigation.js +361 -113
  239. package/dist/shims/navigation.js.map +1 -1
  240. package/dist/shims/request-context.d.ts +2 -2
  241. package/dist/shims/request-context.js.map +1 -1
  242. package/dist/shims/router-state.d.ts +4 -4
  243. package/dist/shims/router-state.js.map +1 -1
  244. package/dist/shims/router.d.ts +28 -47
  245. package/dist/shims/router.js.map +1 -1
  246. package/dist/shims/script.d.ts +16 -31
  247. package/dist/shims/script.js.map +1 -1
  248. package/dist/shims/server.d.ts +11 -10
  249. package/dist/shims/server.js +3 -0
  250. package/dist/shims/server.js.map +1 -1
  251. package/dist/shims/unified-request-context.d.ts +4 -4
  252. package/dist/shims/unified-request-context.js +1 -0
  253. package/dist/shims/unified-request-context.js.map +1 -1
  254. package/dist/shims/web-vitals.d.ts +2 -2
  255. package/dist/shims/web-vitals.js.map +1 -1
  256. package/dist/utils/lazy-chunks.d.ts +34 -0
  257. package/dist/utils/lazy-chunks.js +50 -0
  258. package/dist/utils/lazy-chunks.js.map +1 -0
  259. package/dist/utils/vinext-root.d.ts +24 -0
  260. package/dist/utils/vinext-root.js +31 -0
  261. package/dist/utils/vinext-root.js.map +1 -0
  262. package/package.json +8 -4
package/README.md CHANGED
@@ -74,6 +74,16 @@ Options: `-p / --port <port>`, `-H / --hostname <host>`, `--turbopack` (accepted
74
74
 
75
75
  `vinext init` options: `--port <port>` (default: 3001), `--skip-check`, `--force`.
76
76
 
77
+ If your `next.config.*` sets `output: "standalone"`, `vinext build` emits a self-hosting bundle at `dist/standalone/`. Start it with:
78
+
79
+ ```bash
80
+ node dist/standalone/server.js
81
+ ```
82
+
83
+ Environment variables: `PORT` (default `3000`), `HOST` (default `0.0.0.0`).
84
+
85
+ > **Note:** Next.js standalone uses `HOSTNAME` for the bind address, but vinext uses `HOST` to avoid collision with the system-set `HOSTNAME` variable on Linux. Update your deployment config accordingly.
86
+
77
87
  ### Starting a new vinext project
78
88
 
79
89
  Run `npm create next-app@latest` to create a new Next.js project, and then follow these instructions to migrate it to vinext.
@@ -94,7 +104,7 @@ This will:
94
104
  2. Install `vite`, `@vitejs/plugin-react`, and App Router-only deps (`@vitejs/plugin-rsc`, `react-server-dom-webpack`) as devDependencies
95
105
  3. Rename CJS config files (e.g. `postcss.config.js` -> `.cjs`) to avoid ESM conflicts
96
106
  4. Add `"type": "module"` to `package.json`
97
- 5. Add `dev:vinext` and `build:vinext` scripts to `package.json`
107
+ 5. Add `dev:vinext`, `build:vinext`, and `start:vinext` scripts to `package.json`
98
108
  6. Generate a minimal `vite.config.ts`
99
109
 
100
110
  The migration is non-destructive -- your existing Next.js setup continues to work alongside vinext. It does not modify `next.config`, `tsconfig.json`, or any source files, and it does not remove Next.js dependencies.
@@ -103,6 +113,8 @@ vinext targets Vite 8, which defaults to Rolldown, Oxc, Lightning CSS, and a new
103
113
 
104
114
  ```bash
105
115
  npm run dev:vinext # Start the vinext dev server (port 3001)
116
+ npm run build:vinext # Build production output with vinext
117
+ npm run start:vinext # Start vinext production server
106
118
  npm run dev # Still runs Next.js as before
107
119
  ```
108
120
 
@@ -458,25 +470,26 @@ Every `next/*` import is shimmed to a Vite-compatible implementation.
458
470
 
459
471
  ### Server features
460
472
 
461
- | Feature | | Notes |
462
- | ---------------------------------- | --- | ------------------------------------------------------------------------------------------- |
463
- | SSR (Pages Router) | ✅ | Streaming, `_app`/`_document`, `__NEXT_DATA__`, hydration |
464
- | SSR (App Router) | ✅ | RSC pipeline, nested layouts, streaming, nav context for client components |
465
- | `getStaticProps` | ✅ | Props, redirect, notFound, revalidate |
466
- | `getStaticPaths` | ✅ | `fallback: false`, `true`, `"blocking"` |
467
- | `getServerSideProps` | ✅ | Full context including locale |
468
- | ISR | ✅ | Stale-while-revalidate, pluggable `CacheHandler`, background regeneration |
469
- | Server Actions (`"use server"`) | ✅ | Action execution, FormData, re-render after mutation, `redirect()` in actions |
470
- | React Server Components | ✅ | Via `@vitejs/plugin-rsc`. `"use client"` boundaries work correctly |
471
- | Streaming SSR | ✅ | Both routers |
472
- | Metadata API | ✅ | `metadata`, `generateMetadata`, `viewport`, `generateViewport`, title templates |
473
- | `generateStaticParams` | ✅ | With `dynamicParams` enforcement |
474
- | Metadata file routes | ✅ | sitemap.xml, robots.txt, manifest, favicon, OG images (static + dynamic) |
475
- | Static export (`output: 'export'`) | ✅ | Generates static HTML/JSON for all routes |
476
- | `connection()` | ✅ | Forces dynamic rendering |
477
- | `"use cache"` directive | ✅ | File-level and function-level. `cacheLife()` profiles, `cacheTag()`, stale-while-revalidate |
478
- | `instrumentation.ts` | ✅ | `register()` and `onRequestError()` callbacks |
479
- | Route segment config | 🟡 | `revalidate`, `dynamic`, `dynamicParams`. `runtime` and `preferredRegion` are ignored |
473
+ | Feature | | Notes |
474
+ | ------------------------------------------ | --- | ------------------------------------------------------------------------------------------- |
475
+ | SSR (Pages Router) | ✅ | Streaming, `_app`/`_document`, `__NEXT_DATA__`, hydration |
476
+ | SSR (App Router) | ✅ | RSC pipeline, nested layouts, streaming, nav context for client components |
477
+ | `getStaticProps` | ✅ | Props, redirect, notFound, revalidate |
478
+ | `getStaticPaths` | ✅ | `fallback: false`, `true`, `"blocking"` |
479
+ | `getServerSideProps` | ✅ | Full context including locale |
480
+ | ISR | ✅ | Stale-while-revalidate, pluggable `CacheHandler`, background regeneration |
481
+ | Server Actions (`"use server"`) | ✅ | Action execution, FormData, re-render after mutation, `redirect()` in actions |
482
+ | React Server Components | ✅ | Via `@vitejs/plugin-rsc`. `"use client"` boundaries work correctly |
483
+ | Streaming SSR | ✅ | Both routers |
484
+ | Metadata API | ✅ | `metadata`, `generateMetadata`, `viewport`, `generateViewport`, title templates |
485
+ | `generateStaticParams` | ✅ | With `dynamicParams` enforcement |
486
+ | Metadata file routes | ✅ | sitemap.xml, robots.txt, manifest, favicon, OG images (static + dynamic) |
487
+ | Static export (`output: 'export'`) | ✅ | Generates static HTML/JSON for all routes |
488
+ | Standalone output (`output: 'standalone'`) | ✅ | Generates `dist/standalone` with `server.js`, build artifacts, and runtime deps |
489
+ | `connection()` | ✅ | Forces dynamic rendering |
490
+ | `"use cache"` directive | ✅ | File-level and function-level. `cacheLife()` profiles, `cacheTag()`, stale-while-revalidate |
491
+ | `instrumentation.ts` | | `register()` and `onRequestError()` callbacks |
492
+ | Route segment config | 🟡 | `revalidate`, `dynamic`, `dynamicParams`. `runtime` and `preferredRegion` are ignored |
480
493
 
481
494
  ### Configuration
482
495
 
@@ -0,0 +1,50 @@
1
+ import { RouteRow } from "./report.js";
2
+
3
+ //#region src/build/nitro-route-rules.d.ts
4
+ type NitroRouteRuleConfig = Record<string, unknown> & {
5
+ swr?: boolean | number;
6
+ cache?: unknown;
7
+ static?: boolean;
8
+ isr?: boolean | number;
9
+ prerender?: boolean;
10
+ };
11
+ type NitroRouteRules = Record<string, {
12
+ swr: number;
13
+ }>;
14
+ /**
15
+ * Scans the filesystem for route files and generates Nitro `routeRules` for ISR routes.
16
+ *
17
+ * Note: this duplicates the filesystem scanning that `printBuildReport` also performs.
18
+ * The `nitro.setup` hook runs during Nitro initialization (before the build), while
19
+ * `printBuildReport` runs after the build, so sharing results is non-trivial. This is
20
+ * a future optimization target.
21
+ *
22
+ * Unlike `printBuildReport`, this path does not receive `prerenderResult`, so routes
23
+ * classified as `unknown` by static analysis (which `printBuildReport` might upgrade
24
+ * to `static` via speculative prerender) are skipped here.
25
+ */
26
+ declare function collectNitroRouteRules(options: {
27
+ appDir?: string | null;
28
+ pagesDir?: string | null;
29
+ pageExtensions: string[];
30
+ }): Promise<NitroRouteRules>;
31
+ declare function generateNitroRouteRules(rows: RouteRow[]): NitroRouteRules;
32
+ /**
33
+ * Converts vinext's internal `:param` route syntax to Nitro's rou3
34
+ * pattern format. Nitro uses `rou3` for routeRules matching, which
35
+ * supports `*` (single-segment) and `**` (multi-segment) wildcards.
36
+ *
37
+ * /blog/:slug -> /blog/* (single segment)
38
+ * /docs/:slug+ -> /docs/** (one or more segments — catch-all)
39
+ * /docs/:slug* -> /docs/** (zero or more segments — optional catch-all)
40
+ * /about -> /about (unchanged)
41
+ * /:a/:b produces `/*`/`/*` (consecutive single-segment params)
42
+ */
43
+ declare function convertToNitroPattern(pattern: string): string;
44
+ declare function mergeNitroRouteRules(existingRouteRules: Record<string, NitroRouteRuleConfig> | undefined, generatedRouteRules: NitroRouteRules): {
45
+ routeRules: Record<string, NitroRouteRuleConfig>;
46
+ skippedRoutes: string[];
47
+ };
48
+ //#endregion
49
+ export { NitroRouteRuleConfig, NitroRouteRules, collectNitroRouteRules, convertToNitroPattern, generateNitroRouteRules, mergeNitroRouteRules };
50
+ //# sourceMappingURL=nitro-route-rules.d.ts.map
@@ -0,0 +1,81 @@
1
+ import { apiRouter, pagesRouter } from "../routing/pages-router.js";
2
+ import { appRouter } from "../routing/app-router.js";
3
+ import { buildReportRows } from "./report.js";
4
+ //#region src/build/nitro-route-rules.ts
5
+ /**
6
+ * Scans the filesystem for route files and generates Nitro `routeRules` for ISR routes.
7
+ *
8
+ * Note: this duplicates the filesystem scanning that `printBuildReport` also performs.
9
+ * The `nitro.setup` hook runs during Nitro initialization (before the build), while
10
+ * `printBuildReport` runs after the build, so sharing results is non-trivial. This is
11
+ * a future optimization target.
12
+ *
13
+ * Unlike `printBuildReport`, this path does not receive `prerenderResult`, so routes
14
+ * classified as `unknown` by static analysis (which `printBuildReport` might upgrade
15
+ * to `static` via speculative prerender) are skipped here.
16
+ */
17
+ async function collectNitroRouteRules(options) {
18
+ const { appDir, pageExtensions, pagesDir } = options;
19
+ let appRoutes = [];
20
+ let pageRoutes = [];
21
+ let apiRoutes = [];
22
+ if (appDir) appRoutes = await appRouter(appDir, pageExtensions);
23
+ if (pagesDir) {
24
+ const [pages, apis] = await Promise.all([pagesRouter(pagesDir, pageExtensions), apiRouter(pagesDir, pageExtensions)]);
25
+ pageRoutes = pages;
26
+ apiRoutes = apis;
27
+ }
28
+ return generateNitroRouteRules(buildReportRows({
29
+ appRoutes,
30
+ pageRoutes,
31
+ apiRoutes
32
+ }));
33
+ }
34
+ function generateNitroRouteRules(rows) {
35
+ const rules = {};
36
+ for (const row of rows) if (row.type === "isr" && typeof row.revalidate === "number" && Number.isFinite(row.revalidate) && row.revalidate > 0) rules[convertToNitroPattern(row.pattern)] = { swr: row.revalidate };
37
+ return rules;
38
+ }
39
+ /**
40
+ * Converts vinext's internal `:param` route syntax to Nitro's rou3
41
+ * pattern format. Nitro uses `rou3` for routeRules matching, which
42
+ * supports `*` (single-segment) and `**` (multi-segment) wildcards.
43
+ *
44
+ * /blog/:slug -> /blog/* (single segment)
45
+ * /docs/:slug+ -> /docs/** (one or more segments — catch-all)
46
+ * /docs/:slug* -> /docs/** (zero or more segments — optional catch-all)
47
+ * /about -> /about (unchanged)
48
+ * /:a/:b produces `/*`/`/*` (consecutive single-segment params)
49
+ */
50
+ function convertToNitroPattern(pattern) {
51
+ return pattern.split("/").map((segment) => {
52
+ if (segment.startsWith(":")) return segment.endsWith("+") || segment.endsWith("*") ? "**" : "*";
53
+ return segment;
54
+ }).join("/");
55
+ }
56
+ function mergeNitroRouteRules(existingRouteRules, generatedRouteRules) {
57
+ const routeRules = { ...existingRouteRules };
58
+ const skippedRoutes = [];
59
+ for (const [route, generatedRule] of Object.entries(generatedRouteRules)) {
60
+ const existingRule = routeRules[route];
61
+ if (existingRule && hasUserDefinedCacheRule(existingRule)) {
62
+ skippedRoutes.push(route);
63
+ continue;
64
+ }
65
+ routeRules[route] = {
66
+ ...existingRule,
67
+ ...generatedRule
68
+ };
69
+ }
70
+ return {
71
+ routeRules,
72
+ skippedRoutes
73
+ };
74
+ }
75
+ function hasUserDefinedCacheRule(rule) {
76
+ return rule.swr !== void 0 || rule.cache !== void 0 || rule.static !== void 0 || rule.isr !== void 0 || rule.prerender !== void 0;
77
+ }
78
+ //#endregion
79
+ export { collectNitroRouteRules, convertToNitroPattern, generateNitroRouteRules, mergeNitroRouteRules };
80
+
81
+ //# sourceMappingURL=nitro-route-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nitro-route-rules.js","names":[],"sources":["../../src/build/nitro-route-rules.ts"],"sourcesContent":["import { appRouter, type AppRoute } from \"../routing/app-router.js\";\nimport { apiRouter, pagesRouter, type Route } from \"../routing/pages-router.js\";\nimport { buildReportRows, type RouteRow } from \"./report.js\";\n\n// Mirrors Nitro's NitroRouteConfig — hand-rolled because nitropack is not a direct dependency.\nexport type NitroRouteRuleConfig = Record<string, unknown> & {\n swr?: boolean | number;\n cache?: unknown;\n static?: boolean;\n isr?: boolean | number;\n prerender?: boolean;\n};\n\nexport type NitroRouteRules = Record<string, { swr: number }>;\n\n/**\n * Scans the filesystem for route files and generates Nitro `routeRules` for ISR routes.\n *\n * Note: this duplicates the filesystem scanning that `printBuildReport` also performs.\n * The `nitro.setup` hook runs during Nitro initialization (before the build), while\n * `printBuildReport` runs after the build, so sharing results is non-trivial. This is\n * a future optimization target.\n *\n * Unlike `printBuildReport`, this path does not receive `prerenderResult`, so routes\n * classified as `unknown` by static analysis (which `printBuildReport` might upgrade\n * to `static` via speculative prerender) are skipped here.\n */\nexport async function collectNitroRouteRules(options: {\n appDir?: string | null;\n pagesDir?: string | null;\n pageExtensions: string[];\n}): Promise<NitroRouteRules> {\n const { appDir, pageExtensions, pagesDir } = options;\n\n let appRoutes: AppRoute[] = [];\n let pageRoutes: Route[] = [];\n let apiRoutes: Route[] = [];\n\n if (appDir) {\n appRoutes = await appRouter(appDir, pageExtensions);\n }\n\n if (pagesDir) {\n const [pages, apis] = await Promise.all([\n pagesRouter(pagesDir, pageExtensions),\n apiRouter(pagesDir, pageExtensions),\n ]);\n pageRoutes = pages;\n apiRoutes = apis;\n }\n\n return generateNitroRouteRules(buildReportRows({ appRoutes, pageRoutes, apiRoutes }));\n}\n\nexport function generateNitroRouteRules(rows: RouteRow[]): NitroRouteRules {\n const rules: NitroRouteRules = {};\n\n for (const row of rows) {\n if (\n row.type === \"isr\" &&\n typeof row.revalidate === \"number\" &&\n Number.isFinite(row.revalidate) &&\n row.revalidate > 0\n ) {\n rules[convertToNitroPattern(row.pattern)] = { swr: row.revalidate };\n }\n }\n\n return rules;\n}\n\n/**\n * Converts vinext's internal `:param` route syntax to Nitro's rou3\n * pattern format. Nitro uses `rou3` for routeRules matching, which\n * supports `*` (single-segment) and `**` (multi-segment) wildcards.\n *\n * /blog/:slug -> /blog/* (single segment)\n * /docs/:slug+ -> /docs/** (one or more segments — catch-all)\n * /docs/:slug* -> /docs/** (zero or more segments — optional catch-all)\n * /about -> /about (unchanged)\n * /:a/:b produces `/*`/`/*` (consecutive single-segment params)\n */\nexport function convertToNitroPattern(pattern: string): string {\n return pattern\n .split(\"/\")\n .map((segment) => {\n if (segment.startsWith(\":\")) {\n // Catch-all (:param+) and optional catch-all (:param*) match multiple segments → **\n // Single dynamic param (:param) matches one segment → *\n return segment.endsWith(\"+\") || segment.endsWith(\"*\") ? \"**\" : \"*\";\n }\n return segment;\n })\n .join(\"/\");\n}\n\nexport function mergeNitroRouteRules(\n existingRouteRules: Record<string, NitroRouteRuleConfig> | undefined,\n generatedRouteRules: NitroRouteRules,\n): {\n routeRules: Record<string, NitroRouteRuleConfig>;\n skippedRoutes: string[];\n} {\n const routeRules = { ...existingRouteRules };\n const skippedRoutes: string[] = [];\n\n for (const [route, generatedRule] of Object.entries(generatedRouteRules)) {\n const existingRule = routeRules[route];\n\n if (existingRule && hasUserDefinedCacheRule(existingRule)) {\n skippedRoutes.push(route);\n continue;\n }\n\n routeRules[route] = {\n ...existingRule,\n ...generatedRule,\n };\n }\n\n return { routeRules, skippedRoutes };\n}\n\nfunction hasUserDefinedCacheRule(rule: NitroRouteRuleConfig): boolean {\n return (\n rule.swr !== undefined ||\n rule.cache !== undefined ||\n rule.static !== undefined ||\n rule.isr !== undefined ||\n rule.prerender !== undefined\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA2BA,eAAsB,uBAAuB,SAIhB;CAC3B,MAAM,EAAE,QAAQ,gBAAgB,aAAa;CAE7C,IAAI,YAAwB,EAAE;CAC9B,IAAI,aAAsB,EAAE;CAC5B,IAAI,YAAqB,EAAE;AAE3B,KAAI,OACF,aAAY,MAAM,UAAU,QAAQ,eAAe;AAGrD,KAAI,UAAU;EACZ,MAAM,CAAC,OAAO,QAAQ,MAAM,QAAQ,IAAI,CACtC,YAAY,UAAU,eAAe,EACrC,UAAU,UAAU,eAAe,CACpC,CAAC;AACF,eAAa;AACb,cAAY;;AAGd,QAAO,wBAAwB,gBAAgB;EAAE;EAAW;EAAY;EAAW,CAAC,CAAC;;AAGvF,SAAgB,wBAAwB,MAAmC;CACzE,MAAM,QAAyB,EAAE;AAEjC,MAAK,MAAM,OAAO,KAChB,KACE,IAAI,SAAS,SACb,OAAO,IAAI,eAAe,YAC1B,OAAO,SAAS,IAAI,WAAW,IAC/B,IAAI,aAAa,EAEjB,OAAM,sBAAsB,IAAI,QAAQ,IAAI,EAAE,KAAK,IAAI,YAAY;AAIvE,QAAO;;;;;;;;;;;;;AAcT,SAAgB,sBAAsB,SAAyB;AAC7D,QAAO,QACJ,MAAM,IAAI,CACV,KAAK,YAAY;AAChB,MAAI,QAAQ,WAAW,IAAI,CAGzB,QAAO,QAAQ,SAAS,IAAI,IAAI,QAAQ,SAAS,IAAI,GAAG,OAAO;AAEjE,SAAO;GACP,CACD,KAAK,IAAI;;AAGd,SAAgB,qBACd,oBACA,qBAIA;CACA,MAAM,aAAa,EAAE,GAAG,oBAAoB;CAC5C,MAAM,gBAA0B,EAAE;AAElC,MAAK,MAAM,CAAC,OAAO,kBAAkB,OAAO,QAAQ,oBAAoB,EAAE;EACxE,MAAM,eAAe,WAAW;AAEhC,MAAI,gBAAgB,wBAAwB,aAAa,EAAE;AACzD,iBAAc,KAAK,MAAM;AACzB;;AAGF,aAAW,SAAS;GAClB,GAAG;GACH,GAAG;GACJ;;AAGH,QAAO;EAAE;EAAY;EAAe;;AAGtC,SAAS,wBAAwB,MAAqC;AACpE,QACE,KAAK,QAAQ,KAAA,KACb,KAAK,UAAU,KAAA,KACf,KAAK,WAAW,KAAA,KAChB,KAAK,QAAQ,KAAA,KACb,KAAK,cAAc,KAAA"}
@@ -0,0 +1,17 @@
1
+ //#region src/build/precompress.d.ts
2
+ type PrecompressResult = {
3
+ filesCompressed: number;
4
+ totalOriginalBytes: number; /** Sum of brotli-compressed sizes (used for compression ratio reporting). */
5
+ totalBrotliBytes: number;
6
+ };
7
+ /**
8
+ * Precompress all compressible hashed assets under `clientDir/assets/`.
9
+ *
10
+ * Writes `.br`, `.gz`, and `.zst` files alongside each original.
11
+ * Safe to re-run — overwrites existing compressed variants with identical
12
+ * output, and never compresses `.br`, `.gz`, or `.zst` files themselves.
13
+ */
14
+ declare function precompressAssets(clientDir: string, onProgress?: (completed: number, total: number, file: string) => void): Promise<PrecompressResult>;
15
+ //#endregion
16
+ export { PrecompressResult, precompressAssets };
17
+ //# sourceMappingURL=precompress.d.ts.map
@@ -0,0 +1,102 @@
1
+ import path from "node:path";
2
+ import fsp from "node:fs/promises";
3
+ import os from "node:os";
4
+ import zlib from "node:zlib";
5
+ import { promisify } from "node:util";
6
+ //#region src/build/precompress.ts
7
+ /**
8
+ * Build-time precompression for hashed static assets.
9
+ *
10
+ * Generates .br (brotli q5), .gz (gzip l8), and .zst (zstd l8) files
11
+ * alongside compressible assets in dist/client/assets/. Served directly by
12
+ * the production server — no per-request compression needed for immutable
13
+ * build output.
14
+ *
15
+ * Only targets assets/ (hashed, immutable) — public directory files use
16
+ * on-the-fly compression since they may change between deploys.
17
+ */
18
+ const brotliCompress = promisify(zlib.brotliCompress);
19
+ const gzip = promisify(zlib.gzip);
20
+ const zstdCompress = typeof zlib.zstdCompress === "function" ? promisify(zlib.zstdCompress) : null;
21
+ /** File extensions worth compressing (text-based, not already compressed). */
22
+ const COMPRESSIBLE_EXTENSIONS = new Set([
23
+ ".js",
24
+ ".mjs",
25
+ ".css",
26
+ ".html",
27
+ ".json",
28
+ ".xml",
29
+ ".svg",
30
+ ".txt",
31
+ ".map",
32
+ ".wasm"
33
+ ]);
34
+ /** Below this size, compression overhead exceeds savings. */
35
+ const MIN_SIZE = 1024;
36
+ /**
37
+ * Past ~8 parallel files, mixed-size asset sets spend more time queueing zlib
38
+ * work than making forward progress. Keep the batch size bounded even on
39
+ * higher-core machines.
40
+ */
41
+ const CONCURRENCY = Math.min(os.availableParallelism(), 8);
42
+ /**
43
+ * Walk a directory recursively, yielding relative paths for regular files.
44
+ */
45
+ async function* walkFiles(dir, base = dir) {
46
+ let entries;
47
+ try {
48
+ entries = await fsp.readdir(dir, { withFileTypes: true });
49
+ } catch {
50
+ return;
51
+ }
52
+ for (const entry of entries) {
53
+ const fullPath = path.join(dir, entry.name);
54
+ if (entry.isDirectory()) yield* walkFiles(fullPath, base);
55
+ else if (entry.isFile()) yield path.relative(base, fullPath);
56
+ }
57
+ }
58
+ /**
59
+ * Precompress all compressible hashed assets under `clientDir/assets/`.
60
+ *
61
+ * Writes `.br`, `.gz`, and `.zst` files alongside each original.
62
+ * Safe to re-run — overwrites existing compressed variants with identical
63
+ * output, and never compresses `.br`, `.gz`, or `.zst` files themselves.
64
+ */
65
+ async function precompressAssets(clientDir, onProgress) {
66
+ const assetsDir = path.join(clientDir, "assets");
67
+ const result = {
68
+ filesCompressed: 0,
69
+ totalOriginalBytes: 0,
70
+ totalBrotliBytes: 0
71
+ };
72
+ const filePaths = [];
73
+ for await (const relativePath of walkFiles(assetsDir)) {
74
+ const ext = path.extname(relativePath).toLowerCase();
75
+ if (!COMPRESSIBLE_EXTENSIONS.has(ext)) continue;
76
+ filePaths.push(path.join(assetsDir, relativePath));
77
+ }
78
+ let processed = 0;
79
+ for (let i = 0; i < filePaths.length; i += CONCURRENCY) {
80
+ const chunk = filePaths.slice(i, i + CONCURRENCY);
81
+ await Promise.all(chunk.map(async (fullPath) => {
82
+ const content = await fsp.readFile(fullPath);
83
+ if (content.length < MIN_SIZE) return;
84
+ const compressions = [brotliCompress(content, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 5 } }), gzip(content, { level: 8 })];
85
+ if (zstdCompress) compressions.push(zstdCompress(content, { params: { [zlib.constants.ZSTD_c_compressionLevel]: 8 } }));
86
+ const [brContent, gzContent, zstdContent] = await Promise.all(compressions);
87
+ const writes = [fsp.writeFile(fullPath + ".br", brContent), fsp.writeFile(fullPath + ".gz", gzContent)];
88
+ if (zstdContent) writes.push(fsp.writeFile(fullPath + ".zst", zstdContent));
89
+ await Promise.all(writes);
90
+ result.filesCompressed++;
91
+ result.totalOriginalBytes += content.length;
92
+ result.totalBrotliBytes += brContent.length;
93
+ }));
94
+ processed += chunk.length;
95
+ onProgress?.(processed, filePaths.length, path.basename(chunk[chunk.length - 1]));
96
+ }
97
+ return result;
98
+ }
99
+ //#endregion
100
+ export { precompressAssets };
101
+
102
+ //# sourceMappingURL=precompress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precompress.js","names":[],"sources":["../../src/build/precompress.ts"],"sourcesContent":["/**\n * Build-time precompression for hashed static assets.\n *\n * Generates .br (brotli q5), .gz (gzip l8), and .zst (zstd l8) files\n * alongside compressible assets in dist/client/assets/. Served directly by\n * the production server — no per-request compression needed for immutable\n * build output.\n *\n * Only targets assets/ (hashed, immutable) — public directory files use\n * on-the-fly compression since they may change between deploys.\n */\nimport fsp from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport zlib from \"node:zlib\";\nimport { promisify } from \"node:util\";\n\nconst brotliCompress = promisify(zlib.brotliCompress);\nconst gzip = promisify(zlib.gzip);\nconst zstdCompress = typeof zlib.zstdCompress === \"function\" ? promisify(zlib.zstdCompress) : null;\n\n/** File extensions worth compressing (text-based, not already compressed). */\nconst COMPRESSIBLE_EXTENSIONS = new Set([\n \".js\",\n \".mjs\",\n \".css\",\n \".html\",\n \".json\",\n \".xml\",\n \".svg\",\n \".txt\",\n \".map\",\n \".wasm\",\n]);\n\n/** Below this size, compression overhead exceeds savings. */\nconst MIN_SIZE = 1024;\n\n/**\n * Past ~8 parallel files, mixed-size asset sets spend more time queueing zlib\n * work than making forward progress. Keep the batch size bounded even on\n * higher-core machines.\n */\nconst CONCURRENCY = Math.min(os.availableParallelism(), 8);\n\nexport type PrecompressResult = {\n filesCompressed: number;\n totalOriginalBytes: number;\n /** Sum of brotli-compressed sizes (used for compression ratio reporting). */\n totalBrotliBytes: number;\n};\n\n/**\n * Walk a directory recursively, yielding relative paths for regular files.\n */\nasync function* walkFiles(dir: string, base: string = dir): AsyncGenerator<string> {\n let entries;\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return; // directory doesn't exist\n }\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkFiles(fullPath, base);\n } else if (entry.isFile()) {\n yield path.relative(base, fullPath);\n }\n }\n}\n\n/**\n * Precompress all compressible hashed assets under `clientDir/assets/`.\n *\n * Writes `.br`, `.gz`, and `.zst` files alongside each original.\n * Safe to re-run — overwrites existing compressed variants with identical\n * output, and never compresses `.br`, `.gz`, or `.zst` files themselves.\n */\nexport async function precompressAssets(\n clientDir: string,\n onProgress?: (completed: number, total: number, file: string) => void,\n): Promise<PrecompressResult> {\n const assetsDir = path.join(clientDir, \"assets\");\n const result: PrecompressResult = {\n filesCompressed: 0,\n totalOriginalBytes: 0,\n totalBrotliBytes: 0,\n };\n\n // Collect compressible file paths, then read + compress in bounded chunks\n // to keep peak memory at O(CONCURRENCY * max_file_size) instead of\n // O(total_assets).\n const filePaths: string[] = [];\n\n for await (const relativePath of walkFiles(assetsDir)) {\n const ext = path.extname(relativePath).toLowerCase();\n\n if (!COMPRESSIBLE_EXTENSIONS.has(ext)) continue;\n // .br/.gz/.zst are intentionally absent from COMPRESSIBLE_EXTENSIONS, so\n // precompressed variants generated by a previous run are never re-compressed.\n\n filePaths.push(path.join(assetsDir, relativePath));\n }\n\n let processed = 0;\n for (let i = 0; i < filePaths.length; i += CONCURRENCY) {\n const chunk = filePaths.slice(i, i + CONCURRENCY);\n await Promise.all(\n chunk.map(async (fullPath) => {\n const content = await fsp.readFile(fullPath);\n // readFile already done before this check — stat()-first would save\n // the read for tiny files but costs an extra syscall per file;\n // sub-1KB hashed assets are rare enough that read-first is cheaper.\n if (content.length < MIN_SIZE) return;\n\n // Compress all variants concurrently within each file\n const compressions: Promise<Buffer>[] = [\n brotliCompress(content, {\n params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 5 },\n }),\n gzip(content, { level: 8 }),\n ];\n if (zstdCompress) {\n compressions.push(\n zstdCompress(content, {\n params: { [zlib.constants.ZSTD_c_compressionLevel]: 8 },\n }),\n );\n }\n\n const results = await Promise.all(compressions);\n const [brContent, gzContent, zstdContent] = results;\n\n const writes = [\n fsp.writeFile(fullPath + \".br\", brContent),\n fsp.writeFile(fullPath + \".gz\", gzContent),\n ];\n if (zstdContent) {\n writes.push(fsp.writeFile(fullPath + \".zst\", zstdContent));\n }\n await Promise.all(writes);\n\n // Increment counters only after all writes succeed, so partial\n // failures (e.g. ENOSPC mid-write) don't inflate the reported totals.\n result.filesCompressed++;\n result.totalOriginalBytes += content.length;\n result.totalBrotliBytes += brContent.length;\n }),\n );\n // Report progress once per chunk to avoid non-deterministic ordering\n // within Promise.all (smaller files complete before larger ones).\n // Progress tracks all files (including skipped ones below MIN_SIZE),\n // which differs from filesCompressed (only files actually compressed).\n processed += chunk.length;\n onProgress?.(processed, filePaths.length, path.basename(chunk[chunk.length - 1]));\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,MAAM,iBAAiB,UAAU,KAAK,eAAe;AACrD,MAAM,OAAO,UAAU,KAAK,KAAK;AACjC,MAAM,eAAe,OAAO,KAAK,iBAAiB,aAAa,UAAU,KAAK,aAAa,GAAG;;AAG9F,MAAM,0BAA0B,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;AAGF,MAAM,WAAW;;;;;;AAOjB,MAAM,cAAc,KAAK,IAAI,GAAG,sBAAsB,EAAE,EAAE;;;;AAY1D,gBAAgB,UAAU,KAAa,OAAe,KAA6B;CACjF,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,IAAI,QAAQ,KAAK,EAAE,eAAe,MAAM,CAAC;SACnD;AACN;;AAEF,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,KAAK,KAAK,KAAK,MAAM,KAAK;AAC3C,MAAI,MAAM,aAAa,CACrB,QAAO,UAAU,UAAU,KAAK;WACvB,MAAM,QAAQ,CACvB,OAAM,KAAK,SAAS,MAAM,SAAS;;;;;;;;;;AAYzC,eAAsB,kBACpB,WACA,YAC4B;CAC5B,MAAM,YAAY,KAAK,KAAK,WAAW,SAAS;CAChD,MAAM,SAA4B;EAChC,iBAAiB;EACjB,oBAAoB;EACpB,kBAAkB;EACnB;CAKD,MAAM,YAAsB,EAAE;AAE9B,YAAW,MAAM,gBAAgB,UAAU,UAAU,EAAE;EACrD,MAAM,MAAM,KAAK,QAAQ,aAAa,CAAC,aAAa;AAEpD,MAAI,CAAC,wBAAwB,IAAI,IAAI,CAAE;AAIvC,YAAU,KAAK,KAAK,KAAK,WAAW,aAAa,CAAC;;CAGpD,IAAI,YAAY;AAChB,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,aAAa;EACtD,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI,YAAY;AACjD,QAAM,QAAQ,IACZ,MAAM,IAAI,OAAO,aAAa;GAC5B,MAAM,UAAU,MAAM,IAAI,SAAS,SAAS;AAI5C,OAAI,QAAQ,SAAS,SAAU;GAG/B,MAAM,eAAkC,CACtC,eAAe,SAAS,EACtB,QAAQ,GAAG,KAAK,UAAU,uBAAuB,GAAG,EACrD,CAAC,EACF,KAAK,SAAS,EAAE,OAAO,GAAG,CAAC,CAC5B;AACD,OAAI,aACF,cAAa,KACX,aAAa,SAAS,EACpB,QAAQ,GAAG,KAAK,UAAU,0BAA0B,GAAG,EACxD,CAAC,CACH;GAIH,MAAM,CAAC,WAAW,WAAW,eADb,MAAM,QAAQ,IAAI,aAAa;GAG/C,MAAM,SAAS,CACb,IAAI,UAAU,WAAW,OAAO,UAAU,EAC1C,IAAI,UAAU,WAAW,OAAO,UAAU,CAC3C;AACD,OAAI,YACF,QAAO,KAAK,IAAI,UAAU,WAAW,QAAQ,YAAY,CAAC;AAE5D,SAAM,QAAQ,IAAI,OAAO;AAIzB,UAAO;AACP,UAAO,sBAAsB,QAAQ;AACrC,UAAO,oBAAoB,UAAU;IACrC,CACH;AAKD,eAAa,MAAM;AACnB,eAAa,WAAW,UAAU,QAAQ,KAAK,SAAS,MAAM,MAAM,SAAS,GAAG,CAAC;;AAGnF,QAAO"}
@@ -5,10 +5,9 @@ import { readPrerenderSecret } from "./server-manifest.js";
5
5
  import { Server } from "node:http";
6
6
 
7
7
  //#region src/build/prerender.d.ts
8
- interface PrerenderResult {
9
- /** One entry per route (including skipped/error routes). */
10
- routes: PrerenderRouteResult[];
11
- }
8
+ type PrerenderResult = {
9
+ /** One entry per route (including skipped/error routes). */routes: PrerenderRouteResult[];
10
+ };
12
11
  type PrerenderRouteResult = {
13
12
  /** The route's file-system pattern, e.g. `/blog/:slug`. */route: string;
14
13
  status: "rendered";
@@ -37,13 +36,12 @@ type PrerenderProgressCallback = (update: {
37
36
  route: string; /** Its final status. */
38
37
  status: PrerenderRouteResult["status"];
39
38
  }) => void;
40
- interface PrerenderOptions {
39
+ type PrerenderOptions = {
41
40
  /**
42
41
  * 'default' — prerender static/ISR routes; skip SSR routes
43
42
  * 'export' — same as default but SSR routes are errors
44
43
  */
45
- mode: "default" | "export";
46
- /** Output directory for generated HTML/RSC files. */
44
+ mode: "default" | "export"; /** Output directory for generated HTML/RSC files. */
47
45
  outDir: string;
48
46
  /**
49
47
  * Directory where `vinext-prerender.json` is written.
@@ -51,8 +49,7 @@ interface PrerenderOptions {
51
49
  * Set this when the manifest should land in a different location than the
52
50
  * generated HTML/RSC files (e.g. `dist/server/` while HTML goes to `dist/server/prerendered-routes/`).
53
51
  */
54
- manifestDir?: string;
55
- /** Resolved next.config.js. */
52
+ manifestDir?: string; /** Resolved next.config.js. */
56
53
  config: ResolvedNextConfig;
57
54
  /**
58
55
  * Maximum number of routes rendered in parallel.
@@ -70,13 +67,10 @@ interface PrerenderOptions {
70
67
  * multiple phases and write a single unified manifest itself.
71
68
  */
72
69
  skipManifest?: boolean;
73
- }
74
- interface PrerenderPagesOptions extends PrerenderOptions {
75
- /** Discovered page routes (non-API). */
76
- routes: Route[];
77
- /** Discovered API routes. */
78
- apiRoutes: Route[];
79
- /** Pages directory path. */
70
+ };
71
+ type PrerenderPagesOptions = {
72
+ /** Discovered page routes (non-API). */routes: Route[]; /** Discovered API routes. */
73
+ apiRoutes: Route[]; /** Pages directory path. */
80
74
  pagesDir: string;
81
75
  /**
82
76
  * Absolute path to the pre-built Pages Router server bundle
@@ -86,15 +80,14 @@ interface PrerenderPagesOptions extends PrerenderOptions {
86
80
  * `runPrerender` passes a shared `_prodServer` instead.
87
81
  */
88
82
  pagesBundlePath?: string;
89
- }
90
- interface PrerenderAppOptions extends PrerenderOptions {
91
- /** Discovered app routes. */
92
- routes: AppRoute[];
83
+ } & PrerenderOptions;
84
+ type PrerenderAppOptions = {
85
+ /** Discovered app routes. */routes: AppRoute[];
93
86
  /**
94
87
  * Absolute path to the pre-built RSC handler bundle (e.g. `dist/server/index.js`).
95
88
  */
96
89
  rscBundlePath: string;
97
- }
90
+ } & PrerenderOptions;
98
91
  type PrerenderPagesOptionsInternal = PrerenderPagesOptions & {
99
92
  _prodServer?: {
100
93
  server: Server;
@@ -124,6 +117,18 @@ declare function buildUrlFromParams(pattern: string, params: Record<string, stri
124
117
  * Respects trailingSlash config.
125
118
  */
126
119
  declare function getOutputPath(urlPath: string, trailingSlash: boolean): string;
120
+ /** Map of route patterns to generateStaticParams functions (or null/undefined). */
121
+ type StaticParamsMap = Record<string, ((opts: {
122
+ params: Record<string, string | string[]>;
123
+ }) => Promise<Record<string, string | string[]>[]>) | null | undefined>;
124
+ /**
125
+ * Resolve parent dynamic segment params for a route.
126
+ * Handles top-down generateStaticParams resolution for nested dynamic routes.
127
+ *
128
+ * Uses the `staticParamsMap` (pattern → generateStaticParams) exported from
129
+ * the production bundle.
130
+ */
131
+ declare function resolveParentParams(childRoute: AppRoute, routeIndex: ReadonlyMap<string, AppRoute>, staticParamsMap: StaticParamsMap): Promise<Record<string, string | string[]>[]>;
127
132
  /**
128
133
  * Run the prerender phase for Pages Router.
129
134
  *
@@ -189,5 +194,5 @@ declare function writePrerenderIndex(routes: PrerenderRouteResult[], outDir: str
189
194
  trailingSlash?: boolean;
190
195
  }): void;
191
196
  //#endregion
192
- export { PrerenderAppOptions, PrerenderOptions, PrerenderPagesOptions, PrerenderProgressCallback, PrerenderResult, PrerenderRouteResult, buildUrlFromParams, getOutputPath, getRscOutputPath, prerenderApp, prerenderPages, readPrerenderSecret, writePrerenderIndex };
197
+ export { PrerenderAppOptions, PrerenderOptions, PrerenderPagesOptions, PrerenderProgressCallback, PrerenderResult, PrerenderRouteResult, StaticParamsMap, buildUrlFromParams, getOutputPath, getRscOutputPath, prerenderApp, prerenderPages, readPrerenderSecret, resolveParentParams, writePrerenderIndex };
193
198
  //# sourceMappingURL=prerender.d.ts.map
@@ -86,31 +86,30 @@ function getOutputPath(urlPath, trailingSlash) {
86
86
  * Uses the `staticParamsMap` (pattern → generateStaticParams) exported from
87
87
  * the production bundle.
88
88
  */
89
- async function resolveParentParams(childRoute, allRoutes, staticParamsMap) {
90
- const patternParts = childRoute.pattern.split("/").filter(Boolean);
89
+ async function resolveParentParams(childRoute, routeIndex, staticParamsMap) {
90
+ const { patternParts } = childRoute;
91
+ let lastDynamicIdx = -1;
92
+ for (let i = patternParts.length - 1; i >= 0; i--) if (patternParts[i].startsWith(":")) {
93
+ lastDynamicIdx = i;
94
+ break;
95
+ }
91
96
  const parentSegments = [];
92
- for (let i = 0; i < patternParts.length; i++) {
97
+ let prefixPattern = "";
98
+ for (let i = 0; i < lastDynamicIdx; i++) {
93
99
  const part = patternParts[i];
100
+ prefixPattern += "/" + part;
94
101
  if (!part.startsWith(":")) continue;
95
- if (!patternParts.slice(i + 1).some((p) => p.startsWith(":"))) break;
96
- const prefixPattern = "/" + patternParts.slice(0, i + 1).join("/");
97
- if (allRoutes.find((r) => r.pattern === prefixPattern)?.pagePath) {
102
+ if (routeIndex.get(prefixPattern)?.pagePath) {
98
103
  const fn = staticParamsMap[prefixPattern];
99
- if (typeof fn === "function") {
100
- const paramName = part.replace(/^:/, "").replace(/[+*]$/, "");
101
- parentSegments.push({
102
- params: [paramName],
103
- generateStaticParams: fn
104
- });
105
- }
104
+ if (typeof fn === "function") parentSegments.push(fn);
106
105
  }
107
106
  }
108
107
  if (parentSegments.length === 0) return [];
109
108
  let currentParams = [{}];
110
- for (const segment of parentSegments) {
109
+ for (const generateStaticParams of parentSegments) {
111
110
  const nextParams = [];
112
111
  for (const parentParams of currentParams) {
113
- const results = await segment.generateStaticParams({ params: parentParams });
112
+ const results = await generateStaticParams({ params: parentParams });
114
113
  if (Array.isArray(results)) for (const result of results) nextParams.push({
115
114
  ...parentParams,
116
115
  ...result
@@ -414,6 +413,7 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
414
413
  return false;
415
414
  }
416
415
  });
416
+ const routeIndex = new Map(routes.map((r) => [r.pattern, r]));
417
417
  const urlsToRender = [];
418
418
  for (const route of routes) {
419
419
  if (route.routePath && !route.pagePath) {
@@ -463,7 +463,7 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
463
463
  });
464
464
  continue;
465
465
  }
466
- const parentParamSets = await resolveParentParams(route, routes, staticParamsMap);
466
+ const parentParamSets = await resolveParentParams(route, routeIndex, staticParamsMap);
467
467
  let paramSets;
468
468
  if (parentParamSets.length > 0) {
469
469
  paramSets = [];
@@ -688,6 +688,6 @@ function writePrerenderIndex(routes, outDir, options) {
688
688
  fs.writeFileSync(path.join(outDir, "vinext-prerender.json"), JSON.stringify(index, null, 2), "utf-8");
689
689
  }
690
690
  //#endregion
691
- export { buildUrlFromParams, getOutputPath, getRscOutputPath, prerenderApp, prerenderPages, readPrerenderSecret, writePrerenderIndex };
691
+ export { buildUrlFromParams, getOutputPath, getRscOutputPath, prerenderApp, prerenderPages, readPrerenderSecret, resolveParentParams, writePrerenderIndex };
692
692
 
693
693
  //# sourceMappingURL=prerender.js.map