@wular/pnext 0.0.3 → 0.0.6

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 (392) hide show
  1. package/README.md +11 -11
  2. package/bin/pnext +1 -1
  3. package/config/lint/base.js +7 -7
  4. package/config/ts/base.json +2 -4
  5. package/config/ts/react.json +2 -6
  6. package/package.json +23 -2
  7. package/reference/compat.md +1 -1
  8. package/reference/config.md +2 -2
  9. package/reference/css.md +13 -9
  10. package/reference/dev.md +1 -1
  11. package/reference/metadata.md +7 -7
  12. package/reference/navigation.md +35 -28
  13. package/reference/performance.md +70 -70
  14. package/reference/rendering.md +17 -17
  15. package/reference/routing.md +17 -17
  16. package/reference/typegen.md +12 -8
  17. package/src/api/cache.ts +36 -37
  18. package/src/api/client-cache.ts +2 -2
  19. package/src/api/client-navigation.ts +113 -109
  20. package/src/api/dynamic.tsx +58 -55
  21. package/src/api/link.tsx +49 -52
  22. package/src/api/navigation.ts +59 -58
  23. package/src/api/server.ts +152 -144
  24. package/src/api/suspense.ts +4 -4
  25. package/src/cli/adapters/vercel-warm.ts +126 -121
  26. package/src/cli/adapters/vercel.ts +437 -443
  27. package/src/cli/analyze.ts +333 -144
  28. package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
  29. package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
  30. package/src/cli/build.ts +976 -978
  31. package/src/cli/create.ts +71 -63
  32. package/src/cli/dev.ts +116 -116
  33. package/src/cli/index.ts +88 -85
  34. package/src/cli/migrate/package-json.ts +73 -71
  35. package/src/cli/migrate/report.ts +32 -33
  36. package/src/cli/migrate/{index.ts → run.ts} +48 -49
  37. package/src/cli/migrate/scan.ts +46 -46
  38. package/src/cli/migrate/spinner.ts +9 -9
  39. package/src/cli/migrate/tsconfig.ts +35 -35
  40. package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
  41. package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
  42. package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
  43. package/src/cli/start.ts +65 -66
  44. package/src/{typegen.ts → cli/typegen.ts} +59 -59
  45. package/src/client/build.ts +794 -768
  46. package/src/client/chunk-fold.ts +245 -240
  47. package/src/client/{paths.ts → chunk-name.ts} +6 -6
  48. package/src/client/entry.ts +162 -147
  49. package/src/client/prebuilt.ts +231 -223
  50. package/src/client/profile.ts +29 -29
  51. package/src/client/react-compiler.ts +20 -15
  52. package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
  53. package/src/client/reference-stub.ts +51 -51
  54. package/src/client/reference.ts +19 -19
  55. package/src/{api → client}/router/events.ts +17 -17
  56. package/src/{api → client}/router/history.ts +16 -16
  57. package/src/{api → client}/router/hub.ts +52 -53
  58. package/src/{api/router.ts → client/router/index.ts} +61 -60
  59. package/src/{api → client}/router/policies.ts +24 -24
  60. package/src/{api → client}/router/runtime.ts +1988 -1961
  61. package/src/{api → client}/router/types.ts +93 -98
  62. package/src/compat/actions/client-plugin.ts +42 -43
  63. package/src/compat/actions/client-stub.ts +14 -14
  64. package/src/compat/actions/{action-client.ts → client.ts} +194 -193
  65. package/src/compat/actions/config.ts +63 -64
  66. package/src/compat/actions/detect.ts +68 -68
  67. package/src/compat/actions/discovery.ts +105 -105
  68. package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
  69. package/src/compat/actions/early-submit.ts +1 -1
  70. package/src/compat/actions/endpoint.ts +198 -198
  71. package/src/compat/actions/extensions.ts +811 -0
  72. package/src/compat/actions/flight.ts +23 -23
  73. package/src/compat/actions/form-state.ts +34 -34
  74. package/src/compat/actions/hoist.ts +382 -236
  75. package/src/compat/actions/ids.ts +7 -12
  76. package/src/compat/actions/index.ts +8 -8
  77. package/src/compat/actions/instances.ts +46 -46
  78. package/src/compat/actions/origin.ts +47 -48
  79. package/src/compat/actions/protocol.ts +22 -22
  80. package/src/compat/actions/registry.ts +19 -19
  81. package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
  82. package/src/compat/actions/rewrite.ts +125 -125
  83. package/src/compat/actions/{action-router.ts → router.ts} +10 -10
  84. package/src/compat/actions/serve.ts +132 -136
  85. package/src/compat/actions/server-tag.ts +4 -4
  86. package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
  87. package/src/compat/actions/unrecognized-error.ts +4 -4
  88. package/src/compat/{index.ts → aliases.ts} +112 -109
  89. package/src/compat/bundler/bun-externals.ts +18 -18
  90. package/src/compat/bundler/cjs-exports.ts +271 -223
  91. package/src/compat/bundler/config.ts +116 -111
  92. package/src/compat/bundler/externals.ts +12 -12
  93. package/src/compat/bundler/import-meta-url.ts +19 -19
  94. package/src/compat/bundler/modularize-imports.ts +36 -33
  95. package/src/compat/bundler/new-url-asset.ts +22 -24
  96. package/src/compat/bundler/optimize-package-imports.ts +111 -107
  97. package/src/compat/bundler/polyfill.ts +28 -28
  98. package/src/compat/bundler/react-compiler.ts +35 -29
  99. package/src/compat/bundler/react-profiler.tsx +11 -11
  100. package/src/compat/bundler/relay-transform.ts +48 -47
  101. package/src/compat/bundler/require-context.ts +119 -113
  102. package/src/compat/bundler/resolve-extensions.ts +19 -19
  103. package/src/compat/bundler/source-cache.ts +25 -25
  104. package/src/compat/bundler/static-imports.ts +7 -7
  105. package/src/compat/bundler/symlink-imports.ts +46 -46
  106. package/src/compat/bundler/tsconfig-paths.ts +13 -15
  107. package/src/compat/bundler/wasm.ts +58 -60
  108. package/src/compat/bundler/webpack-loaders.ts +254 -241
  109. package/src/compat/bundler/worker.ts +101 -104
  110. package/src/compat/cache/build-flags.ts +29 -29
  111. package/src/compat/cache/build-prerender-errors.ts +40 -44
  112. package/src/compat/cache/custom-handler.ts +60 -53
  113. package/src/compat/cache/fetch-patch.ts +240 -240
  114. package/src/compat/cache/handler.ts +27 -31
  115. package/src/compat/cache/modern-handler.ts +148 -126
  116. package/src/compat/cache/resume-data-cache.ts +47 -45
  117. package/src/compat/cache/revalidate.ts +233 -232
  118. package/src/compat/cache/runtime-error.ts +35 -35
  119. package/src/compat/cache/use-cache-transform.ts +442 -417
  120. package/src/compat/cache/use-cache.ts +650 -614
  121. package/src/compat/cache-control.ts +140 -142
  122. package/src/compat/client/base-path.ts +21 -20
  123. package/src/compat/client/css-order.ts +18 -18
  124. package/src/compat/client/errors/bare-boundary.ts +11 -11
  125. package/src/compat/client/errors/control-flow.ts +23 -23
  126. package/src/compat/client/errors/error-boundary.ts +61 -61
  127. package/src/compat/client/errors/global-error.ts +101 -91
  128. package/src/compat/client/errors/install.ts +71 -72
  129. package/src/compat/client/errors/lazy.ts +23 -23
  130. package/src/compat/client/errors/primitive-throw.ts +47 -45
  131. package/src/compat/client/errors/soft-refresh.ts +4 -4
  132. package/src/compat/client/link-status.ts +33 -33
  133. package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
  134. package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
  135. package/src/compat/client/navigation-scroll.ts +63 -63
  136. package/src/compat/client/optimistic-routing.ts +93 -88
  137. package/src/compat/client/prefetch-cache.ts +23 -24
  138. package/src/compat/client/route-announcer.ts +35 -35
  139. package/src/compat/client/segment-cache-policy.ts +20 -20
  140. package/src/compat/client/segment-cache.ts +309 -315
  141. package/src/compat/client/segment-prefetch.ts +127 -132
  142. package/src/compat/client/trailing-slash.ts +5 -4
  143. package/src/compat/css/chunking.ts +113 -116
  144. package/src/compat/css/inline-css.ts +21 -21
  145. package/src/compat/css/lightningcss.ts +37 -38
  146. package/src/compat/css/modules.ts +161 -175
  147. package/src/compat/css/nonce.ts +7 -7
  148. package/src/compat/css/sass-plugin.ts +18 -21
  149. package/src/compat/css/sass.ts +150 -152
  150. package/src/compat/css/styled-jsx-runtime.ts +27 -27
  151. package/src/compat/css/styled-jsx.ts +21 -21
  152. package/src/compat/edge-runtime.ts +27 -27
  153. package/src/compat/{adapter → export}/build-complete.ts +72 -75
  154. package/src/compat/export/client.ts +29 -31
  155. package/src/compat/export/{index.ts → emit.ts} +111 -110
  156. package/src/compat/export/standalone.ts +62 -54
  157. package/src/compat/image-optimizer/cache.ts +51 -49
  158. package/src/compat/image-optimizer/detect.ts +52 -52
  159. package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
  160. package/src/compat/image-optimizer/source.ts +77 -80
  161. package/src/compat/lifecycle/after-scope.ts +26 -26
  162. package/src/compat/lifecycle/after.ts +48 -45
  163. package/src/compat/lifecycle/error-funnel.ts +98 -102
  164. package/src/compat/lifecycle/error-serialize.ts +30 -26
  165. package/src/compat/lifecycle/error-ui.ts +67 -32
  166. package/src/compat/lifecycle/instrumentation-client.ts +36 -38
  167. package/src/compat/lifecycle/instrumentation.ts +85 -85
  168. package/src/compat/lifecycle/node-console.ts +11 -11
  169. package/src/compat/lifecycle/testmode.ts +160 -132
  170. package/src/compat/mdx/compile.ts +60 -57
  171. package/src/compat/mdx/plugin.ts +11 -11
  172. package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
  173. package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
  174. package/src/compat/metadata.ts +75 -81
  175. package/src/compat/next/cache.ts +68 -69
  176. package/src/compat/next/canonical-url.ts +9 -9
  177. package/src/compat/next/client-cache.ts +19 -19
  178. package/src/compat/next/client-navigation.ts +112 -116
  179. package/src/compat/next/client-only.ts +1 -1
  180. package/src/compat/next/client-script.tsx +99 -93
  181. package/src/compat/next/client-server.ts +10 -10
  182. package/src/compat/next/config-loader.ts +176 -173
  183. package/src/compat/next/config.ts +7 -7
  184. package/src/compat/next/constants.cjs +6 -6
  185. package/src/compat/next/constants.ts +6 -6
  186. package/src/compat/next/custom-server.ts +26 -24
  187. package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
  188. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
  189. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
  190. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
  191. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
  192. package/src/compat/next/dynamic.tsx +21 -18
  193. package/src/compat/next/error.tsx +52 -52
  194. package/src/compat/next/font/cache.ts +74 -74
  195. package/src/compat/next/font/google.ts +2 -2
  196. package/src/compat/next/font/index.ts +1 -1
  197. package/src/compat/next/font/local.ts +3 -3
  198. package/src/compat/next/font/runtime-client.ts +17 -15
  199. package/src/compat/next/font/runtime.ts +443 -408
  200. package/src/compat/next/font/shared.ts +120 -108
  201. package/src/compat/next/form.tsx +63 -63
  202. package/src/compat/next/head.tsx +2 -2
  203. package/src/compat/next/headers.ts +103 -95
  204. package/src/compat/next/image/client.tsx +220 -0
  205. package/src/compat/next/image/config.ts +56 -58
  206. package/src/compat/next/image/optimizer.ts +40 -36
  207. package/src/compat/next/image/patterns.ts +37 -40
  208. package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
  209. package/src/compat/next/image/shared.ts +65 -57
  210. package/src/compat/next/image/static-metadata.ts +98 -107
  211. package/src/compat/next/image/validate.ts +79 -88
  212. package/src/compat/next/image.tsx +19 -23
  213. package/src/compat/next/index.ts +1 -1
  214. package/src/compat/next/legacy-image.tsx +59 -60
  215. package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
  216. package/src/compat/next/link.tsx +158 -158
  217. package/src/compat/next/navigation.cjs +12 -3
  218. package/src/compat/next/navigation.ts +48 -50
  219. package/src/compat/next/offline.ts +27 -27
  220. package/src/compat/next/og.ts +121 -124
  221. package/src/compat/next/preferred-region.ts +13 -14
  222. package/src/compat/next/redirects.ts +58 -56
  223. package/src/compat/next/resource-hints.ts +73 -76
  224. package/src/compat/next/rewrites.ts +130 -133
  225. package/src/compat/next/root-params.ts +45 -45
  226. package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
  227. package/src/compat/next/router.cjs +4 -2
  228. package/src/compat/next/router.ts +58 -61
  229. package/src/compat/next/script.tsx +108 -108
  230. package/src/compat/next/server-only.ts +1 -1
  231. package/src/compat/next/server.ts +19 -19
  232. package/src/compat/next/svgr.ts +18 -17
  233. package/src/compat/next/telemetry.ts +24 -24
  234. package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
  235. package/src/compat/next/user-agent.ts +53 -49
  236. package/src/compat/next/web-vitals.ts +22 -24
  237. package/src/compat/otel/api.ts +41 -41
  238. package/src/compat/otel/client-trace-metadata.ts +25 -27
  239. package/src/compat/otel/fetch-span.ts +29 -29
  240. package/src/compat/otel/tracer.ts +331 -331
  241. package/src/compat/pages/api.ts +456 -0
  242. package/src/compat/pages/client-plugin.ts +36 -36
  243. package/src/compat/pages/router-state.ts +34 -34
  244. package/src/compat/pages/{index.ts → router.ts} +130 -135
  245. package/src/compat/ppr/io.ts +12 -12
  246. package/src/compat/ppr/missing-root-params.ts +34 -38
  247. package/src/compat/ppr/root-params-scan.ts +66 -66
  248. package/src/compat/ppr/root-params-transform.ts +24 -26
  249. package/src/compat/ppr/root-params.ts +30 -30
  250. package/src/compat/ppr/segment-config-incompat.ts +6 -7
  251. package/src/compat/protocol.ts +71 -70
  252. package/src/compat/react/action-state.ts +47 -48
  253. package/src/compat/react/client-lite.ts +15 -15
  254. package/src/compat/react/client.ts +4 -4
  255. package/src/compat/react/compiler-runtime.ts +11 -11
  256. package/src/compat/react/dom-client.ts +44 -44
  257. package/src/compat/react/dom-react-server.ts +10 -16
  258. package/src/compat/react/dom-server.ts +10 -10
  259. package/src/compat/react/dom.ts +52 -52
  260. package/src/compat/react/hooks-extra.ts +34 -35
  261. package/src/compat/react/parity.ts +64 -61
  262. package/src/compat/react/preact.ts +81 -82
  263. package/src/compat/react/react-server.ts +28 -28
  264. package/src/compat/react/router-shim.ts +1 -1
  265. package/src/compat/react/server-component-use.ts +8 -8
  266. package/src/compat/react/server-inserted-html.ts +30 -31
  267. package/src/compat/react/server.ts +53 -55
  268. package/src/compat/react/use.ts +32 -32
  269. package/src/compat/react/view-transition.ts +20 -20
  270. package/src/compat/register/actions.ts +35 -824
  271. package/src/compat/register/boot.ts +41 -41
  272. package/src/compat/register/build-tier.ts +5 -5
  273. package/src/compat/register/build.ts +74 -70
  274. package/src/compat/register/bundler.ts +161 -156
  275. package/src/compat/register/cache.ts +20 -20
  276. package/src/compat/register/client-errors.ts +3 -3
  277. package/src/compat/register/config.ts +6 -6
  278. package/src/compat/register/css-extras.ts +30 -34
  279. package/src/compat/register/edge-runtime.ts +3 -3
  280. package/src/compat/register/errors.ts +10 -12
  281. package/src/compat/register/export.ts +16 -16
  282. package/src/compat/register/font.ts +11 -11
  283. package/src/compat/register/hooks.ts +2 -2
  284. package/src/compat/register/image.ts +40 -40
  285. package/src/compat/register/index.ts +59 -62
  286. package/src/compat/register/instrumentation-client.ts +10 -10
  287. package/src/compat/register/lifecycle.ts +25 -28
  288. package/src/compat/register/mdx.ts +10 -10
  289. package/src/compat/register/middleware.ts +226 -16
  290. package/src/compat/register/otel.ts +80 -88
  291. package/src/compat/register/pages-api.ts +10 -463
  292. package/src/compat/register/ppr.ts +16 -16
  293. package/src/compat/register/protocol.ts +17 -18
  294. package/src/compat/register/proxy.ts +49 -51
  295. package/src/compat/register/render.ts +79 -76
  296. package/src/compat/register/routing.ts +162 -159
  297. package/src/compat/register/segment.ts +24 -1887
  298. package/src/compat/register/static-image.ts +3 -3
  299. package/src/compat/register/{misc.ts → taint.ts} +9 -9
  300. package/src/compat/register/typed-routes.ts +14 -14
  301. package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
  302. package/src/compat/register/validation.ts +18 -18
  303. package/src/compat/segment/loading-boundary.ts +43 -45
  304. package/src/compat/segment/page-slot.ts +69 -69
  305. package/src/compat/segment/serve.ts +1884 -0
  306. package/src/compat/segment/tree.ts +113 -112
  307. package/src/compat/segment/vary-key.ts +38 -38
  308. package/src/compat/segment/vary-params.ts +138 -142
  309. package/src/compat/static-params.ts +14 -12
  310. package/src/compat/tsconfig-defaults.ts +87 -91
  311. package/src/compat/typecheck/{index.ts → check.ts} +234 -212
  312. package/src/compat/typecheck/worker.ts +15 -12
  313. package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
  314. package/src/compat/typed-routes/manifest.ts +174 -170
  315. package/src/compat/typed-routes/typegen.ts +127 -110
  316. package/src/compat/validation/errors.ts +16 -19
  317. package/src/compat/validation/prerender-diagnostics.ts +521 -504
  318. package/src/compat/validation/{index.ts → validate.ts} +647 -648
  319. package/src/compat-bootstrap.ts +16 -16
  320. package/src/config.ts +69 -69
  321. package/src/css/build.ts +230 -227
  322. package/src/css/postcss.ts +79 -80
  323. package/src/css/worker.ts +14 -15
  324. package/src/dev/client-actions.ts +10 -10
  325. package/src/dev/client-chunk-store.ts +27 -27
  326. package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
  327. package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
  328. package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
  329. package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
  330. package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
  331. package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
  332. package/src/dev/server.ts +804 -820
  333. package/src/env.ts +46 -43
  334. package/src/extensions.ts +491 -478
  335. package/src/index.ts +8 -8
  336. package/src/internal.ts +20 -23
  337. package/src/{islands → render}/boundary-error.ts +3 -3
  338. package/src/render/hooks.ts +71 -71
  339. package/src/render/island-context.ts +14 -14
  340. package/src/render/metadata.ts +310 -310
  341. package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
  342. package/src/{ppr.ts → render/ppr.ts} +244 -245
  343. package/src/render/renderer.ts +2076 -2082
  344. package/src/render/resource-hints.ts +16 -17
  345. package/src/render/slots.tsx +224 -235
  346. package/src/{islands → render}/static-children.ts +9 -12
  347. package/src/{islands → render}/static-slots.ts +37 -37
  348. package/src/{cache/context.ts → request/cache.ts} +20 -20
  349. package/src/request/context.ts +107 -107
  350. package/src/{dynamic/source.ts → resolve/dynamic.ts} +152 -143
  351. package/src/resolve/engine.ts +90 -77
  352. package/src/resolve/imports.ts +475 -463
  353. package/src/resolve/scan-facts.ts +442 -186
  354. package/src/resolve/source-text.ts +37 -37
  355. package/src/{dynamic → resolve}/tree-shake.ts +132 -128
  356. package/src/routing/forwarded.ts +19 -19
  357. package/src/routing/handler.ts +84 -91
  358. package/src/routing/href.ts +69 -70
  359. package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
  360. package/src/{proxy.ts → routing/proxy.ts} +306 -312
  361. package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
  362. package/src/routing/routes.ts +833 -848
  363. package/src/routing/slots.ts +164 -160
  364. package/src/runtime/loader.ts +952 -0
  365. package/src/{dev → runtime}/module-cache.ts +309 -287
  366. package/src/{dev → runtime}/module-generations.ts +9 -9
  367. package/src/{dev → runtime}/module-transform.ts +81 -72
  368. package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
  369. package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
  370. package/src/runtime/vendor.ts +425 -404
  371. package/src/styles.d.ts +9 -0
  372. package/src/types.ts +320 -335
  373. package/src/utils/ansi.ts +5 -5
  374. package/src/utils/{source.ts → code.ts} +15 -12
  375. package/src/utils/content-type.ts +3 -3
  376. package/src/utils/decode.ts +2 -2
  377. package/src/utils/dev-profile.ts +13 -13
  378. package/src/utils/error-log.ts +6 -6
  379. package/src/utils/esbuild.ts +18 -18
  380. package/src/utils/fs-cache.ts +13 -13
  381. package/src/utils/fs.ts +57 -49
  382. package/src/utils/html.ts +20 -24
  383. package/src/utils/native-require.ts +8 -8
  384. package/src/utils/serialize.ts +139 -146
  385. package/src/utils/verbose.ts +18 -18
  386. package/src/cli/analyze-print.ts +0 -181
  387. package/src/compat/middleware/manifest.ts +0 -210
  388. package/src/compat/next/image-client.tsx +0 -215
  389. package/src/compat/next/link-usage.ts +0 -29
  390. package/src/css/index.ts +0 -2
  391. package/src/render/index.ts +0 -1
  392. package/src/style-modules.d.ts +0 -9
package/src/cli/build.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { copyFile, mkdir, rename, writeFile } from 'node:fs/promises';
2
- import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
3
- import { createRequire } from 'node:module';
4
- import path from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import { writeVercelOutput } from './adapters/vercel';
7
- import { startWarmChild } from './adapters/vercel-warm';
8
- import { devOutSegment, loadConfig, pathToFileHref } from '../config';
9
- import { bootstrapCompat } from '../compat-bootstrap';
1
+ import { copyFile, mkdir, rename, writeFile } from 'node:fs/promises'
2
+ import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs'
3
+ import { createRequire } from 'node:module'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { writeVercelOutput } from './adapters/vercel'
7
+ import { startWarmChild } from './adapters/vercel-warm'
8
+ import { devOutSegment, loadConfig, pathToFileHref } from '../config'
9
+ import { bootstrapCompat } from '../compat-bootstrap'
10
10
  import {
11
11
  buildParallelPhaseError,
12
12
  clearBuildParallelPhases,
@@ -18,13 +18,13 @@ import {
18
18
  getRenderExtensions,
19
19
  runInitHooks,
20
20
  withRouteRuntime,
21
- } from '../extensions';
22
- import { buildClientEntries, emitStaticClientChunks, startClientSources } from '../client/build';
23
- import { beginSourceScope, endSourceScope, sourceCacheStats } from '../resolve/source-text';
24
- import { flushDevModuleCaches } from '../dev/module-cache';
25
- import { scanFactsStats } from '../resolve/scan-facts';
26
- import { clientEntryName } from '../client/paths';
27
- import { registerServerRuntime, serverBundleTargetForRuntime } from '../runtime/server';
21
+ } from '../extensions'
22
+ import { buildClientEntries, emitStaticClientChunks, startClientSources } from '../client/build'
23
+ import { beginSourceScope, endSourceScope, sourceCacheStats } from '../resolve/source-text'
24
+ import { flushDevModuleCaches } from '../runtime/module-cache'
25
+ import { scanFactsStats } from '../resolve/scan-facts'
26
+ import { clientEntryName } from '../client/chunk-name'
27
+ import { registerServerRuntime, serverBundleTargetForRuntime } from '../runtime/loader'
28
28
  import {
29
29
  buildClientReferenceCss,
30
30
  buildGlobalCss,
@@ -33,8 +33,8 @@ import {
33
33
  prepareRouteCssChunks,
34
34
  registerCssRuntime,
35
35
  warmCssPipeline,
36
- } from '../css';
37
- import { ensureEmptyDir, listFiles, readText, toPosixPath, writeText } from '../utils/fs';
36
+ } from '../css/build'
37
+ import { ensureEmptyDir, listFiles, readText, toPosixPath, writeText } from '../utils/fs'
38
38
  import {
39
39
  discoverStaticMetadataFiles,
40
40
  metadataRouteHandlerModule,
@@ -45,8 +45,8 @@ import {
45
45
  staticRouteMetadataKey,
46
46
  withDynamicMetadataRoutes,
47
47
  type StaticMetadataFile,
48
- } from '../routing/metadata';
49
- import { readModuleMetadata, readModuleViewport } from '../render/metadata';
48
+ } from '../routing/metadata-files'
49
+ import { readModuleMetadata, readModuleViewport } from '../render/metadata'
50
50
  import {
51
51
  defaultNotFoundDocument,
52
52
  pprShellPath,
@@ -57,33 +57,33 @@ import {
57
57
  renderPartialShell,
58
58
  renderSubShell,
59
59
  staticParamsFor,
60
- } from '../render';
60
+ } from '../render/renderer'
61
61
  import {
62
62
  abortActivePrerenderScopes,
63
63
  beginShellSourceTracking,
64
64
  cacheComponents,
65
65
  endShellSourceTracking,
66
- } from '../ppr';
67
- import { handleRouteModule, staticRouteParams, type RouteHandlerModule } from '../routing/handler';
68
- import { runWithCacheScope } from '../cache/context';
69
- import { devServerModuleHref, setEmitCompiledSpecifiersManifest } from '../dev/imports';
66
+ } from '../render/ppr'
67
+ import { handleRouteModule, staticRouteParams, type RouteHandlerModule } from '../routing/handler'
68
+ import { runWithCacheScope } from '../request/cache'
69
+ import { devServerModuleHref, setEmitCompiledSpecifiersManifest } from '../runtime/modules'
70
70
  import {
71
71
  beginDynamicBailoutProbe,
72
72
  endDynamicBailoutProbe,
73
73
  runWithWorkUnit,
74
- } from '../request/context';
75
- import { findProxyFile, proxyExternalLoadTarget, validateProxyFiles } from '../proxy';
76
- import { setRequestRuntime } from '../routing/request-runtime';
74
+ } from '../request/context'
75
+ import { findProxyFile, proxyExternalLoadTarget, validateProxyFiles } from '../routing/proxy'
76
+ import { setRequestRuntime } from '../routing/request-environment'
77
77
  import {
78
78
  addClientEntryReason,
79
79
  findLayouts,
80
80
  materializeRouteFacts,
81
81
  scanRoutes,
82
- } from '../routing/routes';
83
- import { interceptionMarkerLevels } from '../routing/slots';
84
- import { writeTypegen } from '../typegen';
85
- import { createVerboseLogger, type VerboseLogger } from '../utils/verbose';
86
- import { bold, cyan, dim, green } from '../utils/ansi';
82
+ } from '../routing/routes'
83
+ import { interceptionMarkerLevels } from '../routing/slots'
84
+ import { writeTypegen } from './typegen'
85
+ import { createVerboseLogger, type VerboseLogger } from '../utils/verbose'
86
+ import { bold, cyan, dim, green } from '../utils/ansi'
87
87
  import type {
88
88
  ActionManifestEntry,
89
89
  BuildManifest,
@@ -92,123 +92,123 @@ import type {
92
92
  StaticFileMetadata,
93
93
  StaticModuleMetadata,
94
94
  StaticRouteMetadata,
95
- } from '../types';
95
+ } from '../types'
96
96
 
97
97
  /** Mutable accumulator handed to compat build steps as their `manifest` ctx. */
98
98
  interface BuildStepState {
99
- actions: ActionManifestEntry[];
99
+ actions: ActionManifestEntry[]
100
100
  /** Root-relative 'use server' module paths — known from discovery, so the
101
101
  * client stage keys off this instead of waiting for their compile. */
102
- actionSources?: string[];
102
+ actionSources?: string[]
103
103
  /** First-party files that import a node_modules action module (absolute paths) — see
104
104
  * ActionDiscovery.actionImporters; route.sourceFiles never reaches into node_modules itself. */
105
- actionImporters?: string[];
105
+ actionImporters?: string[]
106
106
  /** Work a step handed back rather than finishing inline; awaited before the
107
107
  * build manifest is written, so it lands under the client stage. */
108
- deferred?: Promise<void>;
108
+ deferred?: Promise<void>
109
109
  }
110
110
 
111
111
  type CacheLifeStash = ReturnType<typeof getBuildExtensions>['compat'] extends {
112
- takeCacheLifeStash: () => infer T;
112
+ takeCacheLifeStash: () => infer T
113
113
  }
114
114
  ? NonNullable<T>
115
- : never;
116
- type SegmentMeta = ReturnType<ReturnType<typeof getBuildExtensions>['compat']['buildSegmentMeta']>;
115
+ : never
116
+ type SegmentMeta = ReturnType<ReturnType<typeof getBuildExtensions>['compat']['buildSegmentMeta']>
117
117
 
118
118
  function nextCompatEnabled(config: Awaited<ReturnType<typeof loadConfig>>) {
119
- return getCompatModeExtensions().nextEnabled(config);
119
+ return getCompatModeExtensions().nextEnabled(config)
120
120
  }
121
121
 
122
122
  function reactCompatEnabled(config: Awaited<ReturnType<typeof loadConfig>>) {
123
- return getCompatModeExtensions().reactEnabled(config);
123
+ return getCompatModeExtensions().reactEnabled(config)
124
124
  }
125
125
 
126
126
  function buildCompat() {
127
- return getBuildExtensions().compat;
127
+ return getBuildExtensions().compat
128
128
  }
129
129
 
130
130
  interface BuildOptions {
131
- adapter?: 'vercel';
132
- verbose?: boolean;
131
+ adapter?: 'vercel'
132
+ verbose?: boolean
133
133
  /**
134
134
  * `next build --debug-build-paths <glob>` parity: restrict the build to the route files matching
135
135
  * the (comma-separated) pattern(s), relative to the project root.
136
136
  */
137
- debugBuildPaths?: string;
137
+ debugBuildPaths?: string
138
138
  /**
139
139
  * `next build --experimental-build-mode compile|generate` parity: `compile`
140
140
  * bundles without prerendering; `generate` runs the prerender/export pass
141
141
  * with Next-shaped page-data output (and fails with Next's exact
142
142
  * blocking-prerender diagnostics under cacheComponents).
143
143
  */
144
- buildMode?: 'compile' | 'generate';
144
+ buildMode?: 'compile' | 'generate'
145
145
  /** `next build --debug-prerender`: unminified prerender stacks + codeframes. */
146
- debugPrerender?: boolean;
146
+ debugPrerender?: boolean
147
147
  }
148
148
 
149
149
  export async function buildProject(root?: string, options: BuildOptions = {}) {
150
- clearBuildParallelPhases();
150
+ clearBuildParallelPhases()
151
151
  // One read per source for the whole build (route-fact walk, action discovery,
152
152
  // client loader), released here so nothing survives the build.
153
- beginSourceScope();
153
+ beginSourceScope()
154
154
  // Only the vercel adapter's trace step reads the per-artifact specifier
155
155
  // sidecars; every other build path pays nothing for them.
156
- const restoreSpecifiersManifest = setEmitCompiledSpecifiersManifest(options.adapter === 'vercel');
156
+ const restoreSpecifiersManifest = setEmitCompiledSpecifiersManifest(options.adapter === 'vercel')
157
157
  try {
158
- return await runBuild(root, options);
158
+ return await runBuild(root, options)
159
159
  } catch (error) {
160
160
  // A parallel phase that already failed (a type error) is the root cause of
161
161
  // whatever broke downstream; report it instead of the symptom.
162
- const phaseError = buildParallelPhaseError();
163
- throw phaseError ?? error;
162
+ const phaseError = buildParallelPhaseError()
163
+ throw phaseError ?? error
164
164
  } finally {
165
- endSourceScope();
166
- restoreSpecifiersManifest();
167
- flushDevModuleCaches();
165
+ endSourceScope()
166
+ restoreSpecifiersManifest()
167
+ flushDevModuleCaches()
168
168
  }
169
169
  }
170
170
 
171
171
  async function runBuild(root: string | undefined, options: BuildOptions) {
172
- const verbose = options.verbose ?? false;
173
- const log = createVerboseLogger(verbose, 'build');
174
- const startedAt = performance.now();
172
+ const verbose = options.verbose ?? false
173
+ const log = createVerboseLogger(verbose, 'build')
174
+ const startedAt = performance.now()
175
175
 
176
176
  console.log(
177
177
  `${cyan('▲')} ${bold('pnext')} ${dim('— Creating an optimized production build ...\n')}`,
178
- );
179
- const config = await loadConfig(root);
178
+ )
179
+ const config = await loadConfig(root)
180
180
  // Load Tailwind on the CSS worker while the steps below run, so the first
181
181
  // stylesheet doesn't pay its cold boot.
182
- warmCssPipeline(config);
182
+ warmCssPipeline(config)
183
183
  // Compat plugin loader: the single gated seam that populates the core
184
184
  // extension registries when compat is enabled (no-op for pure-core apps).
185
185
  // A build compiles immediately, so it takes both tiers up front.
186
- await bootstrapCompat(config);
187
- log.log(`config loaded — out ${path.relative(config.root, config.outPath) || '.'}`);
186
+ await bootstrapCompat(config)
187
+ log.log(`config loaded — out ${path.relative(config.root, config.outPath) || '.'}`)
188
188
  // Run registered init hooks (compat installs the Next fetch-cache patch so prerenders observe
189
189
  // force-cache / next: { revalidate, tags }). No-op for pure-core apps. `build: true` keeps
190
190
  // server-boot-only hooks out of the build - Next never runs register() during `next build`, so
191
191
  // build prerenders must see a noop tracer.
192
- runInitHooks(config, { build: true });
192
+ runInitHooks(config, { build: true })
193
193
  // The vercel adapter's warm pass runs in its own process; start it here so
194
194
  // its boot overlaps the build and only the warming itself lands on the
195
195
  // adapter step.
196
- const warm = options.adapter === 'vercel' ? startWarmChild(config) : undefined;
196
+ const warm = options.adapter === 'vercel' ? startWarmChild(config) : undefined
197
197
  await log.step('prepare output directory', async () => {
198
198
  // Build-owned outputs only: `<outRoot>/dev` belongs to a possibly-running
199
199
  // dev server and must survive.
200
- await ensureEmptyDir(config.outPath, [devOutSegment]);
201
- await copyPublicDir(config.publicPath, path.join(config.outPath, 'public'));
202
- });
200
+ await ensureEmptyDir(config.outPath, [devOutSegment])
201
+ await copyPublicDir(config.publicPath, path.join(config.outPath, 'public'))
202
+ })
203
203
  // Prebundled server entry for `pnext start`: framework-only, independent of the
204
204
  // app build, so it runs in a child process for the whole build — its bundling
205
205
  // heap never stacks on the build's peak RSS and its wall hides under the build.
206
206
  // Best-effort — a failure only costs start time. Awaited before the summary.
207
- const serverEntryDone = import('./server-entry')
207
+ const serverEntryDone = import('./serve/entry')
208
208
  .then(entry => entry.emitServerEntryChild(config.outPath))
209
209
  .catch((error: Error) => {
210
- console.warn(`pnext build: server entry bundling skipped — ${error.message}`);
211
- });
210
+ console.warn(`pnext build: server entry bundling skipped — ${error.message}`)
211
+ })
212
212
  // The document-level stylesheets run their postcss/Tailwind pass on the CSS
213
213
  // worker, so they overlap with the route scan below instead of serializing
214
214
  // ahead of it. Awaited before prepareRouteCssChunks — route CSS still builds
@@ -217,50 +217,50 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
217
217
  .then(() => buildGlobalCss(config, { verbose }))
218
218
  // 404/not-found documents reference their own CSS chunk; emit it alongside
219
219
  // the global chunk so the synthetic not-found routes don't 404 their styles.
220
- .then(() => buildNotFoundCss(config, { verbose }));
220
+ .then(() => buildNotFoundCss(config, { verbose }))
221
221
  // Nothing awaits it until below; park the rejection so a CSS failure reports
222
222
  // at that await instead of as an unhandled rejection mid-scan.
223
- documentCss.catch(() => undefined);
223
+ documentCss.catch(() => undefined)
224
224
  // experimental.nextScriptWorkers: copy the Partytown library so `worker`
225
225
  // scripts (rewritten to type="text/partytown") can load it from
226
226
  // /_next/static/~partytown/. Tolerates the package being absent. Nothing
227
227
  // downstream reads it, so it runs beside the scan instead of ahead of it.
228
- const partytownLib = log.step('partytown lib', () => copyPartytownLib(config));
229
- partytownLib.catch(() => undefined);
228
+ const partytownLib = log.step('partytown lib', () => copyPartytownLib(config))
229
+ partytownLib.catch(() => undefined)
230
230
 
231
- let routes = await log.step('scan routes', () => scanRoutes(config.appPath));
232
- log.log(`found ${routes.length} route${routes.length === 1 ? '' : 's'}`);
231
+ let routes = await log.step('scan routes', () => scanRoutes(config.appPath))
232
+ log.log(`found ${routes.length} route${routes.length === 1 ? '' : 's'}`)
233
233
  if (options.debugBuildPaths) {
234
- routes = filterDebugBuildRoutes(config.root, routes, options.debugBuildPaths);
234
+ routes = filterDebugBuildRoutes(config.root, routes, options.debugBuildPaths)
235
235
  log.log(
236
236
  `--debug-build-paths ${options.debugBuildPaths} — ${routes.length} route${routes.length === 1 ? '' : 's'} kept`,
237
- );
237
+ )
238
238
  }
239
239
  // The page routes' server modules are the bulk of the adapter's warm pass and
240
240
  // depend on nothing but the scan; hand them over now so the child compiles
241
241
  // them under the client stage instead of after it.
242
- warm?.prewarm(routes.filter(route => route.kind === 'page').map(route => route.file));
242
+ warm?.prewarm(routes.filter(route => route.kind === 'page').map(route => route.file))
243
243
  // Compat build steps (action discovery/bundling + server-reference manifest)
244
244
  // populate their output onto this accumulator. Pure-core registers no steps.
245
- const buildState: BuildStepState = { actions: [] };
246
- const stepContext = { config, routes, manifest: buildState, log };
247
- const { steps } = getBuildExtensions();
245
+ const buildState: BuildStepState = { actions: [] }
246
+ const stepContext = { config, routes, manifest: buildState, log }
247
+ const { steps } = getBuildExtensions()
248
248
  // Steps that declare themselves route-fact-independent (action discovery) run UNDER the scan below
249
249
  // rather than after it, which would make both strictly serial.
250
250
  const earlySteps = runBuildSteps(
251
251
  steps.filter(step => step.early),
252
252
  stepContext,
253
- );
253
+ )
254
254
  // Nothing awaits it until the client-entry set is computed; park the rejection
255
255
  // so a discovery failure reports there instead of as an unhandled rejection.
256
- earlySteps.catch(() => undefined);
256
+ earlySteps.catch(() => undefined)
257
257
  // The table arrives with its content facts deferred (dev boots on paths
258
258
  // alone); a build needs all of them, and needs their scan errors up front.
259
259
  // The client stage needs nothing but each route's client file list, so it
260
260
  // starts here, route by route as the facts land: the transform chain and the
261
261
  // React Compiler run on oxc's threadpool under the rest of the build instead
262
262
  // of inside the client stage's own wall.
263
- const clientSources = startClientSources(config);
263
+ const clientSources = startClientSources(config)
264
264
  // Unpaced on purpose: the walk holds the loop for ~0.55 s and action
265
265
  // discovery beside it cannot resume, but pacing it (setImmediate per route)
266
266
  // measures a wash — discovery's 0.6 s of starvation is hidden entirely inside
@@ -269,27 +269,25 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
269
269
  'scan route facts',
270
270
  // eslint-disable-next-line @typescript-eslint/require-await
271
271
  async () => materializeRouteFacts(routes, route => clientSources.warmRoutes([route])),
272
- );
272
+ )
273
273
  // Render-time extensions read the route table from the request runtime (e.g.
274
274
  // compat's static-sibling route state); publish it for the prerender pass the
275
275
  // same way the serve handlers do.
276
- setRequestRuntime({ config, routes, dev: false });
276
+ setRequestRuntime({ config, routes, dev: false })
277
277
  // Compute the compat CSS-chunk plan across all routes up front (Next's CSS chunking needs the
278
278
  // global picture); buildRouteCss then emits one asset per planned slice. No-op for pure-core apps.
279
279
  // The plan is derived from the route table alone, so it does NOT wait on the document stylesheets
280
280
  // - those are awaited just before the first route stylesheet is emitted, which lets a Tailwind
281
281
  // pass run under the build steps and the client bundles instead of ahead of them.
282
- prepareRouteCssChunks(routes);
282
+ prepareRouteCssChunks(routes)
283
283
  // output:'export' with trailingSlash:false lays prerendered pages out flat
284
284
  // (`/a.html` rather than `/a/index.html`). Only meaningful under compat (the
285
285
  // Next config carries `output`); pure-core apps always use the dir layout.
286
286
  const flatExportLayout =
287
- nextCompatEnabled(config) &&
288
- buildCompat().nextOutputExport() &&
289
- config.trailingSlash !== true;
287
+ nextCompatEnabled(config) && buildCompat().nextOutputExport() && config.trailingSlash !== true
290
288
  // The proxy bundle is independent of the remaining build steps; overlap them.
291
- const proxyBuild = log.step('proxy module', () => buildProxyModule(config));
292
- proxyBuild.catch(() => undefined);
289
+ const proxyBuild = log.step('proxy module', () => buildProxyModule(config))
290
+ proxyBuild.catch(() => undefined)
293
291
  // Gate steps (validation) run here so their diagnostics still precede any
294
292
  // failure the client stage or the not-found prerender would raise for the
295
293
  // same broken app; every other step is a manifest write nothing downstream
@@ -297,27 +295,27 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
297
295
  await runBuildSteps(
298
296
  steps.filter(step => !step.early && step.gate),
299
297
  stepContext,
300
- );
298
+ )
301
299
  // Static metadata depends on the scan, not on action discovery — collect and
302
300
  // copy it while the early steps finish rather than after them.
303
- const staticFiles: Record<string, StaticFileMetadata> = {};
301
+ const staticFiles: Record<string, StaticFileMetadata> = {}
304
302
  const staticMetadata = (async () => {
305
303
  const files = await log.step('discover static metadata files', () =>
306
304
  Promise.resolve(discoverStaticMetadataFiles(config.appPath)),
307
- );
305
+ )
308
306
  await log.step('static metadata files', () =>
309
307
  copyStaticMetadataFiles(config, staticFiles, files),
310
- );
311
- return files;
312
- })();
313
- staticMetadata.catch(() => undefined);
308
+ )
309
+ return files
310
+ })()
311
+ staticMetadata.catch(() => undefined)
314
312
  // Action discovery arms the client-stub set buildClientEntries consumes, so
315
313
  // this is the point the early steps have to have landed by. What the step
316
314
  // deferred is not part of that arming and is awaited further below.
317
- await earlySteps;
318
- const deferredSteps = buildState.deferred ?? Promise.resolve();
319
- deferredSteps.catch(() => undefined);
320
- const staticMetadataFiles = await staticMetadata;
315
+ await earlySteps
316
+ const deferredSteps = buildState.deferred ?? Promise.resolve()
317
+ deferredSteps.catch(() => undefined)
318
+ const staticMetadataFiles = await staticMetadata
321
319
  // Warnings only - nothing downstream reads the result. Started here and awaited after the client
322
320
  // stage so it costs the build nothing, and still prints before the first prerendered route line.
323
321
  const metadataWarnings = nextCompatEnabled(config)
@@ -328,36 +326,38 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
328
326
  staticMetadataFiles,
329
327
  }),
330
328
  )
331
- : undefined;
332
- metadataWarnings?.catch(() => undefined);
329
+ : undefined
330
+ metadataWarnings?.catch(() => undefined)
333
331
  const staticModuleMetadata = await log.step('core module metadata', () =>
334
332
  collectStaticModuleMetadata(config, routes),
335
- );
333
+ )
336
334
 
337
- const actionSources = buildState.actionSources ?? buildState.actions.map(a => a.sourceKey);
335
+ const actionSources = buildState.actionSources ?? buildState.actions.map(a => a.sourceKey)
338
336
  // realpath, not resolve: a hybrid app's routes are scanned through the pages-compat mirror, whose
339
337
  // `source-app` is a symlink to the real app dir - the two spellings of one file must compare equal.
340
- const actionFiles = new Set(actionSources.map(source => realFilePath(path.resolve(config.root, source))));
338
+ const actionFiles = new Set(
339
+ actionSources.map(source => realFilePath(path.resolve(config.root, source))),
340
+ )
341
341
  // A node_modules action module never lands in route.sourceFiles itself (that walk stops at the
342
342
  // package boundary), so a route reaching one only through a first-party importer is matched here.
343
- const actionImporters = new Set((buildState.actionImporters ?? []).map(realFilePath));
343
+ const actionImporters = new Set((buildState.actionImporters ?? []).map(realFilePath))
344
344
  for (const route of routes) {
345
345
  if (
346
346
  route.kind === 'page' &&
347
347
  route.sourceFiles.some(file => {
348
- const real = realFilePath(file);
349
- return actionFiles.has(real) || actionImporters.has(real);
348
+ const real = realFilePath(file)
349
+ return actionFiles.has(real) || actionImporters.has(real)
350
350
  })
351
351
  ) {
352
- addClientEntryReason(route, 'actions');
352
+ addClientEntryReason(route, 'actions')
353
353
  }
354
354
  }
355
355
  const clientRoutes = routes.filter(
356
356
  route =>
357
357
  route.kind !== 'handler' &&
358
358
  (route.client || route.clientReferences.length > 0 || route.needsRouterEntry),
359
- );
360
- for (const route of clientRoutes) route.clientEntry = `assets/${clientEntryName(route)}.js`;
359
+ )
360
+ for (const route of clientRoutes) route.clientEntry = `assets/${clientEntryName(route)}.js`
361
361
  // A prerenderable 404 boots the server graph; the client bundles are esbuild.
362
362
  // Both need only the stub set and the entry names decided just above, so run
363
363
  // them side by side instead of paying the render after the bundle. Result is
@@ -369,8 +369,8 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
369
369
  documentCss,
370
370
  staticMetadataFiles,
371
371
  staticModuleMetadata,
372
- });
373
- notFoundDocuments.catch(() => undefined);
372
+ })
373
+ notFoundDocuments.catch(() => undefined)
374
374
  const clientBundles = log.step(
375
375
  `client bundles (${clientRoutes.length} route${clientRoutes.length === 1 ? '' : 's'})`,
376
376
  () =>
@@ -382,45 +382,43 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
382
382
  hasServerActions: actionSources.length > 0,
383
383
  pipeline: clientSources,
384
384
  }),
385
- );
386
- clientBundles.catch(() => undefined);
385
+ )
386
+ clientBundles.catch(() => undefined)
387
387
  // The manifest-writing steps and typegen depend on nothing the client stage produces, so they run
388
388
  // under it instead of ahead of it. Awaited first so a step failure still reports before a
389
389
  // bundling error.
390
390
  const remainingSteps = runBuildSteps(
391
391
  steps.filter(step => !step.early && !step.gate),
392
392
  stepContext,
393
- ).then(() =>
394
- log.step('typegen', () => writeTypegen(config, routes)),
395
- );
396
- remainingSteps.catch(() => undefined);
397
- await remainingSteps;
398
- await clientBundles;
399
- const proxyModule = await proxyBuild;
400
- await partytownLib;
393
+ ).then(() => log.step('typegen', () => writeTypegen(config, routes)))
394
+ remainingSteps.catch(() => undefined)
395
+ await remainingSteps
396
+ await clientBundles
397
+ const proxyModule = await proxyBuild
398
+ await partytownLib
401
399
  // Standalone static chunks (compat: the no-module polyfills chunk) live
402
400
  // outside the esbuild entry graph, so emit them regardless of whether any
403
401
  // route produced a client bundle.
404
- await emitStaticClientChunks(config, path.join(config.outPath, 'public', 'assets'));
402
+ await emitStaticClientChunks(config, path.join(config.outPath, 'public', 'assets'))
405
403
  // All three source consumers have run by here; the split says how much of the
406
404
  // app was read once and shared rather than read per consumer.
407
405
  if (verbose) {
408
- const sources = sourceCacheStats();
406
+ const sources = sourceCacheStats()
409
407
  log.log(
410
408
  `source cache — ${sources.reads} reads, ${sources.hits} shared, ${sources.files} files · parses ${JSON.stringify(scanFactsStats())}`,
411
- );
409
+ )
412
410
  }
413
- await metadataWarnings;
411
+ await metadataWarnings
414
412
 
415
413
  // A throwing after() during a prerender must fail the whole build (Next
416
414
  // exits 1), but with `prerenderEarlyExit: false` the errors for every route
417
415
  // are collected first — so record it and keep prerendering, then fail at the
418
416
  // end once all routes have logged their prerender-error lines.
419
- let hadAfterPrerenderError = false;
417
+ let hadAfterPrerenderError = false
420
418
  // `dynamic = 'error'` routes that read dynamic data can't be rendered
421
419
  // statically; Next fails the build naming each offending route + API. Collect
422
420
  // them all (matching prerenderEarlyExit:false) and fail once at the end.
423
- let hadDynamicErrorFailure = false;
421
+ let hadDynamicErrorFailure = false
424
422
 
425
423
  // A handler route whose logical output path doubles as a parent directory for a descendant
426
424
  // route's static output cannot be written as a plain file AND host a child directory on one
@@ -428,28 +426,28 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
428
426
  // the file and the directory would collide). The ancestor keeps the physical file so it serves
429
427
  // statically; each descendant records its prerender-manifest entry but skips the physical copy
430
428
  // and serves dynamically. Descendants are detected up-front so the outcome is order-independent.
431
- const descendantHandlerIds = collectDescendantHandlerIds(routes);
429
+ const descendantHandlerIds = collectDescendantHandlerIds(routes)
432
430
 
433
- const isCompile = options.buildMode === 'compile';
434
- const isGenerate = options.buildMode === 'generate';
435
- const debugPrerender = options.debugPrerender ?? false;
431
+ const isCompile = options.buildMode === 'compile'
432
+ const isGenerate = options.buildMode === 'generate'
433
+ const debugPrerender = options.debugPrerender ?? false
436
434
  // Record the build inputs the serving runtime needs (compat: --debug-prerender
437
435
  // selects the shape of the runtime 'use cache' error log). No-op for core.
438
- buildCompat().recordBuildFlags(config.outPath, debugPrerender);
436
+ buildCompat().recordBuildFlags(config.outPath, debugPrerender)
439
437
  // Generate mode: Next prints the page-data banner, then ONLY prerender diagnostics until the
440
438
  // route table - the cache-components-errors suites capture everything after this line and
441
439
  // inline-snapshot it. Per-path lines are buffered and printed after a `Route (app)` header, which
442
440
  // ends the suites' capture window.
443
- const generateRouteLines: string[] = [];
441
+ const generateRouteLines: string[] = []
444
442
  /** Route ids that prerendered a partial (cache-components) shell — Next's `◐`. */
445
- const partialPrerenders = new Set<string>();
443
+ const partialPrerenders = new Set<string>()
446
444
  /** Routes that failed generate-mode prerender diagnostics, in scan order. */
447
- const generateFailures: { route: string; omitErrorLine: boolean }[] = [];
448
- if (isGenerate) console.log(' Collecting page data ...');
445
+ const generateFailures: { route: string; omitErrorLine: boolean }[] = []
446
+ if (isGenerate) console.log(' Collecting page data ...')
449
447
 
450
448
  // Route stylesheets build strictly after the document ones; this is the first
451
449
  // point that needs them, so everything above ran alongside the CSS worker.
452
- await documentCss;
450
+ await documentCss
453
451
 
454
452
  for (const route of routes) {
455
453
  if (route.dynamicErrorApi) {
@@ -457,19 +455,19 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
457
455
  `Error: Route ${toNextRoutePattern(route.route || '/')} with \`dynamic = "error"\` ` +
458
456
  `couldn't be rendered statically because it used ${route.dynamicErrorApi}. ` +
459
457
  `See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`,
460
- );
461
- hadDynamicErrorFailure = true;
462
- continue;
458
+ )
459
+ hadDynamicErrorFailure = true
460
+ continue
463
461
  }
464
462
  if (route.kind === 'handler') {
465
- if (isCompile) continue;
466
- if (!(await staticRouteHandlerCandidate(route))) continue;
467
- const dynamicUsage = await staticHandlerDynamicUsage(route);
463
+ if (isCompile) continue
464
+ if (!(await staticRouteHandlerCandidate(route))) continue
465
+ const dynamicUsage = await staticHandlerDynamicUsage(route)
468
466
  if (dynamicUsage) {
469
467
  console.log(
470
468
  `Caught Error: Dynamic server usage: Route ${route.route || '/'} couldn't be rendered statically because it used \`${dynamicUsage}\`.`,
471
- );
472
- continue;
469
+ )
470
+ continue
473
471
  }
474
472
  // A single handler prerender failure must not kill the whole build:
475
473
  // skip the static copy and let the route serve dynamically at runtime.
@@ -481,22 +479,22 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
481
479
  skipPhysicalWrite: descendantHandlerIds.has(route.id),
482
480
  }),
483
481
  ),
484
- );
482
+ )
485
483
  } catch (error) {
486
- rethrowIfProgrammingError(error);
487
- if (isAfterPrerenderError(error)) hadAfterPrerenderError = true;
488
- warnSkippedStatic(route.route, error instanceof Error ? error.message : String(error));
484
+ rethrowIfProgrammingError(error)
485
+ if (isAfterPrerenderError(error)) hadAfterPrerenderError = true
486
+ warnSkippedStatic(route.route, error instanceof Error ? error.message : String(error))
489
487
  }
490
- continue;
488
+ continue
491
489
  }
492
490
 
493
- await buildRouteCss(config, route, { verbose });
491
+ await buildRouteCss(config, route, { verbose })
494
492
  for (const reference of route.clientReferences) {
495
- await buildClientReferenceCss(config, reference, { verbose });
493
+ await buildClientReferenceCss(config, reference, { verbose })
496
494
  }
497
495
 
498
496
  // Compile mode bundles only — every prerender/export step is generate's.
499
- if (isCompile) continue;
497
+ if (isCompile) continue
500
498
 
501
499
  // Generate mode fails a cacheComponents route whose prerender would block,
502
500
  // with Next's exact diagnostic block (owner stack + codeframe under
@@ -505,26 +503,26 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
505
503
  if (isGenerate && cacheComponents() && !route.client && !route.interception) {
506
504
  // Next names the route by its source pattern (`/use-cache-params/[slug]`),
507
505
  // not pnext's `:param` form — the diagnostics quote it verbatim.
508
- const diagnosticRoute = toNextRoutePattern(route.route || '/');
506
+ const diagnosticRoute = toNextRoutePattern(route.route || '/')
509
507
  const diagnostic = buildCompat().diagnoseCacheComponentsPrerender({
510
508
  route: diagnosticRoute,
511
509
  pageFile: route.file,
512
510
  appPath: config.appPath,
513
511
  debugPrerender,
514
- });
512
+ })
515
513
  if (diagnostic) {
516
- console.error(diagnostic);
514
+ console.error(diagnostic)
517
515
  generateFailures.push({
518
516
  route: diagnosticRoute,
519
517
  omitErrorLine: buildCompat().diagnosticLeadsWithErrorLine(diagnostic),
520
- });
521
- continue;
518
+ })
519
+ continue
522
520
  }
523
521
  }
524
522
 
525
523
  if (route.ppr) {
526
- await log.step(`ppr shell ${route.route}`, () => buildPprShell(config, route));
527
- continue;
524
+ await log.step(`ppr shell ${route.route}`, () => buildPprShell(config, route))
525
+ continue
528
526
  }
529
527
 
530
528
  // Under the global cacheComponents flag every page is a PPR candidate:
@@ -538,35 +536,35 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
538
536
  ) {
539
537
  const built = await log.step(`cache-components shell ${route.route}`, () =>
540
538
  buildCacheComponentsShell(config, route),
541
- );
539
+ )
542
540
  if (built) {
543
541
  // Next marks a partially prerendered route with a distinct glyph in its build output, and
544
542
  // suites grep for it. That glyph is for a PARTIAL prerender: a shell with dynamic holes (or
545
543
  // blocked metadata), or a param FALLBACK shell whose params only resolve per request. A
546
544
  // hole-less shell of a fully-known route keeps Next's static marker.
547
545
  const paramFallback =
548
- (route.params.length > 0 || Boolean(route.catchAll)) && !route.hasStaticParams;
546
+ (route.params.length > 0 || Boolean(route.catchAll)) && !route.hasStaticParams
549
547
  if (paramFallback || (route.pprHoles?.length ?? 0) > 0 || route.pprMetadata) {
550
- partialPrerenders.add(route.id);
548
+ partialPrerenders.add(route.id)
551
549
  }
552
- if (isGenerate) generateRouteLines.push(` ◐ ${route.route || '/'}`);
553
- continue;
550
+ if (isGenerate) generateRouteLines.push(` ◐ ${route.route || '/'}`)
551
+ continue
554
552
  }
555
553
  }
556
554
  // A whole-client page cannot consume hanging dynamic params during a shell
557
555
  // prerender. Keep it runtime-only until that boundary can postpone.
558
- if (cacheComponents() && route.client && (route.params.length > 0 || route.catchAll)) continue;
556
+ if (cacheComponents() && route.client && (route.params.length > 0 || route.catchAll)) continue
559
557
 
560
558
  // Segment config gates prerendering: force-dynamic / revalidate 0 /
561
559
  // force-no-store routes are never prebuilt; force-static prerenders even
562
560
  // when the route reads request data (with an empty synthetic request,
563
561
  // Next-style).
564
- const segmentConfig = route.segmentConfig;
565
- const forceStatic = segmentConfig?.dynamic === 'force-static';
562
+ const segmentConfig = route.segmentConfig
563
+ const forceStatic = segmentConfig?.dynamic === 'force-static'
566
564
  if (forceStatic && segmentConfig?.runtime === 'edge') {
567
565
  console.warn(
568
566
  `Page "${route.route}" is using runtime = 'edge' which is currently incompatible with dynamic = 'force-static'. Please remove either "runtime" or "force-static" for correct behavior`,
569
- );
567
+ )
570
568
  }
571
569
  const configForcesDynamic =
572
570
  segmentConfig?.dynamic === 'force-dynamic' ||
@@ -576,9 +574,9 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
576
574
  // absent from its prerender manifest / static output); their data
577
575
  // stability comes from the fetch cache, not prebuilt HTML. force-static
578
576
  // keeps the existing (warned-about) prerender behavior.
579
- (segmentConfig?.runtime === 'edge' && !forceStatic);
577
+ (segmentConfig?.runtime === 'edge' && !forceStatic)
580
578
 
581
- let staticParams: Awaited<ReturnType<typeof staticParamsFor>> | null = null;
579
+ let staticParams: Awaited<ReturnType<typeof staticParamsFor>> | null = null
582
580
  if (!configForcesDynamic && route.hasStaticParams) {
583
581
  try {
584
582
  staticParams = await log.step(`static params ${route.route}`, () =>
@@ -591,24 +589,24 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
591
589
  prerender: true,
592
590
  })
593
591
  .then(result => result.value),
594
- );
592
+ )
595
593
  } catch (error) {
596
- rethrowIfProgrammingError(error);
597
- if (!isAfterPrerenderError(error)) throw error;
594
+ rethrowIfProgrammingError(error)
595
+ if (!isAfterPrerenderError(error)) throw error
598
596
  // Next reports a generateStaticParams failure with this exact prefix
599
597
  // (naming the route in `[param]` form), followed by the underlying
600
598
  // error (the thrown after() message).
601
- console.error(`Failed to collect page data for ${toNextRoutePattern(route.route)}`);
602
- console.error(error instanceof Error ? (error.stack ?? error.message) : String(error));
603
- hadAfterPrerenderError = true;
604
- continue;
599
+ console.error(`Failed to collect page data for ${toNextRoutePattern(route.route)}`)
600
+ console.error(error instanceof Error ? (error.stack ?? error.message) : String(error))
601
+ hadAfterPrerenderError = true
602
+ continue
605
603
  }
606
604
  }
607
605
  // Dynamic request APIs normally skip page prerendering. An unstable_cache
608
606
  // boundary is the exception: Next executes the fill during prerender so a
609
607
  // request API read inside it can fail the build at the exact call site.
610
608
  const validateUnstableCacheScope =
611
- !configForcesDynamic && route.usesRequest && !forceStatic && (await usesUnstableCache(route));
609
+ !configForcesDynamic && route.usesRequest && !forceStatic && (await usesUnstableCache(route))
612
610
  const paramSets = staticParams
613
611
  ? staticParams.paths
614
612
  : validateUnstableCacheScope
@@ -617,10 +615,10 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
617
615
  ? []
618
616
  : route.mode === 'static' && route.params.length === 0 && !route.catchAll
619
617
  ? [{}]
620
- : [];
618
+ : []
621
619
  // Partial sets included: dynamicParams=false checks match governed params
622
620
  // against these at request time.
623
- if (staticParams) route.prerenderedParams = staticParams.allSets;
621
+ if (staticParams) route.prerenderedParams = staticParams.allSets
624
622
  // NEXT_DEBUG_BUILD parity: Next logs why a route that would otherwise have
625
623
  // been prerendered fell back to dynamic (the e2e suite greps this line).
626
624
  // This branch covers the request-API bailout (headers()/cookies() in the
@@ -635,7 +633,7 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
635
633
  route.params.length === 0 &&
636
634
  !route.catchAll
637
635
  ) {
638
- logStaticBailout(route.route || '/', await requestApiBailoutReason(route));
636
+ logStaticBailout(route.route || '/', await requestApiBailoutReason(route))
639
637
  }
640
638
  if (paramSets.length === 0) {
641
639
  // Next prerenders every non-force-dynamic page and only marks it dynamic once a request API
@@ -651,12 +649,11 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
651
649
  !route.catchAll &&
652
650
  (await schedulesDeferredWork(route))
653
651
  ) {
654
- await probeDeferredDynamicUsage(config, route, staticMetadataFiles, staticModuleMetadata);
652
+ await probeDeferredDynamicUsage(config, route, staticMetadataFiles, staticModuleMetadata)
655
653
  }
656
- continue;
654
+ continue
657
655
  }
658
- if (!forceStatic && (await needsRequestOriginForMetadataImages(config.appPath, route)))
659
- continue;
656
+ if (!forceStatic && (await needsRequestOriginForMetadataImages(config.appPath, route))) continue
660
657
 
661
658
  await log.step(
662
659
  `prerender ${route.route} (${paramSets.length} page${paramSets.length === 1 ? '' : 's'})`,
@@ -666,15 +663,15 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
666
663
  // generateStaticParams value, so a page sees `sticks%20%26%20stones`,
667
664
  // never the raw `sticks & stones` (prerender-encoding suite). File
668
665
  // layout and dynamicParams matching keep the raw values.
669
- const params = encodeStaticRouteParams(rawParams);
670
- const routePath = fillRoutePath(route.route, rawParams);
671
- const file = staticHtmlPath(config.outPath, routePath, flatExportLayout);
672
- const collision = staticOutputCollision(config.outPath, file);
666
+ const params = encodeStaticRouteParams(rawParams)
667
+ const routePath = fillRoutePath(route.route, rawParams)
668
+ const file = staticHtmlPath(config.outPath, routePath, flatExportLayout)
669
+ const collision = staticOutputCollision(config.outPath, file)
673
670
  if (collision) {
674
- warnSkippedStatic(routePath, collision);
675
- continue;
671
+ warnSkippedStatic(routePath, collision)
672
+ continue
676
673
  }
677
- const url = new URL(`http://pnext.local${routePath}`);
674
+ const url = new URL(`http://pnext.local${routePath}`)
678
675
  // A single page prerender failure must not kill the whole build:
679
676
  // skip the static copy and let the route serve dynamically.
680
677
  try {
@@ -682,71 +679,74 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
682
679
  // stashes its effective revalidate/expire/stale (stashCacheLife
683
680
  // reads the work-unit scope). We read + persist it below so a pure
684
681
  // static HIT can re-emit the SWR headers from start.ts.
685
- let cacheLife: CacheLifeStash | undefined;
686
- let fontLinkHeader: string | undefined;
687
- let prerenderVary: BuildResponseVary | undefined;
682
+ let cacheLife: CacheLifeStash | undefined
683
+ let fontLinkHeader: string | undefined
684
+ let prerenderVary: BuildResponseVary | undefined
688
685
  const rendered = await runWithWorkUnit('render', async () => {
689
686
  const tracked = await buildCompat().withVaryParamsTracking(() =>
690
- getRenderExtensions().collectRenderMeta(
691
- () =>
692
- runWithNextBuildPhase(() =>
693
- withRouteRuntime(route.segmentConfig?.runtime, () =>
694
- renderPageWithStatus({
695
- config,
696
- route,
697
- params,
698
- url,
699
- // Empty request so request APIs (cookies/headers) resolve
700
- // to empty values instead of failing the prerender.
701
- ...(route.usesRequest ? { request: new Request(url) } : {}),
702
- staticMetadataFiles,
703
- staticModuleMetadata,
704
- resolveDynamicMetadataRoutes: true,
705
- }),
687
+ getRenderExtensions().collectRenderMeta(
688
+ () =>
689
+ runWithNextBuildPhase(() =>
690
+ withRouteRuntime(route.segmentConfig?.runtime, () =>
691
+ renderPageWithStatus({
692
+ config,
693
+ route,
694
+ params,
695
+ url,
696
+ // Empty request so request APIs (cookies/headers) resolve
697
+ // to empty values instead of failing the prerender.
698
+ ...(route.usesRequest ? { request: new Request(url) } : {}),
699
+ staticMetadataFiles,
700
+ staticModuleMetadata,
701
+ resolveDynamicMetadataRoutes: true,
702
+ }),
703
+ ),
706
704
  ),
707
- ),
708
- { fetchCache: segmentConfig?.fetchCache, route: routePath || '/', prerender: true },
709
- ),
710
- );
711
- const result = tracked.value;
712
- prerenderVary = tracked.vary;
713
- cacheLife = buildCompat().takeCacheLifeStash();
705
+ {
706
+ fetchCache: segmentConfig?.fetchCache,
707
+ route: routePath || '/',
708
+ prerender: true,
709
+ },
710
+ ),
711
+ )
712
+ const result = tracked.value
713
+ prerenderVary = tracked.vary
714
+ cacheLife = buildCompat().takeCacheLifeStash()
714
715
  // A static prerender never runs the response finalizer that flushes
715
716
  // font preloads to the `Link` header, so bake it into the manifest
716
717
  // headers here (must read the work unit before it unwinds).
717
- fontLinkHeader = buildCompat().takeFontLinkHeader();
718
- return result;
719
- });
718
+ fontLinkHeader = buildCompat().takeFontLinkHeader()
719
+ return result
720
+ })
720
721
  // Explicit no-store signals (fetch no-store/no-cache, revalidate
721
722
  // 0, unstable_noStore) keep the route dynamic — unless the
722
723
  // segment config explicitly opts into static/ISR output.
723
724
  const optedStatic =
724
725
  forceStatic ||
725
726
  typeof segmentConfig?.revalidate === 'number' ||
726
- segmentConfig?.revalidate === false;
727
+ segmentConfig?.revalidate === false
727
728
  if (rendered.noStore && !optedStatic) {
728
- if (process.env.NEXT_DEBUG_BUILD)
729
- logStaticBailout(routePath || '/', 'no-store fetch');
730
- continue;
729
+ if (process.env.NEXT_DEBUG_BUILD) logStaticBailout(routePath || '/', 'no-store fetch')
730
+ continue
731
731
  }
732
- await writeText(file, rendered.value.html);
733
- recordPrerenderVary(route, routePath || '/', prerenderVary);
732
+ await writeText(file, rendered.value.html)
733
+ recordPrerenderVary(route, routePath || '/', prerenderVary)
734
734
  // Prerendered paths print like Next's build output; tooling (and
735
735
  // the Next e2e suite) greps for them. Generate mode defers them
736
736
  // below the `Route (app)` header so the diagnostics capture window
737
737
  // stays clean.
738
- const routeLine = `${dim(' ○')} ${routePath || '/'}`;
739
- if (isGenerate) generateRouteLines.push(routeLine);
740
- else console.log(routeLine);
738
+ const routeLine = `${dim(' ○')} ${routePath || '/'}`
739
+ if (isGenerate) generateRouteLines.push(routeLine)
740
+ else console.log(routeLine)
741
741
  await emitConcreteNextPageArtifacts(config, route, routePath || '/', {
742
742
  html: rendered.value.html,
743
743
  status: rendered.value.status,
744
- });
745
- const relative = toPosixPath(path.relative(path.join(config.outPath, 'public'), file));
744
+ })
745
+ const relative = toPosixPath(path.relative(path.join(config.outPath, 'public'), file))
746
746
  const revalidateSeconds = combineRevalidate(
747
747
  segmentConfig?.revalidate,
748
748
  rendered.revalidateSeconds,
749
- );
749
+ )
750
750
  // A `use cache` render stashes its effective cacheLife; the SWR
751
751
  // cache-control + x-nextjs-stale-time normally come from a response
752
752
  // finalizer, but a pure static HIT never re-renders, so we bake the
@@ -754,7 +754,7 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
754
754
  // start.ts can re-emit them on the HIT. The work-unit stash is empty
755
755
  // when the build prerender ran without a request work unit; the
756
756
  // render cache meta carries the same windows, so fall back to it.
757
- const defaultExpireTime = buildCompat().defaultExpireTimeSeconds();
757
+ const defaultExpireTime = buildCompat().defaultExpireTimeSeconds()
758
758
  const effectiveCacheLife: CacheLifeStash | undefined =
759
759
  cacheLife ??
760
760
  (rendered.expireSeconds !== undefined || rendered.staleSeconds !== undefined
@@ -772,15 +772,15 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
772
772
  revalidateSeconds,
773
773
  expireSeconds: defaultExpireTime,
774
774
  }
775
- : undefined);
776
- const cacheLifeHeaders = cacheLifeResponseHeaders(effectiveCacheLife);
775
+ : undefined)
776
+ const cacheLifeHeaders = cacheLifeResponseHeaders(effectiveCacheLife)
777
777
  const headers: [string, string][] = [
778
778
  ...(rendered.value.location
779
779
  ? [['location', rendered.value.location] as [string, string]]
780
780
  : []),
781
781
  ...(fontLinkHeader ? [['link', fontLinkHeader] as [string, string]] : []),
782
782
  ...cacheLifeHeaders,
783
- ];
783
+ ]
784
784
  staticFiles[relative] = {
785
785
  status: rendered.value.status,
786
786
  headers,
@@ -794,15 +794,15 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
794
794
  ? { staleSeconds: effectiveCacheLife.staleSeconds }
795
795
  : {}),
796
796
  ...(rendered.tags.length > 0 ? { tags: rendered.tags } : {}),
797
- };
797
+ }
798
798
  } catch (error) {
799
- rethrowIfProgrammingError(error);
800
- if (isAfterPrerenderError(error)) hadAfterPrerenderError = true;
801
- warnSkippedStatic(routePath, error instanceof Error ? error.message : String(error));
799
+ rethrowIfProgrammingError(error)
800
+ if (isAfterPrerenderError(error)) hadAfterPrerenderError = true
801
+ warnSkippedStatic(routePath, error instanceof Error ? error.message : String(error))
802
802
  }
803
803
  }
804
804
  },
805
- );
805
+ )
806
806
  }
807
807
 
808
808
  // Generate-mode prerender diagnostics fail the pass with Next's exact
@@ -812,34 +812,34 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
812
812
  for (const failed of generateFailures) {
813
813
  console.error(
814
814
  buildCompat().prerenderFailureFooter(failed.route, debugPrerender, failed.omitErrorLine),
815
- );
815
+ )
816
816
  }
817
- console.error('Next.js build worker exited with code: 1 and signal: null');
818
- process.exit(1);
817
+ console.error('Next.js build worker exited with code: 1 and signal: null')
818
+ process.exit(1)
819
819
  }
820
820
  if (isGenerate) {
821
- console.log('\nRoute (app)');
822
- for (const line of generateRouteLines) console.log(line);
821
+ console.log('\nRoute (app)')
822
+ for (const line of generateRouteLines) console.log(line)
823
823
  }
824
824
 
825
825
  // Fail the build after every route has been given the chance to log its
826
826
  // prerender error (matching `prerenderEarlyExit: false`).
827
827
  if (hadAfterPrerenderError) {
828
- throw new Error('Build failed because an error was thrown inside `after()` while prerendering.');
828
+ throw new Error('Build failed because an error was thrown inside `after()` while prerendering.')
829
829
  }
830
830
  if (hadDynamicErrorFailure) {
831
831
  throw new Error(
832
832
  'Build failed because a route with `dynamic = "error"` read dynamic data during prerendering.',
833
- );
833
+ )
834
834
  }
835
835
 
836
836
  const interceptionPrerenders = isCompile
837
837
  ? new Map<string, string[]>()
838
- : await logInterceptionPrerenders(config, routes);
838
+ : await logInterceptionPrerenders(config, routes)
839
839
 
840
- const fallback404 = await notFoundDocuments;
841
- await emitNextNotFoundArtifacts(config, fallback404);
842
- await emitProxyServerArtifacts(config, proxyModule);
840
+ const fallback404 = await notFoundDocuments
841
+ await emitNextNotFoundArtifacts(config, fallback404)
842
+ await emitProxyServerArtifacts(config, proxyModule)
843
843
 
844
844
  // Next always prerenders the built-in `/_not-found` and `/_global-error`
845
845
  // pseudo-routes for an app-router build (even when the app ships no custom
@@ -855,25 +855,25 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
855
855
  // it and the prerender manifest omits the route. The hybrid pages+app
856
856
  // materializer synthesizes an app/layout.js, so inspect the ORIGINAL app
857
857
  // (the shim's `source-app` symlink) when it exists.
858
- const sourceApp = path.join(config.appPath, '..', 'source-app');
859
- const layoutRoot = existsSync(sourceApp) ? sourceApp : config.appPath;
858
+ const sourceApp = path.join(config.appPath, '..', 'source-app')
859
+ const layoutRoot = existsSync(sourceApp) ? sourceApp : config.appPath
860
860
  const hasTopLevelRootLayout = ['tsx', 'ts', 'jsx', 'js'].some(ext =>
861
861
  existsSync(path.join(layoutRoot, `layout.${ext}`)),
862
- );
862
+ )
863
863
  const pseudos = hasTopLevelRootLayout
864
864
  ? ['_not-found.html', '_global-error.html']
865
- : ['_not-found.html'];
865
+ : ['_not-found.html']
866
866
  for (const pseudo of pseudos) {
867
867
  if (!(pseudo in staticFiles)) {
868
- staticFiles[pseudo] = { status: 200, headers: [], kind: 'page' };
868
+ staticFiles[pseudo] = { status: 200, headers: [], kind: 'page' }
869
869
  }
870
870
  }
871
871
  }
872
872
 
873
873
  // The action modules' server compile ran under the client stage; the manifest
874
874
  // carries its entries, so this is where it has to have landed.
875
- await deferredSteps;
876
- const actions = buildState.actions;
875
+ await deferredSteps
876
+ const actions = buildState.actions
877
877
  const manifest: BuildManifest = {
878
878
  version: 0,
879
879
  root: config.root,
@@ -892,22 +892,22 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
892
892
  : {}),
893
893
  ...(proxyModule ? { proxyModule } : {}),
894
894
  ...(actions.length > 0 ? { actions } : {}),
895
- };
896
- await writeBuildManifest(config.outPath, manifest);
897
- log.log(`wrote manifest.json (${routes.length} route${routes.length === 1 ? '' : 's'})`);
898
- await log.step('server entry', () => serverEntryDone);
895
+ }
896
+ await writeBuildManifest(config.outPath, manifest)
897
+ log.log(`wrote manifest.json (${routes.length} route${routes.length === 1 ? '' : 's'})`)
898
+ await log.step('server entry', () => serverEntryDone)
899
899
  for (const hook of getBuildExtensions().completeHooks) {
900
- await hook({ config, manifest, log });
900
+ await hook({ config, manifest, log })
901
901
  }
902
902
  if (options.adapter === 'vercel')
903
903
  await log.step('vercel adapter output', () =>
904
904
  writeVercelOutput(config, manifest, { verbose, warm }),
905
- );
905
+ )
906
906
 
907
907
  // Bundling is done; the build metric stops here. Phases that ran alongside it
908
908
  // (typecheck) are awaited next and reported on their own lines.
909
- const buildDurationMs = performance.now() - startedAt;
910
- const phases = await settleBuildParallelPhases();
909
+ const buildDurationMs = performance.now() - startedAt
910
+ const phases = await settleBuildParallelPhases()
911
911
 
912
912
  printBuildSummary(
913
913
  config,
@@ -918,23 +918,23 @@ async function runBuild(root: string | undefined, options: BuildOptions) {
918
918
  interceptionPrerenders,
919
919
  partialPrerenders,
920
920
  phases,
921
- );
922
- return manifest;
921
+ )
922
+ return manifest
923
923
  }
924
924
 
925
925
  /** Run build steps in registration order, each timed under its own label. */
926
926
  async function runBuildSteps(steps: BuildStep[], ctx: BuildStepContext) {
927
927
  for (const [index, step] of steps.entries()) {
928
- await ctx.log.step(`build step ${step.name || index}`, () => step(ctx));
928
+ await ctx.log.step(`build step ${step.name || index}`, () => step(ctx))
929
929
  }
930
930
  }
931
931
 
932
932
  /** Await every background phase, returning each one's own elapsed time. */
933
933
  async function settleBuildParallelPhases() {
934
- const phases = getBuildExtensions().parallelPhases;
935
- const timings: { name: string; durationMs: number }[] = [];
936
- for (const phase of phases) timings.push({ name: phase.name, durationMs: await phase.run });
937
- return timings;
934
+ const phases = getBuildExtensions().parallelPhases
935
+ const timings: { name: string; durationMs: number }[] = []
936
+ for (const phase of phases) timings.push({ name: phase.name, durationMs: await phase.run })
937
+ return timings
938
938
  }
939
939
 
940
940
  /**
@@ -948,33 +948,35 @@ async function logInterceptionPrerenders(
948
948
  config: Awaited<ReturnType<typeof loadConfig>>,
949
949
  routes: RouteManifestEntry[],
950
950
  ): Promise<Map<string, string[]>> {
951
- const prerenders = new Map<string, string[]>();
951
+ const prerenders = new Map<string, string[]>()
952
952
  for (const route of routes) {
953
- if (route.kind !== 'page' || !route.interception) continue;
954
- if (!routeFileExportsStaticParams(route.file)) continue;
955
- let paths: string[];
953
+ if (route.kind !== 'page' || !route.interception) continue
954
+ if (!routeFileExportsStaticParams(route.file)) continue
955
+ let paths: string[]
956
956
  try {
957
- const staticParams = await staticParamsFor(config, route);
957
+ const staticParams = await staticParamsFor(config, route)
958
958
  paths = staticParams.paths.map(params =>
959
959
  interceptionDisplayRoute(route, fillRoutePath(route.route, params)),
960
- );
960
+ )
961
961
  } catch (error) {
962
- rethrowIfProgrammingError(error);
963
- warnSkippedStatic(route.route, error instanceof Error ? error.message : String(error));
964
- continue;
962
+ rethrowIfProgrammingError(error)
963
+ warnSkippedStatic(route.route, error instanceof Error ? error.message : String(error))
964
+ continue
965
965
  }
966
- if (paths.length === 0) continue;
967
- prerenders.set(route.id, paths);
968
- for (const routePath of paths) console.log(`${dim(' ○')} ${routePath}`);
966
+ if (paths.length === 0) continue
967
+ prerenders.set(route.id, paths)
968
+ for (const routePath of paths) console.log(`${dim(' ○')} ${routePath}`)
969
969
  }
970
- return prerenders;
970
+ return prerenders
971
971
  }
972
972
 
973
973
  function routeFileExportsStaticParams(file: string): boolean {
974
- if (!existsSync(file)) return false;
975
- const source = readTextSync(file);
976
- return /\bexport\s+(?:async\s+)?function\s+generateStaticParams\b/.test(source) ||
977
- /\bexport\s+const\s+generateStaticParams\b/.test(source);
974
+ if (!existsSync(file)) return false
975
+ const source = readTextSync(file)
976
+ return (
977
+ /\bexport\s+(?:async\s+)?function\s+generateStaticParams\b/.test(source) ||
978
+ /\bexport\s+const\s+generateStaticParams\b/.test(source)
979
+ )
978
980
  }
979
981
 
980
982
  /**
@@ -985,39 +987,39 @@ function routeFileExportsStaticParams(file: string): boolean {
985
987
  * marker is re-inserted at the segment the rewind landed on.
986
988
  */
987
989
  function interceptionDisplayRoute(route: RouteManifestEntry, filled?: string): string {
988
- const interception = route.interception;
989
- if (!interception) return route.route;
990
- const base = interception.base === '/' ? [] : interception.base.split('/').filter(Boolean);
991
- const levels = interceptionMarkerLevels(interception.marker);
992
- const target = (filled ?? toNextRoutePattern(route.route)).split('/').filter(Boolean);
990
+ const interception = route.interception
991
+ if (!interception) return route.route
992
+ const base = interception.base === '/' ? [] : interception.base.split('/').filter(Boolean)
993
+ const levels = interceptionMarkerLevels(interception.marker)
994
+ const target = (filled ?? toNextRoutePattern(route.route)).split('/').filter(Boolean)
993
995
  // `route.route` is `<kept base>/<segments below the marker>`; the marker dir
994
996
  // itself is the first segment past what the rewind kept.
995
- const cut = Number.isFinite(levels) ? Math.max(0, base.length - levels) : 0;
996
- const below = target.slice(cut);
997
- const segments = [...base, `${interception.marker}${below[0] ?? ''}`, ...below.slice(1)];
998
- return `/${segments.join('/')}`;
997
+ const cut = Number.isFinite(levels) ? Math.max(0, base.length - levels) : 0
998
+ const below = target.slice(cut)
999
+ const segments = [...base, `${interception.marker}${below[0] ?? ''}`, ...below.slice(1)]
1000
+ return `/${segments.join('/')}`
999
1001
  }
1000
1002
 
1001
1003
  async function writeBuildManifest(outPath: string, manifest: BuildManifest) {
1002
- assertManifestServerArtifacts(outPath, manifest);
1003
- const file = path.join(outPath, 'manifest.json');
1004
- const temporary = path.join(outPath, `.manifest-${process.pid}.tmp`);
1005
- await writeFile(temporary, `${JSON.stringify(manifest, null, 2)}\n`);
1006
- await rename(temporary, file);
1004
+ assertManifestServerArtifacts(outPath, manifest)
1005
+ const file = path.join(outPath, 'manifest.json')
1006
+ const temporary = path.join(outPath, `.manifest-${process.pid}.tmp`)
1007
+ await writeFile(temporary, `${JSON.stringify(manifest, null, 2)}\n`)
1008
+ await rename(temporary, file)
1007
1009
  }
1008
1010
 
1009
1011
  function assertManifestServerArtifacts(outPath: string, manifest: BuildManifest) {
1010
1012
  const artifacts = [
1011
1013
  ...(manifest.proxyModule ? [manifest.proxyModule] : []),
1012
1014
  ...(manifest.actions?.map(action => action.modulePath) ?? []),
1013
- ];
1015
+ ]
1014
1016
  for (const artifact of artifacts) {
1015
- const file = path.resolve(outPath, artifact);
1017
+ const file = path.resolve(outPath, artifact)
1016
1018
  if (file !== outPath && !file.startsWith(`${outPath}${path.sep}`)) {
1017
- throw new Error(`Build manifest server artifact escapes output directory: ${artifact}`);
1019
+ throw new Error(`Build manifest server artifact escapes output directory: ${artifact}`)
1018
1020
  }
1019
1021
  if (!existsSync(file)) {
1020
- throw new Error(`Build manifest server artifact is missing: ${artifact}`);
1022
+ throw new Error(`Build manifest server artifact is missing: ${artifact}`)
1021
1023
  }
1022
1024
  }
1023
1025
  }
@@ -1032,18 +1034,17 @@ function printBuildSummary(
1032
1034
  partialPrerenders = new Set<string>(),
1033
1035
  parallelPhases: { name: string; durationMs: number }[] = [],
1034
1036
  ) {
1035
- const pages = routes.filter(route => route.kind !== 'handler').length;
1036
- const handlers = routes.length - pages;
1037
- const parts = [`${pages} ${pages === 1 ? 'page' : 'pages'}`];
1038
- if (handlers > 0)
1039
- parts.push(`${handlers} ${handlers === 1 ? 'route handler' : 'route handlers'}`);
1040
- const outDir = path.relative(config.root, config.outPath) || '.';
1037
+ const pages = routes.filter(route => route.kind !== 'handler').length
1038
+ const handlers = routes.length - pages
1039
+ const parts = [`${pages} ${pages === 1 ? 'page' : 'pages'}`]
1040
+ if (handlers > 0) parts.push(`${handlers} ${handlers === 1 ? 'route handler' : 'route handlers'}`)
1041
+ const outDir = path.relative(config.root, config.outPath) || '.'
1041
1042
 
1042
1043
  // Route table: ○ static, ● SSG (params), ◐ partial prerender, ƒ dynamic.
1043
- console.log('');
1044
- console.log('Route (app)');
1044
+ console.log('')
1045
+ console.log('Route (app)')
1045
1046
  for (const route of routes) {
1046
- const intercepted = interceptionPrerenders.get(route.id);
1047
+ const intercepted = interceptionPrerenders.get(route.id)
1047
1048
  const marker =
1048
1049
  route.kind === 'handler'
1049
1050
  ? Object.values(staticFiles).some(file => file.routeId === route.id)
@@ -1057,36 +1058,36 @@ function printBuildSummary(
1057
1058
  ? 'ƒ'
1058
1059
  : route.hasStaticParams
1059
1060
  ? '●'
1060
- : '○';
1061
+ : '○'
1061
1062
  // An interception route is named by its own directory path (marker kept),
1062
1063
  // never by the target route it resolves to — which is a real route of its own.
1063
- const label = route.interception ? interceptionDisplayRoute(route) : route.route || '/';
1064
+ const label = route.interception ? interceptionDisplayRoute(route) : route.route || '/'
1064
1065
  // Next names a dynamic route by its source pattern (`/[dyn]`), not pnext's
1065
1066
  // `:param` form; compat apps' e2e output is matched against Next's.
1066
- console.log(` ${marker} ${nextCompatEnabled(config) ? toNextRoutePattern(label) : label}`);
1067
+ console.log(` ${marker} ${nextCompatEnabled(config) ? toNextRoutePattern(label) : label}`)
1067
1068
  }
1068
1069
  for (const file of staticMetadataSummaryFiles(staticFiles)) {
1069
- console.log(` ${dim('○')} /${file}`);
1070
+ console.log(` ${dim('○')} /${file}`)
1070
1071
  }
1071
1072
  if (hasMiddleware) {
1072
- console.log('');
1073
- console.log(` ${dim('ƒ')} Middleware`);
1073
+ console.log('')
1074
+ console.log(` ${dim('ƒ')} Middleware`)
1074
1075
  }
1075
- console.log('');
1076
+ console.log('')
1076
1077
  console.log(
1077
1078
  `${green('✓')} ${bold('Build complete')} ${dim(`in ${formatBuildDuration(durationMs)}`)}`,
1078
- );
1079
+ )
1079
1080
  // Phases that ran alongside the build are their own metric, never folded into
1080
1081
  // the build time — a typecheck fully hidden behind bundling costs 0 wall.
1081
1082
  for (const phase of parallelPhases) {
1082
1083
  console.log(
1083
1084
  `${green('✓')} ${bold(`${phase.name} complete`)} ${dim(`in ${formatBuildDuration(phase.durationMs)}`)}`,
1084
- );
1085
+ )
1085
1086
  }
1086
- console.log(` ${dim(`${parts.join(', ')} → ${outDir}`)}`);
1087
- console.log('');
1088
- console.log(` Run ${cyan('pnext start')} to serve the production build.`);
1089
- console.log(` ${cyan('pnext analyze')} to analyze the build.`);
1087
+ console.log(` ${dim(`${parts.join(', ')} → ${outDir}`)}`)
1088
+ console.log('')
1089
+ console.log(` Run ${cyan('pnext start')} to serve the production build.`)
1090
+ console.log(` ${cyan('pnext analyze')} to analyze the build.`)
1090
1091
  }
1091
1092
 
1092
1093
  function staticMetadataSummaryFiles(staticFiles: Record<string, StaticFileMetadata>) {
@@ -1098,15 +1099,15 @@ function staticMetadataSummaryFiles(staticFiles: Record<string, StaticFileMetada
1098
1099
  ),
1099
1100
  )
1100
1101
  .map(([file]) => file)
1101
- .sort();
1102
+ .sort()
1102
1103
  }
1103
1104
 
1104
1105
  function formatBuildDuration(durationMs: number) {
1105
- const totalSeconds = Math.max(0, durationMs) / 1000;
1106
- if (totalSeconds < 60) return `${totalSeconds.toFixed(totalSeconds < 10 ? 2 : 1)}s`;
1107
- const minutes = Math.floor(totalSeconds / 60);
1108
- const seconds = Math.round(totalSeconds % 60);
1109
- return `${minutes}m ${seconds}s`;
1106
+ const totalSeconds = Math.max(0, durationMs) / 1000
1107
+ if (totalSeconds < 60) return `${totalSeconds.toFixed(totalSeconds < 10 ? 2 : 1)}s`
1108
+ const minutes = Math.floor(totalSeconds / 60)
1109
+ const seconds = Math.round(totalSeconds % 60)
1110
+ return `${minutes}m ${seconds}s`
1110
1111
  }
1111
1112
 
1112
1113
  /**
@@ -1117,14 +1118,14 @@ function formatBuildDuration(durationMs: number) {
1117
1118
  * start.ts on a static HIT). Mirrors the static-prerender loop's capture.
1118
1119
  */
1119
1120
  interface ShellCacheMeta {
1120
- cacheLife?: CacheLifeStash;
1121
- tags: string[];
1121
+ cacheLife?: CacheLifeStash
1122
+ tags: string[]
1122
1123
  /** Prerendered `Link` header (font preloads + react-dom resource hints). */
1123
- linkHeader?: string;
1124
+ linkHeader?: string
1124
1125
  }
1125
1126
 
1126
1127
  /** A baked partial/fallback shell (renderPartialShell's non-null result). */
1127
- type PrebuiltShell = NonNullable<Awaited<ReturnType<typeof renderPartialShell>>>;
1128
+ type PrebuiltShell = NonNullable<Awaited<ReturnType<typeof renderPartialShell>>>
1128
1129
 
1129
1130
  /**
1130
1131
  * Render a shell (partial/fallback) inside a work unit + render cache-meta scope
@@ -1135,32 +1136,36 @@ async function renderShellWithCacheMeta(
1135
1136
  config: Awaited<ReturnType<typeof loadConfig>>,
1136
1137
  route: RouteManifestEntry,
1137
1138
  render: () => Promise<PrebuiltShell | null>,
1138
- ): Promise<{ prebuilt: PrebuiltShell | null; meta: ShellCacheMeta; vary: BuildResponseVary | undefined }> {
1139
+ ): Promise<{
1140
+ prebuilt: PrebuiltShell | null
1141
+ meta: ShellCacheMeta
1142
+ vary: BuildResponseVary | undefined
1143
+ }> {
1139
1144
  // A shell prerender resolves its `use cache` scopes asynchronously and the cacheLife-to-work-unit
1140
1145
  // stash is not in scope during that propagation. collectRenderMeta's render cache-meta IS, so the
1141
1146
  // effective revalidate/expire/stale windows come back on the result.
1142
- let linkHeader: string | undefined;
1147
+ let linkHeader: string | undefined
1143
1148
  // A BAKED segment is rendered here, outside any request, so without this scope its param accesses
1144
1149
  // are never tracked and the artifact ships with no vary set - which keys every prerendered route
1145
1150
  // on its exact URL and re-fetches the shared shell for every param value.
1146
1151
  const tracked = await buildCompat().withVaryParamsTracking(async () =>
1147
1152
  runWithWorkUnit('render', async () => {
1148
- const rendered = await getRenderExtensions().collectRenderMeta(
1149
- () => runWithNextBuildPhase(render),
1150
- {
1151
- fetchCache: route.segmentConfig?.fetchCache,
1152
- route: route.route || '/',
1153
- prerender: true,
1154
- },
1155
- );
1156
- // Font preloads + react-dom preload() hints recorded during the shell
1157
- // render become the route's serve-time `Link` header (a PPR resume never
1158
- // re-runs the components that emitted them). Read before the unit unwinds.
1159
- linkHeader = buildCompat().takeFontLinkHeader();
1160
- return rendered;
1153
+ const rendered = await getRenderExtensions().collectRenderMeta(
1154
+ () => runWithNextBuildPhase(render),
1155
+ {
1156
+ fetchCache: route.segmentConfig?.fetchCache,
1157
+ route: route.route || '/',
1158
+ prerender: true,
1159
+ },
1160
+ )
1161
+ // Font preloads + react-dom preload() hints recorded during the shell
1162
+ // render become the route's serve-time `Link` header (a PPR resume never
1163
+ // re-runs the components that emitted them). Read before the unit unwinds.
1164
+ linkHeader = buildCompat().takeFontLinkHeader()
1165
+ return rendered
1161
1166
  }),
1162
- );
1163
- const result = tracked.value;
1167
+ )
1168
+ const result = tracked.value
1164
1169
  const cacheLife: CacheLifeStash | undefined =
1165
1170
  result.revalidateSeconds !== undefined ||
1166
1171
  result.expireSeconds !== undefined ||
@@ -1172,7 +1177,7 @@ async function renderShellWithCacheMeta(
1172
1177
  ...(result.expireSeconds !== undefined ? { expireSeconds: result.expireSeconds } : {}),
1173
1178
  ...(result.staleSeconds !== undefined ? { staleSeconds: result.staleSeconds } : {}),
1174
1179
  }
1175
- : undefined;
1180
+ : undefined
1176
1181
  return {
1177
1182
  prebuilt: result.value,
1178
1183
  meta: {
@@ -1181,7 +1186,7 @@ async function renderShellWithCacheMeta(
1181
1186
  ...(linkHeader ? { linkHeader } : {}),
1182
1187
  },
1183
1188
  vary: tracked.vary,
1184
- };
1189
+ }
1185
1190
  }
1186
1191
 
1187
1192
  /**
@@ -1190,7 +1195,7 @@ async function renderShellWithCacheMeta(
1190
1195
  * client prefetches: such data is not worth prefetching, so its boundary stays a hole the
1191
1196
  * navigation fills live.
1192
1197
  */
1193
- const DYNAMIC_EXPIRE_SECONDS = 300;
1198
+ const DYNAMIC_EXPIRE_SECONDS = 300
1194
1199
 
1195
1200
  /**
1196
1201
  * True when the route's own sources declare a cacheLife that expires inside the dynamic-expire
@@ -1199,18 +1204,18 @@ const DYNAMIC_EXPIRE_SECONDS = 300;
1199
1204
  */
1200
1205
  function routeDeclaresShortLivedCache(route: RouteManifestEntry): boolean {
1201
1206
  for (const file of route.sourceFiles) {
1202
- if (!existsSync(file)) continue;
1203
- const source = readTextSync(file);
1207
+ if (!existsSync(file)) continue
1208
+ const source = readTextSync(file)
1204
1209
  for (const literal of source.matchAll(/cacheLife\s*\(\s*\{[^}]*\bexpire\s*:\s*([^,}]+)/gs)) {
1205
- const value = staticNumberExpression(literal[1]!);
1206
- if (value !== undefined && value < DYNAMIC_EXPIRE_SECONDS) return true;
1210
+ const value = staticNumberExpression(literal[1]!)
1211
+ if (value !== undefined && value < DYNAMIC_EXPIRE_SECONDS) return true
1207
1212
  }
1208
1213
  for (const named of source.matchAll(/cacheLife\s*\(\s*['"]([^'"]+)['"]\s*\)/g)) {
1209
- const expire = builtInCacheLifeExpire(named[1]!);
1210
- if (expire !== undefined && expire < DYNAMIC_EXPIRE_SECONDS) return true;
1214
+ const expire = builtInCacheLifeExpire(named[1]!)
1215
+ if (expire !== undefined && expire < DYNAMIC_EXPIRE_SECONDS) return true
1211
1216
  }
1212
1217
  }
1213
- return false;
1218
+ return false
1214
1219
  }
1215
1220
 
1216
1221
  /**
@@ -1225,15 +1230,15 @@ async function renderPrefetchBody(
1225
1230
  route: RouteManifestEntry,
1226
1231
  render: () => Promise<PrebuiltShell | null>,
1227
1232
  ): Promise<{ prebuilt: PrebuiltShell; vary: BuildResponseVary | undefined } | undefined> {
1228
- if (!routeDeclaresShortLivedCache(route)) return undefined;
1233
+ if (!routeDeclaresShortLivedCache(route)) return undefined
1229
1234
  try {
1230
- const { prebuilt, vary } = await renderShellWithCacheMeta(config, route, render);
1231
- return prebuilt ? { prebuilt, vary } : undefined;
1235
+ const { prebuilt, vary } = await renderShellWithCacheMeta(config, route, render)
1236
+ return prebuilt ? { prebuilt, vary } : undefined
1232
1237
  } catch {
1233
1238
  // Best-effort: the document shell doubles as the prefetch body, as before.
1234
- return undefined;
1239
+ return undefined
1235
1240
  } finally {
1236
- abortActivePrerenderScopes();
1241
+ abortActivePrerenderScopes()
1237
1242
  }
1238
1243
  }
1239
1244
 
@@ -1241,29 +1246,29 @@ async function buildPprShell(
1241
1246
  config: Awaited<ReturnType<typeof loadConfig>>,
1242
1247
  route: RouteManifestEntry,
1243
1248
  ) {
1244
- const url = new URL(`http://pnext.local${route.route}`);
1249
+ const url = new URL(`http://pnext.local${route.route}`)
1245
1250
  // Render under the production-build phase so build-phase sentinels
1246
1251
  // (process.env.NEXT_PHASE) resolve to their buildtime value in the shell.
1247
1252
  const { prebuilt, meta, vary } = await renderShellWithCacheMeta(config, route, () =>
1248
1253
  renderPartialShell({ config, route, url }),
1249
- );
1254
+ )
1250
1255
  if (!prebuilt) {
1251
1256
  // Dynamic data escaped every Suspense boundary — there is no static shell to
1252
1257
  // bake, so drop the PPR flag and let the route render fully dynamically.
1253
- route.ppr = false;
1254
- return;
1255
- }
1256
- route.pprHoles = prebuilt.holes;
1257
- route.pprMetadata = prebuilt.metadataDynamic || undefined;
1258
- persistRouteCacheLife(route, meta.cacheLife);
1259
- recordRouteCacheTags(route, meta.tags);
1260
- if (meta.linkHeader) route.linkHeader = meta.linkHeader;
1261
- const file = pprShellPath(config.outPath, route.id);
1262
- await mkdir(path.dirname(file), { recursive: true });
1263
- await writeText(file, prebuilt.shell);
1258
+ route.ppr = false
1259
+ return
1260
+ }
1261
+ route.pprHoles = prebuilt.holes
1262
+ route.pprMetadata = prebuilt.metadataDynamic || undefined
1263
+ persistRouteCacheLife(route, meta.cacheLife)
1264
+ recordRouteCacheTags(route, meta.tags)
1265
+ if (meta.linkHeader) route.linkHeader = meta.linkHeader
1266
+ const file = pprShellPath(config.outPath, route.id)
1267
+ await mkdir(path.dirname(file), { recursive: true })
1268
+ await writeText(file, prebuilt.shell)
1264
1269
  const prefetchBody = (await renderPrefetchBody(config, route, () =>
1265
1270
  renderPartialShell({ config, route, url, prefetchShell: true }),
1266
- )) ?? { prebuilt, vary };
1271
+ )) ?? { prebuilt, vary }
1267
1272
  await emitSegmentArtifacts(
1268
1273
  config,
1269
1274
  route,
@@ -1275,25 +1280,25 @@ async function buildPprShell(
1275
1280
  // renderPartialShell against the PATTERN url: a route-level, params-hanging
1276
1281
  // render, so an empty tracked set is honest (see buildVaryTrusted).
1277
1282
  true,
1278
- );
1283
+ )
1279
1284
  }
1280
1285
 
1281
1286
  /** Union shell-prerender cache tags onto the route (PPR-shell tag staleness). */
1282
1287
  function recordRouteCacheTags(route: RouteManifestEntry, tags: readonly string[]): void {
1283
- if (tags.length === 0) return;
1284
- route.cacheTags = [...new Set([...(route.cacheTags ?? []), ...tags])];
1288
+ if (tags.length === 0) return
1289
+ route.cacheTags = [...new Set([...(route.cacheTags ?? []), ...tags])]
1285
1290
  }
1286
1291
 
1287
1292
  /** Persist a shell render's effective cacheLife on the route so the static-serve
1288
1293
  * path (start.ts) can re-emit the SWR cache-control + x-nextjs-stale-time headers
1289
1294
  * on a pure static HIT/MISS (the header finalizer only fires on a live render). */
1290
1295
  function persistRouteCacheLife(route: RouteManifestEntry, life: CacheLifeStash | undefined): void {
1291
- if (!life) return;
1296
+ if (!life) return
1292
1297
  route.cacheLife = {
1293
1298
  ...(life.revalidateSeconds !== undefined ? { revalidateSeconds: life.revalidateSeconds } : {}),
1294
1299
  ...(life.expireSeconds !== undefined ? { expireSeconds: life.expireSeconds } : {}),
1295
1300
  ...(life.staleSeconds !== undefined ? { staleSeconds: life.staleSeconds } : {}),
1296
- };
1301
+ }
1297
1302
  }
1298
1303
 
1299
1304
  /**
@@ -1317,14 +1322,12 @@ async function emitSegmentArtifacts(
1317
1322
  * otherwise defends against. Never pass `true` for a CONCRETE-param render. */
1318
1323
  fallbackParamsRender = false,
1319
1324
  ): Promise<void> {
1320
- const isStatic = route.mode === 'static' && !postponed;
1321
- const compat = buildCompat();
1325
+ const isStatic = route.mode === 'static' && !postponed
1326
+ const compat = buildCompat()
1322
1327
  const staleTime =
1323
1328
  staleTimeFromRouteSources(route) ??
1324
- (isStatic
1325
- ? compat.defaultStaticStaleTimeSeconds
1326
- : compat.defaultDynamicStaleTimeSeconds);
1327
- const bodySizeBytes = Buffer.byteLength(body);
1329
+ (isStatic ? compat.defaultStaticStaleTimeSeconds : compat.defaultDynamicStaleTimeSeconds)
1330
+ const bodySizeBytes = Buffer.byteLength(body)
1328
1331
  const tree = compat.buildRootTreePrefetch({
1329
1332
  pathname: route.route,
1330
1333
  isStatic,
@@ -1332,7 +1335,8 @@ async function emitSegmentArtifacts(
1332
1335
  routeId: route.id,
1333
1336
  bodySizeBytes,
1334
1337
  postponed,
1335
- runtimePrefetch: (route.segmentConfig as { prefetch?: unknown } | undefined)?.prefetch === 'allow-runtime',
1338
+ runtimePrefetch:
1339
+ (route.segmentConfig as { prefetch?: unknown } | undefined)?.prefetch === 'allow-runtime',
1336
1340
  // The route's <title> is dynamic and ships as its own `/_head` response;
1337
1341
  // announcing it in the TREE lets the client fetch the head before the body
1338
1342
  // (Next's response order) instead of learning it from the body response.
@@ -1342,7 +1346,7 @@ async function emitSegmentArtifacts(
1342
1346
  ...(route.pprMetadata === true
1343
1347
  ? { headOutlined: true, ...(/[:*]/.test(route.route) ? { headFirst: true } : {}) }
1344
1348
  : {}),
1345
- });
1349
+ })
1346
1350
  const meta = compat.buildSegmentMeta({
1347
1351
  status: statusOverride ?? 200,
1348
1352
  staleTime,
@@ -1350,11 +1354,11 @@ async function emitSegmentArtifacts(
1350
1354
  bodySizeBytes,
1351
1355
  prefetchHints: { [route.route]: tree.tree.prefetchHints },
1352
1356
  ...(vary && buildVaryTrusted(route, vary, fallbackParamsRender) ? { vary } : {}),
1353
- });
1354
- await mkdir(compat.segmentDir(config.outPath, route.id), { recursive: true });
1355
- await writeText(compat.treeSegmentFile(config.outPath, route.id), JSON.stringify(tree));
1356
- await writeText(compat.bodySegmentFile(config.outPath, route.id), body);
1357
- await writeText(compat.segmentMetaFile(config.outPath, route.id), JSON.stringify(meta));
1357
+ })
1358
+ await mkdir(compat.segmentDir(config.outPath, route.id), { recursive: true })
1359
+ await writeText(compat.treeSegmentFile(config.outPath, route.id), JSON.stringify(tree))
1360
+ await writeText(compat.bodySegmentFile(config.outPath, route.id), body)
1361
+ await writeText(compat.segmentMetaFile(config.outPath, route.id), JSON.stringify(meta))
1358
1362
  await emitNextSegmentArtifacts(
1359
1363
  config.root,
1360
1364
  route,
@@ -1362,7 +1366,7 @@ async function emitSegmentArtifacts(
1362
1366
  compat.rootTreePrefetchText(tree, 'flight'),
1363
1367
  meta,
1364
1368
  segmentMetaHeaders(cacheMeta),
1365
- );
1369
+ )
1366
1370
  }
1367
1371
 
1368
1372
  /**
@@ -1387,14 +1391,14 @@ function recordPrerenderVary(
1387
1391
  routePath: string,
1388
1392
  vary: BuildResponseVary | undefined,
1389
1393
  ): void {
1390
- if (!vary || !buildVaryTrusted(route, vary, false)) return;
1391
- if (vary.params.length === 0 && !vary.search) return;
1392
- route.prerenderVary ??= {};
1394
+ if (!vary || !buildVaryTrusted(route, vary, false)) return
1395
+ if (vary.params.length === 0 && !vary.search) return
1396
+ route.prerenderVary ??= {}
1393
1397
  route.prerenderVary[routePath] = {
1394
1398
  vary: buildCompat().varyNamesFor(vary, 'body'),
1395
1399
  layoutVary: buildCompat().varyNamesFor(vary, 'layout'),
1396
1400
  pageVary: buildCompat().varyNamesFor(vary, 'page'),
1397
- };
1401
+ }
1398
1402
  }
1399
1403
 
1400
1404
  function buildVaryTrusted(
@@ -1402,9 +1406,9 @@ function buildVaryTrusted(
1402
1406
  vary: BuildResponseVary,
1403
1407
  fallbackParamsRender: boolean,
1404
1408
  ): boolean {
1405
- if (vary.params.length > 0 || vary.search) return true;
1406
- if (fallbackParamsRender) return true;
1407
- return route.params.length === 0 && !route.catchAll;
1409
+ if (vary.params.length > 0 || vary.search) return true
1410
+ if (fallbackParamsRender) return true
1411
+ return route.params.length === 0 && !route.catchAll
1408
1412
  }
1409
1413
 
1410
1414
  /**
@@ -1415,11 +1419,11 @@ function buildVaryTrusted(
1415
1419
  * tags). Empty when the route declared no cacheLife and no tags.
1416
1420
  */
1417
1421
  function segmentMetaHeaders(cacheMeta: ShellCacheMeta | undefined): Record<string, string> {
1418
- const headers: Record<string, string> = {};
1419
- if (!cacheMeta) return headers;
1420
- for (const [key, value] of cacheLifeResponseHeaders(cacheMeta.cacheLife)) headers[key] = value;
1421
- if (cacheMeta.tags.length > 0) headers['x-next-cache-tags'] = cacheMeta.tags.join(',');
1422
- return headers;
1422
+ const headers: Record<string, string> = {}
1423
+ if (!cacheMeta) return headers
1424
+ for (const [key, value] of cacheLifeResponseHeaders(cacheMeta.cacheLife)) headers[key] = value
1425
+ if (cacheMeta.tags.length > 0) headers['x-next-cache-tags'] = cacheMeta.tags.join(',')
1426
+ return headers
1423
1427
  }
1424
1428
 
1425
1429
  async function emitNextSegmentArtifacts(
@@ -1430,9 +1434,9 @@ async function emitNextSegmentArtifacts(
1430
1434
  meta: SegmentMeta,
1431
1435
  headers: Record<string, string> = {},
1432
1436
  ): Promise<void> {
1433
- const appPath = nextAppRoutePath(route.route);
1434
- const appFile = path.join(root, '.next', 'server', 'app', appPath);
1435
- const segmentsDir = `${appFile}.segments`;
1437
+ const appPath = nextAppRoutePath(route.route)
1438
+ const appFile = path.join(root, '.next', 'server', 'app', appPath)
1439
+ const segmentsDir = `${appFile}.segments`
1436
1440
  const nextMeta = {
1437
1441
  status: meta.status,
1438
1442
  // Next's page `.meta` carries the route's response headers (x-nextjs-stale-
@@ -1445,22 +1449,22 @@ async function emitNextSegmentArtifacts(
1445
1449
  ...(meta.segmentSizes ? { segmentSizes: meta.segmentSizes } : {}),
1446
1450
  ...(meta.inlinedSegmentPaths ? { inlinedSegmentPaths: meta.inlinedSegmentPaths } : {}),
1447
1451
  ...(meta.prefetchHints ? { prefetchHints: meta.prefetchHints } : {}),
1448
- };
1452
+ }
1449
1453
 
1450
- await mkdir(segmentsDir, { recursive: true });
1454
+ await mkdir(segmentsDir, { recursive: true })
1451
1455
  // A baked shell is stored OPEN (no closing tags) so the serve path can stream resumed holes into
1452
1456
  // it. Next's equivalent file is instead the finished document whenever nothing was postponed, and
1453
1457
  // suites tell a complete prerender from an incomplete shell by testing for the `</html>` tail.
1454
1458
  // Close a hole-free shell here; a postponed one stays open, exactly as Next leaves its partials.
1455
- await writeText(`${appFile}.html`, meta.postponed ? body : `${body}\n </body></html>`);
1456
- await writeText(`${appFile}.meta`, JSON.stringify(nextMeta));
1457
- await writeText(path.join(segmentsDir, '_tree.segment.rsc'), tree);
1458
- await writeText(path.join(segmentsDir, '_full.segment.rsc'), body);
1459
- await writeText(path.join(segmentsDir, '_index.segment.rsc'), body);
1459
+ await writeText(`${appFile}.html`, meta.postponed ? body : `${body}\n </body></html>`)
1460
+ await writeText(`${appFile}.meta`, JSON.stringify(nextMeta))
1461
+ await writeText(path.join(segmentsDir, '_tree.segment.rsc'), tree)
1462
+ await writeText(path.join(segmentsDir, '_full.segment.rsc'), body)
1463
+ await writeText(path.join(segmentsDir, '_index.segment.rsc'), body)
1460
1464
 
1461
- const routeSegment = appPath === 'index' ? '__PAGE__' : appPath;
1462
- await writeNestedSegment(segmentsDir, `${routeSegment}.segment.rsc`, body);
1463
- await writeNestedSegment(segmentsDir, path.join(routeSegment, '__PAGE__.segment.rsc'), body);
1465
+ const routeSegment = appPath === 'index' ? '__PAGE__' : appPath
1466
+ await writeNestedSegment(segmentsDir, `${routeSegment}.segment.rsc`, body)
1467
+ await writeNestedSegment(segmentsDir, path.join(routeSegment, '__PAGE__.segment.rsc'), body)
1464
1468
  }
1465
1469
 
1466
1470
  /**
@@ -1479,11 +1483,11 @@ async function emitConcreteNextPageArtifacts(
1479
1483
  routePath: string,
1480
1484
  rendered: { html: string; status: number; postponed?: boolean },
1481
1485
  ): Promise<void> {
1482
- if (!config.compat?.next || !cacheComponents()) return;
1483
- const compat = buildCompat();
1484
- const postponed = rendered.postponed ?? false;
1485
- const bodySizeBytes = Buffer.byteLength(rendered.html);
1486
- const staleTime = staleTimeFromRouteSources(route) ?? compat.defaultStaticStaleTimeSeconds;
1486
+ if (!config.compat?.next || !cacheComponents()) return
1487
+ const compat = buildCompat()
1488
+ const postponed = rendered.postponed ?? false
1489
+ const bodySizeBytes = Buffer.byteLength(rendered.html)
1490
+ const staleTime = staleTimeFromRouteSources(route) ?? compat.defaultStaticStaleTimeSeconds
1487
1491
  const tree = compat.buildRootTreePrefetch({
1488
1492
  pathname: routePath,
1489
1493
  isStatic: !postponed,
@@ -1491,17 +1495,17 @@ async function emitConcreteNextPageArtifacts(
1491
1495
  routeId: route.id,
1492
1496
  bodySizeBytes,
1493
1497
  postponed,
1494
- });
1498
+ })
1495
1499
  const meta = compat.buildSegmentMeta({
1496
1500
  status: rendered.status,
1497
1501
  staleTime,
1498
1502
  postponed,
1499
1503
  bodySizeBytes,
1500
- });
1501
- const appFile = path.join(config.root, '.next', 'server', 'app', nextAppRoutePath(routePath));
1502
- const segmentsDir = `${appFile}.segments`;
1503
- await mkdir(segmentsDir, { recursive: true });
1504
- await writeText(`${appFile}.html`, rendered.html);
1504
+ })
1505
+ const appFile = path.join(config.root, '.next', 'server', 'app', nextAppRoutePath(routePath))
1506
+ const segmentsDir = `${appFile}.segments`
1507
+ await mkdir(segmentsDir, { recursive: true })
1508
+ await writeText(`${appFile}.html`, rendered.html)
1505
1509
  await writeText(
1506
1510
  `${appFile}.meta`,
1507
1511
  JSON.stringify({
@@ -1512,46 +1516,46 @@ async function emitConcreteNextPageArtifacts(
1512
1516
  segmentPaths: meta.segmentPaths,
1513
1517
  ...(meta.segmentSizes ? { segmentSizes: meta.segmentSizes } : {}),
1514
1518
  }),
1515
- );
1519
+ )
1516
1520
  await writeText(
1517
1521
  path.join(segmentsDir, '_tree.segment.rsc'),
1518
1522
  compat.rootTreePrefetchText(tree, 'flight'),
1519
- );
1523
+ )
1520
1524
  }
1521
1525
 
1522
1526
  function staleTimeFromRouteSources(route: RouteManifestEntry): number | undefined {
1523
- let found: number | undefined;
1527
+ let found: number | undefined
1524
1528
  for (const file of route.sourceFiles) {
1525
- if (!existsSync(file)) continue;
1526
- const source = readTextSync(file);
1529
+ if (!existsSync(file)) continue
1530
+ const source = readTextSync(file)
1527
1531
  for (const literal of source.matchAll(/cacheLife\s*\(\s*\{[^}]*\bstale\s*:\s*([^,}]+)/gs)) {
1528
- const value = staticNumberExpression(literal[1]!);
1529
- if (value !== undefined) found = minDefined(found, value);
1532
+ const value = staticNumberExpression(literal[1]!)
1533
+ if (value !== undefined) found = minDefined(found, value)
1530
1534
  }
1531
1535
  for (const named of source.matchAll(/cacheLife\s*\(\s*['"]([^'"]+)['"]\s*\)/g)) {
1532
- const namedValue = builtInCacheLifeStale(named[1]!);
1533
- if (namedValue !== undefined) found = minDefined(found, namedValue);
1536
+ const namedValue = builtInCacheLifeStale(named[1]!)
1537
+ if (namedValue !== undefined) found = minDefined(found, namedValue)
1534
1538
  }
1535
1539
  }
1536
- return found;
1540
+ return found
1537
1541
  }
1538
1542
 
1539
1543
  function readTextSync(file: string): string {
1540
1544
  try {
1541
- return existsSync(file) && statSync(file).isFile() ? readFileSync(file, 'utf8') : '';
1545
+ return existsSync(file) && statSync(file).isFile() ? readFileSync(file, 'utf8') : ''
1542
1546
  } catch {
1543
- return '';
1547
+ return ''
1544
1548
  }
1545
1549
  }
1546
1550
 
1547
1551
  function staticNumberExpression(expression: string): number | undefined {
1548
- const trimmed = expression.trim();
1549
- if (/^\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
1550
- const product = trimmed.split('*').map(part => part.trim());
1552
+ const trimmed = expression.trim()
1553
+ if (/^\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed)
1554
+ const product = trimmed.split('*').map(part => part.trim())
1551
1555
  if (product.length > 1 && product.every(part => /^\d+(?:\.\d+)?$/.test(part))) {
1552
- return product.reduce((value, part) => value * Number(part), 1);
1556
+ return product.reduce((value, part) => value * Number(part), 1)
1553
1557
  }
1554
- return undefined;
1558
+ return undefined
1555
1559
  }
1556
1560
 
1557
1561
  function builtInCacheLifeStale(profile: string): number | undefined {
@@ -1561,16 +1565,16 @@ function builtInCacheLifeStale(profile: string): number | undefined {
1561
1565
  // (their client stale window is below the runtime threshold), so their
1562
1566
  // staleness must not shorten the route's prefetch expiry — only the
1563
1567
  // longer-lived caches that actually land in the prerender count.
1564
- return undefined;
1568
+ return undefined
1565
1569
  case 'default':
1566
1570
  case 'minutes':
1567
1571
  case 'hours':
1568
1572
  case 'days':
1569
1573
  case 'weeks':
1570
1574
  case 'max':
1571
- return 300;
1575
+ return 300
1572
1576
  default:
1573
- return undefined;
1577
+ return undefined
1574
1578
  }
1575
1579
  }
1576
1580
 
@@ -1578,37 +1582,37 @@ function builtInCacheLifeStale(profile: string): number | undefined {
1578
1582
  function builtInCacheLifeExpire(profile: string): number | undefined {
1579
1583
  switch (profile) {
1580
1584
  case 'seconds':
1581
- return 60;
1585
+ return 60
1582
1586
  case 'minutes':
1583
- return 3600;
1587
+ return 3600
1584
1588
  case 'hours':
1585
- return 86400;
1589
+ return 86400
1586
1590
  case 'days':
1587
- return 604800;
1591
+ return 604800
1588
1592
  case 'weeks':
1589
- return 2592000;
1593
+ return 2592000
1590
1594
  case 'default':
1591
1595
  case 'max':
1592
- return 31536000;
1596
+ return 31536000
1593
1597
  default:
1594
- return undefined;
1598
+ return undefined
1595
1599
  }
1596
1600
  }
1597
1601
 
1598
1602
  function minDefined(a: number | undefined, b: number | undefined): number | undefined {
1599
- if (a === undefined) return b;
1600
- if (b === undefined) return a;
1601
- return Math.min(a, b);
1603
+ if (a === undefined) return b
1604
+ if (b === undefined) return a
1605
+ return Math.min(a, b)
1602
1606
  }
1603
1607
 
1604
1608
  async function writeNestedSegment(root: string, relative: string, body: string): Promise<void> {
1605
- const file = path.join(root, relative);
1606
- await mkdir(path.dirname(file), { recursive: true });
1607
- await writeText(file, body);
1609
+ const file = path.join(root, relative)
1610
+ await mkdir(path.dirname(file), { recursive: true })
1611
+ await writeText(file, body)
1608
1612
  }
1609
1613
 
1610
1614
  function nextAppRoutePath(route: string): string {
1611
- return route.replace(/^\/+/, '') || 'index';
1615
+ return route.replace(/^\/+/, '') || 'index'
1612
1616
  }
1613
1617
 
1614
1618
  /**
@@ -1622,15 +1626,15 @@ function nextAppRoutePath(route: string): string {
1622
1626
  function shellRendersContentOutsideHoles(shell: string): boolean {
1623
1627
  // A shell is a PARTIAL document: it usually stops mid-stream with no closing
1624
1628
  // `</body>`, so read from the body tag to whichever comes first.
1625
- const open = /<body[^>]*>/.exec(shell);
1626
- if (!open) return false;
1627
- const rest = shell.slice(open.index + open[0].length);
1628
- const body = rest.split('</body>')[0] ?? '';
1629
+ const open = /<body[^>]*>/.exec(shell)
1630
+ if (!open) return false
1631
+ const rest = shell.slice(open.index + open[0].length)
1632
+ const body = rest.split('</body>')[0] ?? ''
1629
1633
  const outside = stripBalanced(body, 'pnext-suspense')
1630
1634
  .replace(/<(script|template|style)[^>]*>[\s\S]*?<\/\1>/g, '')
1631
- .replace(/<!--[\s\S]*?-->/g, '');
1632
- if (/<(img|svg|video|canvas|input)[\s>]/.test(outside)) return true;
1633
- return outside.replace(/<[^>]*>/g, '').trim().length > 0;
1635
+ .replace(/<!--[\s\S]*?-->/g, '')
1636
+ if (/<(img|svg|video|canvas|input)[\s>]/.test(outside)) return true
1637
+ return outside.replace(/<[^>]*>/g, '').trim().length > 0
1634
1638
  }
1635
1639
 
1636
1640
  /**
@@ -1640,40 +1644,40 @@ function shellRendersContentOutsideHoles(shell: string): boolean {
1640
1644
  * (nothing to show, stays blocking) and a route whose whole body is one hole that still paints.
1641
1645
  */
1642
1646
  function shellHolesRenderFallbackContent(shell: string): boolean {
1643
- const open = /<body[^>]*>/.exec(shell);
1644
- if (!open) return false;
1647
+ const open = /<body[^>]*>/.exec(shell)
1648
+ if (!open) return false
1645
1649
  const body = (shell.slice(open.index + open[0].length).split('</body>')[0] ?? '')
1646
1650
  .replace(/<(script|template|style)[^>]*>[\s\S]*?<\/\1>/g, '')
1647
- .replace(/<!--[\s\S]*?-->/g, '');
1648
- const outside = stripBalanced(body, 'pnext-suspense');
1649
- const text = (h: string) => h.replace(/<[^>]*>/g, '').trim().length;
1650
- const media = (h: string) => /<(img|svg|video|canvas|input)[\s>]/.test(h);
1651
- return text(body) - text(outside) > 0 || (media(body) && !media(outside));
1651
+ .replace(/<!--[\s\S]*?-->/g, '')
1652
+ const outside = stripBalanced(body, 'pnext-suspense')
1653
+ const text = (h: string) => h.replace(/<[^>]*>/g, '').trim().length
1654
+ const media = (h: string) => /<(img|svg|video|canvas|input)[\s>]/.test(h)
1655
+ return text(body) - text(outside) > 0 || (media(body) && !media(outside))
1652
1656
  }
1653
1657
 
1654
1658
  /** Remove every balanced `<tag>…</tag>` region (nesting-aware) from `html`. */
1655
1659
  function stripBalanced(html: string, tag: string): string {
1656
- const open = new RegExp(`<${tag}[\\s>]`, 'g');
1657
- const boundary = new RegExp(`<${tag}[\\s>]|</${tag}>`, 'g');
1658
- let out = '';
1659
- let cursor = 0;
1660
+ const open = new RegExp(`<${tag}[\\s>]`, 'g')
1661
+ const boundary = new RegExp(`<${tag}[\\s>]|</${tag}>`, 'g')
1662
+ let out = ''
1663
+ let cursor = 0
1660
1664
  for (let start = open.exec(html); start; start = open.exec(html)) {
1661
- if (start.index < cursor) continue;
1662
- out += html.slice(cursor, start.index);
1663
- boundary.lastIndex = start.index + 1;
1664
- let depth = 1;
1665
- let end = html.length;
1665
+ if (start.index < cursor) continue
1666
+ out += html.slice(cursor, start.index)
1667
+ boundary.lastIndex = start.index + 1
1668
+ let depth = 1
1669
+ let end = html.length
1666
1670
  for (let hit = boundary.exec(html); hit; hit = boundary.exec(html)) {
1667
- depth += hit[0].startsWith('</') ? -1 : 1;
1671
+ depth += hit[0].startsWith('</') ? -1 : 1
1668
1672
  if (depth === 0) {
1669
- end = hit.index + hit[0].length;
1670
- break;
1673
+ end = hit.index + hit[0].length
1674
+ break
1671
1675
  }
1672
1676
  }
1673
- cursor = end;
1674
- open.lastIndex = cursor;
1677
+ cursor = end
1678
+ open.lastIndex = cursor
1675
1679
  }
1676
- return out + html.slice(cursor);
1680
+ return out + html.slice(cursor)
1677
1681
  }
1678
1682
 
1679
1683
  /**
@@ -1694,24 +1698,24 @@ async function buildCacheComponentsShell(
1694
1698
  config: Awaited<ReturnType<typeof loadConfig>>,
1695
1699
  route: RouteManifestEntry,
1696
1700
  ): Promise<boolean> {
1697
- const url = new URL(`http://pnext.local${route.route}`);
1698
- const hasDynamicParams = route.params.length > 0 || Boolean(route.catchAll);
1701
+ const url = new URL(`http://pnext.local${route.route}`)
1702
+ const hasDynamicParams = route.params.length > 0 || Boolean(route.catchAll)
1699
1703
  // Build-phase so buildtime sentinels resolve correctly in the shell.
1700
- beginShellSourceTracking();
1701
- let renderResult: Awaited<ReturnType<typeof renderShellWithCacheMeta>>;
1704
+ beginShellSourceTracking()
1705
+ let renderResult: Awaited<ReturnType<typeof renderShellWithCacheMeta>>
1702
1706
  try {
1703
1707
  renderResult = await renderShellWithCacheMeta(config, route, () =>
1704
1708
  hasDynamicParams
1705
1709
  ? renderFallbackShell({ config, route, url })
1706
1710
  : renderPartialShell({ config, route, url }),
1707
- );
1711
+ )
1708
1712
  } finally {
1709
1713
  // Belt-and-braces: force-abort any prerender scope this route left armed so a
1710
1714
  // stray cacheSignal-bound timer can never keep the build's event loop alive.
1711
- abortActivePrerenderScopes();
1715
+ abortActivePrerenderScopes()
1712
1716
  }
1713
- const { prebuilt, meta } = renderResult;
1714
- const shellSources = endShellSourceTracking();
1717
+ const { prebuilt, meta } = renderResult
1718
+ const shellSources = endShellSourceTracking()
1715
1719
  // A base fallback shell provides NO partial-shell value when it is either null (dynamic data
1716
1720
  // escaped every boundary) or params-only-empty (its only holes come from `params`, which are
1717
1721
  // URL-derived and resolve at request time). Such a base shell is never served as a postponed
@@ -1744,35 +1748,37 @@ async function buildCacheComponentsShell(
1744
1748
  (shellRendersContentOutsideHoles(prebuilt.shell) ||
1745
1749
  shellHolesRenderFallbackContent(prebuilt.shell))
1746
1750
  ) &&
1747
- !(hasDynamicParams && routeSourcesUseRequestApi(config.root, route)));
1751
+ !(hasDynamicParams && routeSourcesUseRequestApi(config.root, route)))
1748
1752
 
1749
1753
  if (!baseShellIsEmpty) {
1750
- route.ppr = true;
1751
- route.pprHoles = prebuilt.holes;
1752
- route.pprMetadata = prebuilt.metadataDynamic || undefined;
1753
- persistRouteCacheLife(route, meta.cacheLife);
1754
- recordRouteCacheTags(route, meta.tags);
1755
- if (meta.linkHeader) route.linkHeader = meta.linkHeader;
1756
- const file = pprShellPath(config.outPath, route.id);
1757
- await mkdir(path.dirname(file), { recursive: true });
1758
- await writeText(file, prebuilt.shell);
1754
+ route.ppr = true
1755
+ route.pprHoles = prebuilt.holes
1756
+ route.pprMetadata = prebuilt.metadataDynamic || undefined
1757
+ persistRouteCacheLife(route, meta.cacheLife)
1758
+ recordRouteCacheTags(route, meta.tags)
1759
+ if (meta.linkHeader) route.linkHeader = meta.linkHeader
1760
+ const file = pprShellPath(config.outPath, route.id)
1761
+ await mkdir(path.dirname(file), { recursive: true })
1762
+ await writeText(file, prebuilt.shell)
1759
1763
  // A param-free shell that resolved through http-access fallback recovery (a
1760
1764
  // notFound()/forbidden()/unauthorized() above the boundary) must carry the fallback STATUS in
1761
1765
  // its `.meta`, not 200. The shell prerender does not surface it, so probe with a blocking render
1762
1766
  // - the same pattern as the buildSubShells full-param probe.
1763
- let fallbackStatus: number | undefined;
1767
+ let fallbackStatus: number | undefined
1764
1768
  if (!hasDynamicParams && routeSourcesUseHttpFallback(config.root, route)) {
1765
1769
  try {
1766
1770
  const probe = await runWithWorkUnit('render', () =>
1767
1771
  getRenderExtensions()
1768
1772
  .collectRenderMeta(
1769
1773
  () =>
1770
- runWithNextBuildPhase(() => renderPageWithStatus({ config, route, params: {}, url })),
1774
+ runWithNextBuildPhase(() =>
1775
+ renderPageWithStatus({ config, route, params: {}, url }),
1776
+ ),
1771
1777
  { route: route.route || '/', prerender: true },
1772
1778
  )
1773
1779
  .then(result => result.value),
1774
- );
1775
- if (probe.status !== 200) fallbackStatus = probe.status;
1780
+ )
1781
+ if (probe.status !== 200) fallbackStatus = probe.status
1776
1782
  } catch {
1777
1783
  // Probe render failed — the shell itself is unaffected.
1778
1784
  }
@@ -1781,7 +1787,7 @@ async function buildCacheComponentsShell(
1781
1787
  hasDynamicParams
1782
1788
  ? renderFallbackShell({ config, route, url, prefetchShell: true })
1783
1789
  : renderPartialShell({ config, route, url, prefetchShell: true }),
1784
- )) ?? { prebuilt, vary: renderResult.vary };
1790
+ )) ?? { prebuilt, vary: renderResult.vary }
1785
1791
  await emitSegmentArtifacts(
1786
1792
  config,
1787
1793
  route,
@@ -1793,13 +1799,13 @@ async function buildCacheComponentsShell(
1793
1799
  // renderFallbackShell/renderPartialShell against the PATTERN url: a
1794
1800
  // route-level, params-hanging render (see buildVaryTrusted).
1795
1801
  true,
1796
- );
1802
+ )
1797
1803
 
1798
1804
  // Descending-specificity sub-shells from generateStaticParams prefixes.
1799
1805
  if (hasDynamicParams && route.hasStaticParams) {
1800
- await buildSubShells(config, route);
1806
+ await buildSubShells(config, route)
1801
1807
  }
1802
- return true;
1808
+ return true
1803
1809
  }
1804
1810
 
1805
1811
  // The base shell is empty, but a route whose generateStaticParams cover a PARTIAL prefix still
@@ -1821,21 +1827,21 @@ async function buildCacheComponentsShell(
1821
1827
  routeSourcesUseRequestApi(config.root, route) &&
1822
1828
  (await hasFullStaticParamSet(config, route))))
1823
1829
  ) {
1824
- route.ppr = true;
1825
- route.pprMetadata = prebuilt?.metadataDynamic || undefined;
1830
+ route.ppr = true
1831
+ route.pprMetadata = prebuilt?.metadataDynamic || undefined
1826
1832
  if (prebuilt) {
1827
- persistRouteCacheLife(route, meta.cacheLife);
1828
- recordRouteCacheTags(route, meta.tags);
1829
- if (meta.linkHeader) route.linkHeader = meta.linkHeader;
1833
+ persistRouteCacheLife(route, meta.cacheLife)
1834
+ recordRouteCacheTags(route, meta.tags)
1835
+ if (meta.linkHeader) route.linkHeader = meta.linkHeader
1830
1836
  }
1831
- await buildSubShells(config, route);
1832
- return true;
1837
+ await buildSubShells(config, route)
1838
+ return true
1833
1839
  }
1834
1840
 
1835
1841
  // No partial-prefix sub-shells either: fall through to the normal
1836
1842
  // static/dynamic build (full generateStaticParams sets still prerender;
1837
1843
  // uncovered params render dynamically).
1838
- return false;
1844
+ return false
1839
1845
  }
1840
1846
 
1841
1847
  /**
@@ -1848,20 +1854,20 @@ async function hasPartialStaticPrefix(
1848
1854
  config: Awaited<ReturnType<typeof loadConfig>>,
1849
1855
  route: RouteManifestEntry,
1850
1856
  ): Promise<boolean> {
1851
- if (!route.hasStaticParams) return false;
1852
- let staticParams: Awaited<ReturnType<typeof staticParamsFor>>;
1857
+ if (!route.hasStaticParams) return false
1858
+ let staticParams: Awaited<ReturnType<typeof staticParamsFor>>
1853
1859
  try {
1854
- staticParams = await staticParamsFor(config, route);
1860
+ staticParams = await staticParamsFor(config, route)
1855
1861
  } catch {
1856
- return false;
1862
+ return false
1857
1863
  }
1858
- const paramKeys = [...route.params, ...(route.catchAll ? [route.catchAll] : [])];
1864
+ const paramKeys = [...route.params, ...(route.catchAll ? [route.catchAll] : [])]
1859
1865
  for (const set of staticParams.allSets) {
1860
- let filled = 0;
1861
- while (filled < paramKeys.length && set[paramKeys[filled]!] !== undefined) filled++;
1862
- if (filled > 0 && filled < paramKeys.length) return true;
1866
+ let filled = 0
1867
+ while (filled < paramKeys.length && set[paramKeys[filled]!] !== undefined) filled++
1868
+ if (filled > 0 && filled < paramKeys.length) return true
1863
1869
  }
1864
- return false;
1870
+ return false
1865
1871
  }
1866
1872
 
1867
1873
  /**
@@ -1873,16 +1879,16 @@ async function hasFullStaticParamSet(
1873
1879
  config: Awaited<ReturnType<typeof loadConfig>>,
1874
1880
  route: RouteManifestEntry,
1875
1881
  ): Promise<boolean> {
1876
- if (!route.hasStaticParams) return false;
1877
- let staticParams: Awaited<ReturnType<typeof staticParamsFor>>;
1882
+ if (!route.hasStaticParams) return false
1883
+ let staticParams: Awaited<ReturnType<typeof staticParamsFor>>
1878
1884
  try {
1879
- staticParams = await staticParamsFor(config, route);
1885
+ staticParams = await staticParamsFor(config, route)
1880
1886
  } catch {
1881
- return false;
1887
+ return false
1882
1888
  }
1883
- const paramKeys = [...route.params, ...(route.catchAll ? [route.catchAll] : [])];
1884
- if (paramKeys.length === 0) return false;
1885
- return staticParams.allSets.some(set => paramKeys.every(key => set[key] !== undefined));
1889
+ const paramKeys = [...route.params, ...(route.catchAll ? [route.catchAll] : [])]
1890
+ if (paramKeys.length === 0) return false
1891
+ return staticParams.allSets.some(set => paramKeys.every(key => set[key] !== undefined))
1886
1892
  }
1887
1893
 
1888
1894
  /**
@@ -1895,34 +1901,34 @@ async function buildSubShells(
1895
1901
  config: Awaited<ReturnType<typeof loadConfig>>,
1896
1902
  route: RouteManifestEntry,
1897
1903
  ): Promise<void> {
1898
- let staticParams: Awaited<ReturnType<typeof staticParamsFor>>;
1904
+ let staticParams: Awaited<ReturnType<typeof staticParamsFor>>
1899
1905
  try {
1900
- staticParams = await staticParamsFor(config, route);
1906
+ staticParams = await staticParamsFor(config, route)
1901
1907
  } catch {
1902
- return;
1908
+ return
1903
1909
  }
1904
- route.prerenderedParams = staticParams.allSets;
1905
- const paramKeys = [...route.params, ...(route.catchAll ? [route.catchAll] : [])];
1910
+ route.prerenderedParams = staticParams.allSets
1911
+ const paramKeys = [...route.params, ...(route.catchAll ? [route.catchAll] : [])]
1906
1912
  // Distinct leading prefixes AND full param sets. A partial prefix fills keys
1907
1913
  // [0..k) (k < len) and leaves the rest hanging → a sub-shell (postponed). A
1908
1914
  // FULL set fills every param → a fully-prerendered shell with no holes (not
1909
1915
  // postponed) that the request path serves for that exact URL.
1910
- const prefixes = new Map<string, Record<string, RouteParamValue>>();
1916
+ const prefixes = new Map<string, Record<string, RouteParamValue>>()
1911
1917
  for (const set of staticParams.allSets) {
1912
- let filled = 0;
1913
- while (filled < paramKeys.length && set[paramKeys[filled]!] !== undefined) filled++;
1914
- if (filled === 0) continue;
1915
- const concrete: Record<string, RouteParamValue> = {};
1916
- for (let i = 0; i < filled; i++) concrete[paramKeys[i]!] = set[paramKeys[i]!]!;
1917
- const key = subShellKey(concrete);
1918
- if (!prefixes.has(key)) prefixes.set(key, concrete);
1918
+ let filled = 0
1919
+ while (filled < paramKeys.length && set[paramKeys[filled]!] !== undefined) filled++
1920
+ if (filled === 0) continue
1921
+ const concrete: Record<string, RouteParamValue> = {}
1922
+ for (let i = 0; i < filled; i++) concrete[paramKeys[i]!] = set[paramKeys[i]!]!
1923
+ const key = subShellKey(concrete)
1924
+ if (!prefixes.has(key)) prefixes.set(key, concrete)
1919
1925
  }
1920
- if (prefixes.size === 0) return;
1926
+ if (prefixes.size === 0) return
1921
1927
 
1922
- const subShells: NonNullable<RouteManifestEntry['pprSubShells']> = [];
1928
+ const subShells: NonNullable<RouteManifestEntry['pprSubShells']> = []
1923
1929
  for (const [key, concreteParams] of prefixes) {
1924
- const routePath = fillRoutePath(route.route, concreteParams);
1925
- const url = new URL(`http://pnext.local${routePath}`);
1930
+ const routePath = fillRoutePath(route.route, concreteParams)
1931
+ const url = new URL(`http://pnext.local${routePath}`)
1926
1932
  // Collect cache meta so a sub-shell's cache tags (its concrete-param
1927
1933
  // prefix lets `use cache` scopes fill that the fallback shell postponed)
1928
1934
  // join the route's tag set for PPR-shell tag staleness.
@@ -1934,13 +1940,13 @@ async function buildSubShells(
1934
1940
  ),
1935
1941
  { fetchCache: route.segmentConfig?.fetchCache, route: routePath || '/', prerender: true },
1936
1942
  ),
1937
- );
1938
- recordRouteCacheTags(route, tags);
1939
- if (!prebuilt) continue;
1940
- const file = pprSubShellPath(config.outPath, route.id, key);
1941
- await mkdir(path.dirname(file), { recursive: true });
1942
- await writeText(file, prebuilt.shell);
1943
- subShells.push({ key, concreteParams, holes: prebuilt.holes });
1943
+ )
1944
+ recordRouteCacheTags(route, tags)
1945
+ if (!prebuilt) continue
1946
+ const file = pprSubShellPath(config.outPath, route.id, key)
1947
+ await mkdir(path.dirname(file), { recursive: true })
1948
+ await writeText(file, prebuilt.shell)
1949
+ subShells.push({ key, concreteParams, holes: prebuilt.holes })
1944
1950
  // A FULL param set is a concrete prerendered path - emit Next's `.{html,meta}` plus tree-segment
1945
1951
  // artifacts for it. The sub-shell is the prerendered html (dynamic holes stay excluded, so a
1946
1952
  // dynamic <head> never leaks into the static copy); a blocking probe render supplies the real
@@ -1963,7 +1969,7 @@ async function buildSubShells(
1963
1969
  { route: routePath || '/', prerender: true },
1964
1970
  )
1965
1971
  .then(result => result.value),
1966
- );
1972
+ )
1967
1973
  // Dynamic metadata counts: the head resumes at request time, so the shell is partial even
1968
1974
  // with zero body holes. The sub-shell render may not re-flag metadataDynamic once the
1969
1975
  // fallback shell recorded it, so the route-level flag joins in. http-access fallback recovery
@@ -1973,12 +1979,12 @@ async function buildSubShells(
1973
1979
  prebuilt.holes.length > 0 ||
1974
1980
  prebuilt.metadataDynamic ||
1975
1981
  route.pprMetadata === true ||
1976
- routeSourcesHaveDynamicHead(route);
1982
+ routeSourcesHaveDynamicHead(route)
1977
1983
  await emitConcreteNextPageArtifacts(config, route, routePath, {
1978
1984
  html: postponed ? prebuilt.shell : `${prebuilt.shell}\n </body></html>`,
1979
1985
  status: probe.status,
1980
1986
  postponed,
1981
- });
1987
+ })
1982
1988
  } catch {
1983
1989
  // Probe render failed — the sub-shell itself is unaffected.
1984
1990
  }
@@ -1986,8 +1992,8 @@ async function buildSubShells(
1986
1992
  }
1987
1993
  subShells.sort(
1988
1994
  (a, b) => Object.keys(b.concreteParams).length - Object.keys(a.concreteParams).length,
1989
- );
1990
- if (subShells.length > 0) route.pprSubShells = subShells;
1995
+ )
1996
+ if (subShells.length > 0) route.pprSubShells = subShells
1991
1997
  }
1992
1998
 
1993
1999
  /**
@@ -2000,12 +2006,12 @@ function routeSourcesUseHttpFallback(root: string, route: RouteManifestEntry): b
2000
2006
  // App files only: `sourceFiles` also carries framework entries (pnext's own
2001
2007
  // compat sources name these APIs), which would blocking-probe every route
2002
2008
  // that imports next/server.
2003
- if (!file.startsWith(root) || file.includes(`${path.sep}node_modules${path.sep}`)) continue;
2004
- if (!existsSync(file)) continue;
2005
- const source = readTextSync(file);
2006
- if (/\b(?:notFound|forbidden|unauthorized)\s*\(\s*\)/.test(source)) return true;
2009
+ if (!file.startsWith(root) || file.includes(`${path.sep}node_modules${path.sep}`)) continue
2010
+ if (!existsSync(file)) continue
2011
+ const source = readTextSync(file)
2012
+ if (/\b(?:notFound|forbidden|unauthorized)\s*\(\s*\)/.test(source)) return true
2007
2013
  }
2008
- return false;
2014
+ return false
2009
2015
  }
2010
2016
 
2011
2017
  /**
@@ -2021,17 +2027,17 @@ function routeSourcesUseHttpFallback(root: string, route: RouteManifestEntry): b
2021
2027
  */
2022
2028
  function routeSourcesUseRequestApi(root: string, route: RouteManifestEntry): boolean {
2023
2029
  for (const file of route.sourceFiles) {
2024
- if (!file.startsWith(root) || file.includes(`${path.sep}node_modules${path.sep}`)) continue;
2025
- if (!existsSync(file)) continue;
2030
+ if (!file.startsWith(root) || file.includes(`${path.sep}node_modules${path.sep}`)) continue
2031
+ if (!existsSync(file)) continue
2026
2032
  // Comments are stripped first: fixtures routinely SAY "no connection()" in
2027
2033
  // prose above a page that has none (`//` inside a string only costs a
2028
2034
  // false negative, which is the safe direction here).
2029
2035
  const source = readTextSync(file)
2030
2036
  .replace(/\/\*[\s\S]*?\*\//g, '')
2031
- .replace(/(^|[^:])\/\/.*$/gm, '$1');
2032
- if (/\b(?:connection|cookies|headers|draftMode)\s*\(\s*\)/.test(source)) return true;
2037
+ .replace(/(^|[^:])\/\/.*$/gm, '$1')
2038
+ if (/\b(?:connection|cookies|headers|draftMode)\s*\(\s*\)/.test(source)) return true
2033
2039
  }
2034
- return false;
2040
+ return false
2035
2041
  }
2036
2042
 
2037
2043
  /**
@@ -2041,19 +2047,19 @@ function routeSourcesUseRequestApi(root: string, route: RouteManifestEntry): boo
2041
2047
  */
2042
2048
  function routeSourcesHaveDynamicHead(route: RouteManifestEntry): boolean {
2043
2049
  for (const file of route.sourceFiles) {
2044
- if (!existsSync(file)) continue;
2045
- const source = readTextSync(file);
2050
+ if (!existsSync(file)) continue
2051
+ const source = readTextSync(file)
2046
2052
  const match =
2047
2053
  /export\s+(?:async\s+)?function\s+(?:generateMetadata|generateViewport)\s*\([^)]*\)\s*(?::[^{]+)?\{/.exec(
2048
2054
  source,
2049
- );
2050
- if (!match) continue;
2051
- const body = source.slice(match.index + match[0].length);
2055
+ )
2056
+ if (!match) continue
2057
+ const body = source.slice(match.index + match[0].length)
2052
2058
  if (/\bawait\b/.test(body.split('\nexport ')[0] ?? body) && !body.includes('use cache')) {
2053
- return true;
2059
+ return true
2054
2060
  }
2055
2061
  }
2056
- return false;
2062
+ return false
2057
2063
  }
2058
2064
 
2059
2065
  /**
@@ -2070,38 +2076,38 @@ function filterDebugBuildRoutes(
2070
2076
  .split(',')
2071
2077
  .map(pattern => pattern.trim())
2072
2078
  .filter(pattern => pattern.length > 0 && !pattern.startsWith('!'))
2073
- .map(debugBuildPathMatcher);
2074
- if (matchers.length === 0) return routes;
2079
+ .map(debugBuildPathMatcher)
2080
+ if (matchers.length === 0) return routes
2075
2081
  return routes.filter(route => {
2076
- const relative = toPosixPath(path.relative(root, route.file));
2077
- return matchers.some(matcher => matcher(relative));
2078
- });
2082
+ const relative = toPosixPath(path.relative(root, route.file))
2083
+ return matchers.some(matcher => matcher(relative))
2084
+ })
2079
2085
  }
2080
2086
 
2081
2087
  function debugBuildPathMatcher(pattern: string): (file: string) => boolean {
2082
2088
  if (!/[*?]/.test(pattern)) {
2083
- return file => file === pattern || file.endsWith(`/${pattern}`);
2089
+ return file => file === pattern || file.endsWith(`/${pattern}`)
2084
2090
  }
2085
2091
  const regex = new RegExp(
2086
2092
  `^${pattern
2087
2093
  .split(/(\*\*\/|\*\*|\*|\?)/)
2088
2094
  .map(part => {
2089
- if (part === '**/') return '(?:.*/)?';
2090
- if (part === '**') return '.*';
2091
- if (part === '*') return '[^/]*';
2092
- if (part === '?') return '[^/]';
2093
- return part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2095
+ if (part === '**/') return '(?:.*/)?'
2096
+ if (part === '**') return '.*'
2097
+ if (part === '*') return '[^/]*'
2098
+ if (part === '?') return '[^/]'
2099
+ return part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
2094
2100
  })
2095
2101
  .join('')}$`,
2096
- );
2097
- return file => regex.test(file);
2102
+ )
2103
+ return file => regex.test(file)
2098
2104
  }
2099
2105
 
2100
2106
  /** Stable on-disk signature for a sub-shell's concrete-param prefix. */
2101
2107
  function subShellKey(concrete: Record<string, RouteParamValue>): string {
2102
2108
  return Object.entries(concrete)
2103
2109
  .map(([k, v]) => `${k}-${(Array.isArray(v) ? v.join('_') : v).replace(/[^a-zA-Z0-9_]/g, '_')}`)
2104
- .join('.');
2110
+ .join('.')
2105
2111
  }
2106
2112
 
2107
2113
  /**
@@ -2110,16 +2116,16 @@ function subShellKey(concrete: Record<string, RouteParamValue>): string {
2110
2116
  * (next/server etc.), so start must load this build-time artifact instead.
2111
2117
  */
2112
2118
  async function buildProxyModule(config: Awaited<ReturnType<typeof loadConfig>>) {
2113
- await validateProxyFiles(config);
2114
- const file = findProxyFile(config);
2115
- if (!file) return undefined;
2119
+ await validateProxyFiles(config)
2120
+ const file = findProxyFile(config)
2121
+ if (!file) return undefined
2116
2122
  const href = await devServerModuleHref(config, file, 'prod', {
2117
2123
  conditionTarget: 'edge',
2118
2124
  externalLoadTarget: proxyExternalLoadTarget(file),
2119
2125
  reactServerLayer: true,
2120
- });
2121
- const compiled = fileURLToPath(href);
2122
- return toPosixPath(path.relative(config.outPath, compiled));
2126
+ })
2127
+ const compiled = fileURLToPath(href)
2128
+ return toPosixPath(path.relative(config.outPath, compiled))
2123
2129
  }
2124
2130
 
2125
2131
  /**
@@ -2136,14 +2142,14 @@ async function renderNotFoundDocuments({
2136
2142
  staticMetadataFiles,
2137
2143
  staticModuleMetadata,
2138
2144
  }: {
2139
- config: Awaited<ReturnType<typeof loadConfig>>;
2140
- log: VerboseLogger;
2141
- skip: boolean;
2142
- documentCss: Promise<unknown>;
2143
- staticMetadataFiles: StaticMetadataFile[];
2144
- staticModuleMetadata: Record<string, StaticModuleMetadata>;
2145
+ config: Awaited<ReturnType<typeof loadConfig>>
2146
+ log: VerboseLogger
2147
+ skip: boolean
2148
+ documentCss: Promise<unknown>
2149
+ staticMetadataFiles: StaticMetadataFile[]
2150
+ staticModuleMetadata: Record<string, StaticModuleMetadata>
2145
2151
  }): Promise<string | undefined> {
2146
- if (skip) return undefined;
2152
+ if (skip) return undefined
2147
2153
  const render = async () => {
2148
2154
  const response = await renderGlobalNotFoundResponse({
2149
2155
  config,
@@ -2151,29 +2157,29 @@ async function renderNotFoundDocuments({
2151
2157
  staticMetadataFiles,
2152
2158
  staticModuleMetadata,
2153
2159
  resolveDynamicMetadataRoutes: true,
2154
- });
2155
- return response.text();
2156
- };
2160
+ })
2161
+ return response.text()
2162
+ }
2157
2163
  // The document links the not-found stylesheet chunk buildNotFoundCss emits.
2158
- await documentCss;
2164
+ await documentCss
2159
2165
 
2160
2166
  if (await shouldBuildGlobalNotFound(config)) {
2161
2167
  await log.step('global not-found', async () =>
2162
2168
  writeText(path.join(config.outPath, 'public', '404.html'), await render()),
2163
- );
2169
+ )
2164
2170
  }
2165
2171
  // Next always prerenders the built-in `/_not-found` document for a compat
2166
2172
  // build even without a prerenderable not-found file (not-found/default reads
2167
2173
  // `.next/server/app/_not-found.html`). When public/404.html was written, the
2168
2174
  // .next artifacts are copied from it; otherwise they need a document here.
2169
- if (!config.compat?.next) return undefined;
2170
- if (existsSync(path.join(config.outPath, 'public', '404.html'))) return undefined;
2175
+ if (!config.compat?.next) return undefined
2176
+ if (existsSync(path.join(config.outPath, 'public', '404.html'))) return undefined
2171
2177
  // Nothing prerenderable to render into the document: either the app authored no not-found.* at
2172
2178
  // all, or it authored one and declared it force-dynamic, in which case Next leaves `/_not-found`
2173
2179
  // dynamic and prerenders no custom 404 either. Emit the standalone default rather than boot the
2174
2180
  // whole server graph for an artifact that is never served - the app's *runtime* 404, still
2175
2181
  // dynamic, is what users see.
2176
- return defaultNotFoundDocument();
2182
+ return defaultNotFoundDocument()
2177
2183
  }
2178
2184
 
2179
2185
  /**
@@ -2186,9 +2192,9 @@ async function shouldBuildGlobalNotFound(config: Awaited<ReturnType<typeof loadC
2186
2192
  path.join(config.appPath, `global-not-found.${ext}`),
2187
2193
  path.join(config.appPath, `not-found.${ext}`),
2188
2194
  ])
2189
- .find(candidate => existsSync(candidate));
2190
- if (!file) return false;
2191
- return !/\bexport\s+const\s+dynamic\s*=\s*['"]force-dynamic['"]/.test(await readText(file));
2195
+ .find(candidate => existsSync(candidate))
2196
+ if (!file) return false
2197
+ return !/\bexport\s+const\s+dynamic\s*=\s*['"]force-dynamic['"]/.test(await readText(file))
2192
2198
  }
2193
2199
 
2194
2200
  /**
@@ -2202,99 +2208,99 @@ async function emitProxyServerArtifacts(
2202
2208
  config: Awaited<ReturnType<typeof loadConfig>>,
2203
2209
  proxyModule: string | undefined,
2204
2210
  ) {
2205
- if (!config.compat?.next || !proxyModule) return;
2206
- const compiled = path.resolve(config.outPath, proxyModule);
2207
- if (!existsSync(compiled)) return;
2211
+ if (!config.compat?.next || !proxyModule) return
2212
+ const compiled = path.resolve(config.outPath, proxyModule)
2213
+ if (!existsSync(compiled)) return
2208
2214
 
2209
- const serverDir = path.join(config.root, '.next', 'server');
2210
- await mkdir(serverDir, { recursive: true });
2211
- const middlewareFile = path.join(serverDir, 'middleware.js');
2212
- await copyFile(compiled, middlewareFile);
2215
+ const serverDir = path.join(config.root, '.next', 'server')
2216
+ await mkdir(serverDir, { recursive: true })
2217
+ const middlewareFile = path.join(serverDir, 'middleware.js')
2218
+ await copyFile(compiled, middlewareFile)
2213
2219
 
2214
2220
  // NFT files are relative to the trace file's directory and must all exist on
2215
2221
  // disk. The self-contained bundle is the only traced output.
2216
- const files = ['middleware.js'].filter(name => existsSync(path.join(serverDir, name)));
2222
+ const files = ['middleware.js'].filter(name => existsSync(path.join(serverDir, name)))
2217
2223
  await writeFile(
2218
2224
  path.join(serverDir, 'middleware.js.nft.json'),
2219
2225
  `${JSON.stringify({ version: 1, files })}\n`,
2220
- );
2226
+ )
2221
2227
  }
2222
2228
 
2223
2229
  async function emitNextNotFoundArtifacts(
2224
2230
  config: Awaited<ReturnType<typeof loadConfig>>,
2225
2231
  fallback404?: string,
2226
2232
  ) {
2227
- if (!config.compat?.next) return;
2228
- const source = path.join(config.outPath, 'public', '404.html');
2229
- const html = existsSync(source) ? readFileSync(source, 'utf8') : fallback404;
2230
- if (html === undefined) return;
2231
-
2232
- const serverDir = path.join(config.root, '.next', 'server');
2233
- const pagesDir = path.join(serverDir, 'pages');
2234
- const notFoundDir = path.join(serverDir, 'app', '_not-found');
2235
- await mkdir(pagesDir, { recursive: true });
2236
- await mkdir(notFoundDir, { recursive: true });
2237
- await writeFile(path.join(pagesDir, '404.html'), html);
2233
+ if (!config.compat?.next) return
2234
+ const source = path.join(config.outPath, 'public', '404.html')
2235
+ const html = existsSync(source) ? readFileSync(source, 'utf8') : fallback404
2236
+ if (html === undefined) return
2237
+
2238
+ const serverDir = path.join(config.root, '.next', 'server')
2239
+ const pagesDir = path.join(serverDir, 'pages')
2240
+ const notFoundDir = path.join(serverDir, 'app', '_not-found')
2241
+ await mkdir(pagesDir, { recursive: true })
2242
+ await mkdir(notFoundDir, { recursive: true })
2243
+ await writeFile(path.join(pagesDir, '404.html'), html)
2238
2244
  // Next's app-router prerender output for the built-in /_not-found route. The
2239
2245
  // .rsc mirror carries the document body like the segment artifacts do
2240
2246
  // (not-found/default greps both for the noindex marker).
2241
- await writeFile(path.join(serverDir, 'app', '_not-found.html'), html);
2242
- await writeFile(path.join(serverDir, 'app', '_not-found.rsc'), html);
2247
+ await writeFile(path.join(serverDir, 'app', '_not-found.html'), html)
2248
+ await writeFile(path.join(serverDir, 'app', '_not-found.rsc'), html)
2243
2249
 
2244
- const pagesManifestFile = path.join(serverDir, 'pages-manifest.json');
2245
- let pagesManifest: Record<string, string> = {};
2250
+ const pagesManifestFile = path.join(serverDir, 'pages-manifest.json')
2251
+ let pagesManifest: Record<string, string> = {}
2246
2252
  try {
2247
- pagesManifest = JSON.parse(readFileSync(pagesManifestFile, 'utf8')) as Record<string, string>;
2253
+ pagesManifest = JSON.parse(readFileSync(pagesManifestFile, 'utf8')) as Record<string, string>
2248
2254
  } catch {
2249
2255
  // First compat build has no pages manifest yet.
2250
2256
  }
2251
- pagesManifest['/404'] = 'pages/404.html';
2252
- await writeFile(pagesManifestFile, `${JSON.stringify(pagesManifest, null, 2)}\n`);
2257
+ pagesManifest['/404'] = 'pages/404.html'
2258
+ await writeFile(pagesManifestFile, `${JSON.stringify(pagesManifest, null, 2)}\n`)
2253
2259
 
2254
- const clientReferenceManifest = 'page_client-reference-manifest.js';
2255
- await writeFile(path.join(notFoundDir, clientReferenceManifest), 'self.__RSC_MANIFEST={}\n');
2260
+ const clientReferenceManifest = 'page_client-reference-manifest.js'
2261
+ await writeFile(path.join(notFoundDir, clientReferenceManifest), 'self.__RSC_MANIFEST={}\n')
2256
2262
  await writeFile(
2257
2263
  path.join(notFoundDir, 'page.js.nft.json'),
2258
2264
  `${JSON.stringify({ version: 1, files: [clientReferenceManifest] }, null, 2)}\n`,
2259
- );
2265
+ )
2260
2266
  }
2261
2267
 
2262
2268
  async function collectStaticModuleMetadata(
2263
2269
  config: Awaited<ReturnType<typeof loadConfig>>,
2264
2270
  routes: RouteManifestEntry[],
2265
2271
  ) {
2266
- const metadata: Record<string, StaticModuleMetadata> = {};
2267
- if (config.compat?.next) return metadata;
2272
+ const metadata: Record<string, StaticModuleMetadata> = {}
2273
+ if (config.compat?.next) return metadata
2268
2274
 
2269
- const files = new Set<string>();
2275
+ const files = new Set<string>()
2270
2276
  for (const route of routes) {
2271
- if (route.kind !== 'page') continue;
2277
+ if (route.kind !== 'page') continue
2272
2278
  for (const file of findLayouts(config.appPath, route.file)) {
2273
- if (existsSync(file)) files.add(file);
2279
+ if (existsSync(file)) files.add(file)
2274
2280
  }
2275
- files.add(route.file);
2281
+ files.add(route.file)
2276
2282
  }
2277
- if (files.size === 0) return metadata;
2283
+ if (files.size === 0) return metadata
2278
2284
 
2279
- registerServerRuntime(config, [...files]);
2280
- registerCssRuntime();
2285
+ registerServerRuntime(config, [...files])
2286
+ registerCssRuntime()
2281
2287
  for (const file of files) {
2282
2288
  const href = reactCompatEnabled(config)
2283
2289
  ? await devServerModuleHref(config, file, 'build')
2284
- : pathToFileHref(file);
2290
+ : pathToFileHref(file)
2285
2291
  const module = (await import(href)) as {
2286
- metadata?: Parameters<typeof readModuleMetadata>[0]['metadata'];
2287
- viewport?: Parameters<typeof readModuleViewport>[0]['viewport'];
2288
- };
2289
- const entry: StaticModuleMetadata = {};
2290
- const routeMetadata = await readModuleMetadata(module);
2291
- const viewport = await readModuleViewport(module);
2292
- if (routeMetadata) entry.metadata = routeMetadata;
2293
- if (viewport) entry.viewport = viewport;
2294
- if (entry.metadata || entry.viewport) metadata[file] = entry;
2292
+ metadata?: Parameters<typeof readModuleMetadata>[0]['metadata']
2293
+ viewport?: Parameters<typeof readModuleViewport>[0]['viewport']
2294
+ }
2295
+ const entry: StaticModuleMetadata = {}
2296
+ const routeMetadata = await readModuleMetadata(module)
2297
+ const viewport = await readModuleViewport(module)
2298
+ if (routeMetadata) entry.metadata = routeMetadata
2299
+ if (viewport) entry.viewport = viewport
2300
+ if (entry.metadata || entry.viewport) metadata[file] = entry
2295
2301
  }
2296
2302
 
2297
- return metadata;
2303
+ return metadata
2298
2304
  }
2299
2305
 
2300
2306
  async function collectStaticRouteMetadata(
@@ -2302,14 +2308,14 @@ async function collectStaticRouteMetadata(
2302
2308
  routes: RouteManifestEntry[],
2303
2309
  staticMetadataFiles: StaticMetadataFile[],
2304
2310
  ) {
2305
- const metadata: Record<string, StaticRouteMetadata> = {};
2306
- if (config.compat?.next) return metadata;
2311
+ const metadata: Record<string, StaticRouteMetadata> = {}
2312
+ if (config.compat?.next) return metadata
2307
2313
 
2308
2314
  for (const route of routes) {
2309
- if (route.kind !== 'page' || route.interception) continue;
2310
- const paramSets = route.prerenderedParams ?? (route.params.length === 0 ? [{}] : []);
2315
+ if (route.kind !== 'page' || route.interception) continue
2316
+ const paramSets = route.prerenderedParams ?? (route.params.length === 0 ? [{}] : [])
2311
2317
  for (const params of paramSets) {
2312
- const routePath = fillRoutePath(route.route, params);
2318
+ const routePath = fillRoutePath(route.route, params)
2313
2319
  const base = route.interception
2314
2320
  ? staticMetadataForRouteFromFiles(
2315
2321
  staticMetadataFiles,
@@ -2317,19 +2323,19 @@ async function collectStaticRouteMetadata(
2317
2323
  route.file,
2318
2324
  routePath,
2319
2325
  )
2320
- : staticMetadataForPathFromFiles(staticMetadataFiles, routePath);
2326
+ : staticMetadataForPathFromFiles(staticMetadataFiles, routePath)
2321
2327
  const resolved = await withDynamicMetadataRoutes(
2322
2328
  base,
2323
2329
  config.appPath,
2324
2330
  route.file,
2325
2331
  routePath,
2326
2332
  file => importMetadataModule(config, file),
2327
- );
2328
- metadata[staticRouteMetadataKey(routePath)] = resolved;
2333
+ )
2334
+ metadata[staticRouteMetadataKey(routePath)] = resolved
2329
2335
  }
2330
2336
  }
2331
2337
 
2332
- return metadata;
2338
+ return metadata
2333
2339
  }
2334
2340
 
2335
2341
  async function importMetadataModule(
@@ -2338,8 +2344,8 @@ async function importMetadataModule(
2338
2344
  ): Promise<Record<string, unknown>> {
2339
2345
  const href = reactCompatEnabled(config)
2340
2346
  ? await devServerModuleHref(config, file, 'build')
2341
- : pathToFileHref(file);
2342
- return import(href) as Promise<Record<string, unknown>>;
2347
+ : pathToFileHref(file)
2348
+ return import(href) as Promise<Record<string, unknown>>
2343
2349
  }
2344
2350
 
2345
2351
  async function buildStaticRouteHandler(
@@ -2347,8 +2353,8 @@ async function buildStaticRouteHandler(
2347
2353
  route: RouteManifestEntry,
2348
2354
  options: { skipPhysicalWrite?: boolean } = {},
2349
2355
  ) {
2350
- const skipPhysicalWrite = options.skipPhysicalWrite ?? false;
2351
- registerServerRuntime(config, route.sourceFiles);
2356
+ const skipPhysicalWrite = options.skipPhysicalWrite ?? false
2357
+ registerServerRuntime(config, route.sourceFiles)
2352
2358
  // Compat mode loads the compiled module (aliases baked in) so bare next/*
2353
2359
  // imports (next/headers etc.) resolve to the compat layer, not an installed
2354
2360
  // next package. Same pattern as page prerenders.
@@ -2356,16 +2362,16 @@ async function buildStaticRouteHandler(
2356
2362
  ? await devServerModuleHref(config, route.file, 'build', {
2357
2363
  conditionTarget: serverBundleTargetForRuntime(route.segmentConfig?.runtime),
2358
2364
  })
2359
- : pathToFileHref(route.file);
2360
- const imported = (await import(moduleHref)) as Parameters<typeof metadataRouteHandlerModule>[0];
2361
- const routeModule = metadataRouteHandlerModule(imported, route) ?? imported;
2365
+ : pathToFileHref(route.file)
2366
+ const imported = (await import(moduleHref)) as Parameters<typeof metadataRouteHandlerModule>[0]
2367
+ const routeModule = metadataRouteHandlerModule(imported, route) ?? imported
2362
2368
  const module = (
2363
2369
  nextCompatEnabled(config)
2364
2370
  ? buildCompat().normalizeStaticParamsModule(routeModule as Record<string, unknown>)
2365
2371
  : routeModule
2366
- ) as RouteHandlerModule;
2367
- const paramSets = await staticRouteParams(route, module);
2368
- const staticFiles: Record<string, StaticFileMetadata> = {};
2372
+ ) as RouteHandlerModule
2373
+ const paramSets = await staticRouteParams(route, module)
2374
+ const staticFiles: Record<string, StaticFileMetadata> = {}
2369
2375
 
2370
2376
  // A generated-param metadata route (`generateSitemaps` / `generateImageMetadata`)
2371
2377
  // knows every id ahead of time, so each expanded pathname is a prerendered
@@ -2375,26 +2381,24 @@ async function buildStaticRouteHandler(
2375
2381
  if (route.metadataRoute?.generatedParam && paramSets.length > 0) {
2376
2382
  route.metadataRoute.generatedRoutes = paramSets.map(params =>
2377
2383
  fillRoutePath(route.route, params, route.metadataRoute?.generatedParam),
2378
- );
2384
+ )
2379
2385
  }
2380
2386
 
2381
2387
  // force-static handlers see a canonicalized, origin-normalized request (no
2382
2388
  // search/headers/cookies); Next reports the URL against a fixed base host.
2383
2389
  const requestBase =
2384
- route.segmentConfig?.dynamic === 'force-static'
2385
- ? 'http://localhost:3000'
2386
- : 'http://pnext.local';
2390
+ route.segmentConfig?.dynamic === 'force-static' ? 'http://localhost:3000' : 'http://pnext.local'
2387
2391
  for (const params of paramSets) {
2388
- const routePath = fillRoutePath(route.route, params, route.metadataRoute?.generatedParam);
2389
- const file = staticRouteHandlerPath(config.outPath, routePath);
2392
+ const routePath = fillRoutePath(route.route, params, route.metadataRoute?.generatedParam)
2393
+ const file = staticRouteHandlerPath(config.outPath, routePath)
2390
2394
  // A descendant handler intentionally forgoes the physical file (its ancestor
2391
2395
  // owns public/<parent>), so don't treat the guaranteed collision as a skip —
2392
2396
  // still render it to capture the prerender-manifest metadata below.
2393
2397
  if (!skipPhysicalWrite) {
2394
- const collision = staticOutputCollision(config.outPath, file);
2398
+ const collision = staticOutputCollision(config.outPath, file)
2395
2399
  if (collision) {
2396
- warnSkippedStatic(routePath, collision);
2397
- continue;
2400
+ warnSkippedStatic(routePath, collision)
2401
+ continue
2398
2402
  }
2399
2403
  }
2400
2404
  // Wrap in a work unit (like the page prerender path) so an after()
@@ -2421,123 +2425,124 @@ async function buildStaticRouteHandler(
2421
2425
  dynamicError: route.segmentConfig?.dynamic === 'error',
2422
2426
  },
2423
2427
  ),
2424
- );
2428
+ )
2425
2429
  // Cache-components only prerenders handlers that settle synchronously or
2426
2430
  // in a microtask. A later task (including a delayed response stream)
2427
2431
  // depends on runtime work and must not produce a static file.
2428
- const early = await settleBeforeNextTask(prerender);
2429
- const completed = early ?? (await prerender);
2432
+ const early = await settleBeforeNextTask(prerender)
2433
+ const completed = early ?? (await prerender)
2430
2434
  const rendered =
2431
2435
  early ??
2432
- (route.segmentConfig?.dynamic === 'force-static' ||
2433
- (await handlerHasEntirelyCachedIo(route))
2436
+ (route.segmentConfig?.dynamic === 'force-static' || (await handlerHasEntirelyCachedIo(route))
2434
2437
  ? completed
2435
- : undefined);
2436
- if (!rendered) continue;
2437
- const response = rendered.value;
2438
+ : undefined)
2439
+ if (!rendered) continue
2440
+ const response = rendered.value
2438
2441
  // A 5xx means the handler threw during prerender (handleRouteModule funnels
2439
2442
  // uncaught throws to an empty 500). Don't bake a broken response into static
2440
2443
  // output — surface it to the caller's skip-and-warn path so the route serves
2441
2444
  // dynamically instead.
2442
2445
  if (response.status >= 500) {
2443
- throw new Error(`route handler prerender returned ${response.status}`);
2446
+ throw new Error(`route handler prerender returned ${response.status}`)
2444
2447
  }
2445
- if (rendered.noStore && !staticRouteHandlerExplicitlyCached(route)) continue;
2446
- const body = await settleBeforeNextTask(response.clone().arrayBuffer());
2447
- if (!body) continue;
2448
- const relative = toPosixPath(path.relative(path.join(config.outPath, 'public'), file));
2448
+ if (rendered.noStore && !staticRouteHandlerExplicitlyCached(route)) continue
2449
+ const body = await settleBeforeNextTask(response.clone().arrayBuffer())
2450
+ if (!body) continue
2451
+ const relative = toPosixPath(path.relative(path.join(config.outPath, 'public'), file))
2449
2452
  // Descendant handlers record their manifest entry (so the synthesized
2450
2453
  // prerender-manifest lists them) but skip the on-disk copy that would
2451
2454
  // collide with the ancestor's file; they serve dynamically at runtime.
2452
2455
  if (!skipPhysicalWrite) {
2453
- await mkdir(path.dirname(file), { recursive: true });
2454
- await writeFile(file, new Uint8Array(body));
2456
+ await mkdir(path.dirname(file), { recursive: true })
2457
+ await writeFile(file, new Uint8Array(body))
2455
2458
  }
2456
2459
  const revalidateSeconds = combineRevalidate(
2457
2460
  route.segmentConfig?.revalidate,
2458
2461
  rendered.revalidateSeconds,
2459
- );
2462
+ )
2460
2463
  staticFiles[relative] = {
2461
2464
  status: response.status,
2462
2465
  headers: [...response.headers.entries()],
2463
2466
  routeId: route.id,
2464
2467
  ...(revalidateSeconds !== undefined ? { revalidateSeconds } : {}),
2465
2468
  ...(rendered.tags.length > 0 ? { tags: rendered.tags } : {}),
2466
- };
2469
+ }
2467
2470
  }
2468
2471
 
2469
- return staticFiles;
2472
+ return staticFiles
2470
2473
  }
2471
2474
 
2472
2475
  function settleBeforeNextTask<T>(value: Promise<T>): Promise<T | undefined> {
2473
2476
  return new Promise((resolve, reject) => {
2474
- let settled = false;
2477
+ let settled = false
2475
2478
  const timer = setTimeout(() => {
2476
- if (!settled) resolve(undefined);
2477
- }, 0);
2479
+ if (!settled) resolve(undefined)
2480
+ }, 0)
2478
2481
  void value.then(
2479
2482
  result => {
2480
- settled = true;
2481
- clearTimeout(timer);
2482
- resolve(result);
2483
+ settled = true
2484
+ clearTimeout(timer)
2485
+ resolve(result)
2483
2486
  },
2484
2487
  (error: unknown) => {
2485
- settled = true;
2486
- clearTimeout(timer);
2487
- reject(error instanceof Error ? error : new Error(String(error)));
2488
+ settled = true
2489
+ clearTimeout(timer)
2490
+ reject(error instanceof Error ? error : new Error(String(error)))
2488
2491
  },
2489
- );
2490
- });
2492
+ )
2493
+ })
2491
2494
  }
2492
2495
 
2493
2496
  async function handlerHasEntirelyCachedIo(route: RouteManifestEntry): Promise<boolean> {
2494
2497
  // Synthetic slot routes carry a phantom `page.tsx` anchor that never exists
2495
2498
  // on disk — source sniffing must skip them or the build ENOENTs.
2496
- if (route.synthetic || !existsSync(route.file)) return false;
2497
- const source = await readText(route.file);
2499
+ if (route.synthetic || !existsSync(route.file)) return false
2500
+ const source = await readText(route.file)
2498
2501
  // A metadata route handler exports a `default` function rather than `GET`; pnext wraps it into the
2499
2502
  // handler internally. When that default is a top-level `use cache` function every read inside it is
2500
2503
  // cached, so the whole route is prerenderable. Without this the GET sniff below returns false and
2501
2504
  // the route stays runtime-only.
2502
- if (route.metadataRoute && metadataDefaultUsesCache(source)) return true;
2503
- const getStart = source.search(/\bexport\s+(?:async\s+)?function\s+GET\s*\(/);
2504
- if (getStart === -1) return false;
2505
- const afterGet = source.slice(getStart);
2506
- const nextDeclaration = afterGet.slice(1).search(/^(?:async\s+)?(?:function|const|let)\s+/m);
2507
- const body = nextDeclaration === -1 ? afterGet : afterGet.slice(0, nextDeclaration + 1);
2505
+ if (route.metadataRoute && metadataDefaultUsesCache(source)) return true
2506
+ const getStart = source.search(/\bexport\s+(?:async\s+)?function\s+GET\s*\(/)
2507
+ if (getStart === -1) return false
2508
+ const afterGet = source.slice(getStart)
2509
+ const nextDeclaration = afterGet.slice(1).search(/^(?:async\s+)?(?:function|const|let)\s+/m)
2510
+ const body = nextDeclaration === -1 ? afterGet : afterGet.slice(0, nextDeclaration + 1)
2508
2511
  // Reading the wall clock straight in the GET body - outside any `use cache` scope - is dynamic
2509
2512
  // under cacheComponents: Next taints such a route as dynamic and never prerenders it. Prebuilding
2510
2513
  // it would serve a build-time body that is already stale by the first request, and the background
2511
2514
  // regen would race a subsequent read. Empty-parens only: `new Date(ms)` is deterministic.
2512
2515
  if (/\bnew\s+Date\s*\(\s*\)/.test(body) || /\bDate\s*\.\s*now\s*\(/.test(body)) {
2513
- return false;
2516
+ return false
2514
2517
  }
2515
- const awaited = [...body.matchAll(/\bawait\s+([A-Za-z_$][\w$]*)\s*\(/g)].map(
2516
- match => match[1]!,
2517
- );
2518
- if (awaited.length === 0) return false;
2518
+ const awaited = [...body.matchAll(/\bawait\s+([A-Za-z_$][\w$]*)\s*\(/g)].map(match => match[1]!)
2519
+ if (awaited.length === 0) return false
2519
2520
 
2520
2521
  // `await import(...)` is module evaluation, not request IO: a module that top-level-awaits still
2521
2522
  // resolves the same for every request, so the route stays prerenderable. It only settles in a later
2522
2523
  // task the FIRST time, which is exactly the build's prerender, so the awaited-call check forgives it.
2523
- const cached = new Set<string>(['import']);
2524
- for (const match of source.matchAll(/\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
2525
- if (match[2] === 'cache' || match[2] === 'unstable_cache') cached.add(match[1]!);
2524
+ const cached = new Set<string>(['import'])
2525
+ for (const match of source.matchAll(
2526
+ /\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*\(/g,
2527
+ )) {
2528
+ if (match[2] === 'cache' || match[2] === 'unstable_cache') cached.add(match[1]!)
2526
2529
  }
2527
2530
  for (const match of source.matchAll(
2528
2531
  /\b(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*\{\s*['"]use cache['"]/g,
2529
2532
  )) {
2530
- cached.add(match[1]!);
2533
+ cached.add(match[1]!)
2531
2534
  }
2532
2535
  for (const name of awaited) {
2533
- const start = source.search(new RegExp(`\\b(?:const\\s+${name}\\s*=|function\\s+${name}\\s*\\()`));
2534
- if (start === -1) continue;
2535
- const tail = source.slice(start);
2536
- const next = tail.slice(1).search(/^(?:async\s+)?(?:function|const|let)\s+/m);
2537
- const declaration = next === -1 ? tail : tail.slice(0, next + 1);
2538
- if (/\bcache\s*:\s*['"]force-cache['"]/.test(declaration)) cached.add(name);
2536
+ const start = source.search(
2537
+ new RegExp(`\\b(?:const\\s+${name}\\s*=|function\\s+${name}\\s*\\()`),
2538
+ )
2539
+ if (start === -1) continue
2540
+ const tail = source.slice(start)
2541
+ const next = tail.slice(1).search(/^(?:async\s+)?(?:function|const|let)\s+/m)
2542
+ const declaration = next === -1 ? tail : tail.slice(0, next + 1)
2543
+ if (/\bcache\s*:\s*['"]force-cache['"]/.test(declaration)) cached.add(name)
2539
2544
  }
2540
- return awaited.every(name => cached.has(name));
2545
+ return awaited.every(name => cached.has(name))
2541
2546
  }
2542
2547
 
2543
2548
  /**
@@ -2548,54 +2553,51 @@ async function handlerHasEntirelyCachedIo(route: RouteManifestEntry): Promise<bo
2548
2553
  function metadataDefaultUsesCache(source: string): boolean {
2549
2554
  return /\bexport\s+default\s+(?:async\s+)?function\b[^(]*\([^)]*\)\s*(?::[^{]*)?\{\s*(?:\/\/[^\n]*\n\s*|\/\*[\s\S]*?\*\/\s*)*['"]use cache['"]/.test(
2550
2555
  source,
2551
- );
2556
+ )
2552
2557
  }
2553
2558
 
2554
2559
  async function usesUnstableCache(route: RouteManifestEntry): Promise<boolean> {
2555
- if (route.synthetic || !existsSync(route.file)) return false;
2556
- const source = await readText(route.file);
2557
- const localNames: string[] = [];
2558
- const dynamicNames: string[] = [];
2560
+ if (route.synthetic || !existsSync(route.file)) return false
2561
+ const source = await readText(route.file)
2562
+ const localNames: string[] = []
2563
+ const dynamicNames: string[] = []
2559
2564
  for (const match of source.matchAll(
2560
2565
  /\bimport\s*\{([^}]*)\}\s*from\s*['"]next\/(cache|headers|server)(?:\.js)?['"]/g,
2561
2566
  )) {
2562
2567
  for (const item of match[1]!.split(',')) {
2563
- const [imported, local] = item.trim().split(/\s+as\s+/);
2568
+ const [imported, local] = item.trim().split(/\s+as\s+/)
2564
2569
  if (match[2] === 'cache' && imported === 'unstable_cache') {
2565
- localNames.push(local ?? imported);
2570
+ localNames.push(local ?? imported)
2566
2571
  } else if (
2567
2572
  (match[2] === 'headers' && (imported === 'cookies' || imported === 'headers')) ||
2568
2573
  (match[2] === 'server' && imported === 'connection')
2569
2574
  ) {
2570
- dynamicNames.push(local ?? imported);
2575
+ dynamicNames.push(local ?? imported)
2571
2576
  }
2572
2577
  }
2573
2578
  }
2574
- if (dynamicNames.length === 0) return false;
2575
- const dynamicCall = dynamicNames.join('|');
2579
+ if (dynamicNames.length === 0) return false
2580
+ const dynamicCall = dynamicNames.join('|')
2576
2581
  return localNames.some(name =>
2577
2582
  new RegExp(
2578
2583
  `\\b${name}\\s*\\(\\s*(?:async\\s*)?(?:\\([^)]*\\)|[A-Za-z_$][\\w$]*)?\\s*=>[\\s\\S]{0,1000}?\\b(?:${dynamicCall})\\s*\\(`,
2579
2584
  ).test(source),
2580
- );
2585
+ )
2581
2586
  }
2582
2587
 
2583
2588
  async function staticHandlerDynamicUsage(route: RouteManifestEntry): Promise<string | undefined> {
2584
- if (!route.usesRequest) return undefined;
2585
- if (
2586
- route.segmentConfig?.dynamic === 'force-static' ||
2587
- route.segmentConfig?.dynamic === 'error'
2588
- ) {
2589
- return undefined;
2589
+ if (!route.usesRequest) return undefined
2590
+ if (route.segmentConfig?.dynamic === 'force-static' || route.segmentConfig?.dynamic === 'error') {
2591
+ return undefined
2590
2592
  }
2591
- if (route.synthetic || !existsSync(route.file)) return undefined;
2592
- const source = await readText(route.file);
2593
- if (/\b(?:request|req)\.url\b/.test(source)) return 'request.url';
2594
- if (/\bnextUrl\s*\.\s*toString\s*\(/.test(source)) return 'nextUrl.toString';
2595
- if (/\bheaders\s*\(/.test(source)) return 'headers()';
2596
- if (/\bcookies\s*\(/.test(source)) return 'cookies()';
2597
- if (/\bconnection\s*\(/.test(source)) return 'connection()';
2598
- return undefined;
2593
+ if (route.synthetic || !existsSync(route.file)) return undefined
2594
+ const source = await readText(route.file)
2595
+ if (/\b(?:request|req)\.url\b/.test(source)) return 'request.url'
2596
+ if (/\bnextUrl\s*\.\s*toString\s*\(/.test(source)) return 'nextUrl.toString'
2597
+ if (/\bheaders\s*\(/.test(source)) return 'headers()'
2598
+ if (/\bcookies\s*\(/.test(source)) return 'cookies()'
2599
+ if (/\bconnection\s*\(/.test(source)) return 'connection()'
2600
+ return undefined
2599
2601
  }
2600
2602
 
2601
2603
  /**
@@ -2605,55 +2607,55 @@ async function staticHandlerDynamicUsage(route: RouteManifestEntry): Promise<str
2605
2607
  * cleanly. The ancestor keeps its physical file; descendants skip theirs.
2606
2608
  */
2607
2609
  function collectDescendantHandlerIds(routes: RouteManifestEntry[]): Set<string> {
2608
- const ids = new Set<string>();
2609
- const paths = routes.map(route => ({ route, norm: (route.route || '/').replace(/\/+$/, '') }));
2610
+ const ids = new Set<string>()
2611
+ const paths = routes.map(route => ({ route, norm: (route.route || '/').replace(/\/+$/, '') }))
2610
2612
  for (const { route, norm } of paths) {
2611
- if (route.kind !== 'handler') continue;
2613
+ if (route.kind !== 'handler') continue
2612
2614
  const hasAncestor = paths.some(
2613
2615
  other =>
2614
2616
  other.route !== route &&
2615
2617
  other.norm.length > 0 &&
2616
2618
  norm !== other.norm &&
2617
2619
  norm.startsWith(`${other.norm}/`),
2618
- );
2619
- if (hasAncestor) ids.add(route.id);
2620
+ )
2621
+ if (hasAncestor) ids.add(route.id)
2620
2622
  }
2621
- return ids;
2623
+ return ids
2622
2624
  }
2623
2625
 
2624
2626
  async function staticRouteHandlerCandidate(route: RouteManifestEntry) {
2625
- const config = route.segmentConfig;
2626
- if (config?.dynamic === 'force-dynamic') return false;
2627
- if (config?.revalidate === 0) return false;
2628
- if (config?.fetchCache === 'force-no-store') return false;
2627
+ const config = route.segmentConfig
2628
+ if (config?.dynamic === 'force-dynamic') return false
2629
+ if (config?.revalidate === 0) return false
2630
+ if (config?.fetchCache === 'force-no-store') return false
2629
2631
  // A handler that calls revalidatePath/revalidateTag performs a side effect
2630
2632
  // per request; it can never be prerendered (Next keeps it dynamic even with a
2631
2633
  // `revalidate` export), so serving a cached body would drop the revalidation.
2632
- if (route.handlerUsesRevalidationApi) return false;
2633
- if (route.metadataRoute) return !route.usesRequest;
2634
+ if (route.handlerUsesRevalidationApi) return false
2635
+ if (route.metadataRoute) return !route.usesRequest
2634
2636
  // `dynamic = 'error'` deliberately exercises a static render so request API
2635
2637
  // reads can fail at their call sites instead of turning the handler dynamic.
2636
- if (config?.dynamic === 'error') return true;
2637
- if (config?.dynamic === 'force-static') return true;
2638
+ if (config?.dynamic === 'error') return true
2639
+ if (config?.dynamic === 'force-static') return true
2638
2640
  // A `revalidate` export or `generateStaticParams` opts a handler into static
2639
2641
  // (ISR) output even if it reads request data — Next prerenders it with a
2640
2642
  // canonical request. Truly dynamic access (cookies/headers) would surface at
2641
2643
  // runtime, but reading e.g. `req.nextUrl.pathname` is prerender-safe.
2642
- if (staticRouteHandlerExplicitlyCached(route) || route.hasStaticParams) return true;
2643
- if (route.usesRequest) return false;
2644
+ if (staticRouteHandlerExplicitlyCached(route) || route.hasStaticParams) return true
2645
+ if (route.usesRequest) return false
2644
2646
  // Since Next 15, GET route handlers are dynamic by default: only an explicit opt-in prerenders one.
2645
2647
  // Under cacheComponents every request-independent handler is a prerender candidate again, EXCEPT
2646
2648
  // one that performs uncached IO (time/randomness outside any `use cache` scope): baking it would
2647
2649
  // freeze `new Date()` at build time while its `use cache` parts must keep live SWR semantics.
2648
- if (!cacheComponents() || route.mode !== 'static') return false;
2649
- if (!handlerUsesUncachedIo(route)) return true;
2650
+ if (!cacheComponents() || route.mode !== 'static') return false
2651
+ if (!handlerUsesUncachedIo(route)) return true
2650
2652
  // The uncached-IO sniff is textual, so it also trips on time/randomness that
2651
2653
  // sits inside a helper the handler only ever reaches through an
2652
2654
  // `unstable_cache()` wrapper (cache-components routes `/routes/io-cached`).
2653
2655
  // The call-graph check is authoritative there: when EVERY awaited call in GET
2654
2656
  // resolves to a cached wrapper the IO is cached after all. A handler that
2655
2657
  // also awaits the raw helper (`/routes/io-mixed`) still fails it.
2656
- return handlerHasEntirelyCachedIo(route);
2658
+ return handlerHasEntirelyCachedIo(route)
2657
2659
  }
2658
2660
 
2659
2661
  /**
@@ -2663,83 +2665,81 @@ async function staticRouteHandlerCandidate(route: RouteManifestEntry) {
2663
2665
  * cacheComponents, so it must render per-request.
2664
2666
  */
2665
2667
  function handlerUsesUncachedIo(route: RouteManifestEntry): boolean {
2666
- const source = readTextSync(route.file);
2667
- if (!source) return false;
2668
- return /\bnew Date\s*\(|\bDate\.now\s*\(|\bMath\.random\s*\(/.test(
2669
- stripUseCacheBodies(source),
2670
- );
2668
+ const source = readTextSync(route.file)
2669
+ if (!source) return false
2670
+ return /\bnew Date\s*\(|\bDate\.now\s*\(|\bMath\.random\s*\(/.test(stripUseCacheBodies(source))
2671
2671
  }
2672
2672
 
2673
2673
  /** Blank out every function body whose prologue opens with a 'use cache' directive. */
2674
2674
  function stripUseCacheBodies(source: string): string {
2675
- let out = source;
2675
+ let out = source
2676
2676
  for (;;) {
2677
- const directive = /\{\s*(['"])use cache(?:\s*:\s*[\w-]+)?\1\s*;?/.exec(out);
2678
- if (!directive) break;
2679
- const bodyOpen = directive.index + 1;
2680
- let balance = 1;
2681
- let end = out.length;
2677
+ const directive = /\{\s*(['"])use cache(?:\s*:\s*[\w-]+)?\1\s*;?/.exec(out)
2678
+ if (!directive) break
2679
+ const bodyOpen = directive.index + 1
2680
+ let balance = 1
2681
+ let end = out.length
2682
2682
  for (let i = bodyOpen; i < out.length; i += 1) {
2683
- const ch = out[i];
2684
- if (ch === '{') balance += 1;
2683
+ const ch = out[i]
2684
+ if (ch === '{') balance += 1
2685
2685
  else if (ch === '}') {
2686
- balance -= 1;
2686
+ balance -= 1
2687
2687
  if (balance === 0) {
2688
- end = i;
2689
- break;
2688
+ end = i
2689
+ break
2690
2690
  }
2691
2691
  }
2692
2692
  }
2693
- out = `${out.slice(0, bodyOpen)}${out.slice(end)}`;
2693
+ out = `${out.slice(0, bodyOpen)}${out.slice(end)}`
2694
2694
  }
2695
- return out;
2695
+ return out
2696
2696
  }
2697
2697
 
2698
2698
  function staticRouteHandlerExplicitlyCached(route: RouteManifestEntry) {
2699
- const config = route.segmentConfig;
2699
+ const config = route.segmentConfig
2700
2700
  return (
2701
2701
  config?.dynamic === 'force-static' ||
2702
2702
  config?.revalidate === false ||
2703
2703
  (typeof config?.revalidate === 'number' && config.revalidate > 0)
2704
- );
2704
+ )
2705
2705
  }
2706
2706
 
2707
2707
  async function needsRequestOriginForMetadataImages(appPath: string, route: RouteManifestEntry) {
2708
- if (!(await routeNullsMetadataBase(route))) return false;
2709
- return routeMetadataImageFiles(appPath, route.file);
2708
+ if (!(await routeNullsMetadataBase(route))) return false
2709
+ return routeMetadataImageFiles(appPath, route.file)
2710
2710
  }
2711
2711
 
2712
2712
  async function routeNullsMetadataBase(route: RouteManifestEntry) {
2713
- const sourceFiles = route.sourceFiles.filter(file => /\.(tsx?|jsx?|mjs|cjs)$/.test(file));
2713
+ const sourceFiles = route.sourceFiles.filter(file => /\.(tsx?|jsx?|mjs|cjs)$/.test(file))
2714
2714
  for (const file of sourceFiles) {
2715
2715
  try {
2716
2716
  if (/\bmetadataBase\s*:\s*null\b|\bmetadataBase\s*=\s*null\b/.test(await readText(file))) {
2717
- return true;
2717
+ return true
2718
2718
  }
2719
2719
  } catch {
2720
2720
  // Ignore generated/virtual paths that are not readable at build time.
2721
2721
  }
2722
2722
  }
2723
- return false;
2723
+ return false
2724
2724
  }
2725
2725
 
2726
2726
  function routeMetadataImageFiles(appPath: string, routeFile: string) {
2727
- let dir = path.dirname(routeFile);
2728
- const root = path.resolve(appPath);
2727
+ let dir = path.dirname(routeFile)
2728
+ const root = path.resolve(appPath)
2729
2729
  while (dir.startsWith(root)) {
2730
2730
  if (existsSync(dir)) {
2731
2731
  for (const entry of readdirSync(dir)) {
2732
2732
  if (
2733
2733
  /^(opengraph-image|twitter-image)\d*\.(tsx?|jsx?|mjs|png|jpe?g|gif|webp|svg)$/.test(entry)
2734
2734
  ) {
2735
- return true;
2735
+ return true
2736
2736
  }
2737
2737
  }
2738
2738
  }
2739
- if (dir === root) break;
2740
- dir = path.dirname(dir);
2739
+ if (dir === root) break
2740
+ dir = path.dirname(dir)
2741
2741
  }
2742
- return false;
2742
+ return false
2743
2743
  }
2744
2744
 
2745
2745
  async function copyStaticMetadataFiles(
@@ -2748,32 +2748,32 @@ async function copyStaticMetadataFiles(
2748
2748
  metadataFiles = discoverStaticMetadataFiles(config.appPath),
2749
2749
  ) {
2750
2750
  for (const metadataFile of metadataFiles) {
2751
- const target = staticMetadataOutputFile(config.outPath, metadataFile);
2752
- const collision = staticOutputCollision(config.outPath, target);
2751
+ const target = staticMetadataOutputFile(config.outPath, metadataFile)
2752
+ const collision = staticOutputCollision(config.outPath, target)
2753
2753
  if (collision) {
2754
- warnSkippedStatic(`/${metadataFile.outputPath}`, collision);
2755
- continue;
2754
+ warnSkippedStatic(`/${metadataFile.outputPath}`, collision)
2755
+ continue
2756
2756
  }
2757
- await mkdir(path.dirname(target), { recursive: true });
2758
- await copyFile(metadataFile.file, target);
2757
+ await mkdir(path.dirname(target), { recursive: true })
2758
+ await copyFile(metadataFile.file, target)
2759
2759
  staticFiles[metadataFile.outputPath] = {
2760
2760
  status: 200,
2761
2761
  headers: [
2762
2762
  ['content-type', metadataFile.contentType],
2763
2763
  ['cache-control', staticMetadataCacheControl],
2764
2764
  ],
2765
- };
2765
+ }
2766
2766
  }
2767
2767
  }
2768
2768
 
2769
2769
  export function staticHtmlPath(outPath: string, routePath: string, flatLayout = false) {
2770
- if (routePath === '/') return path.join(outPath, 'public', 'index.html');
2771
- const normalized = routePath.replace(/^\/|\/$/g, '');
2770
+ if (routePath === '/') return path.join(outPath, 'public', 'index.html')
2771
+ const normalized = routePath.replace(/^\/|\/$/g, '')
2772
2772
  // output:'export' with trailingSlash:false lays pages out flat (`/a.html`);
2773
2773
  // otherwise (and always for trailingSlash:true) each page gets its own dir
2774
2774
  // (`/a/index.html`).
2775
- if (flatLayout) return safePublicPath(outPath, `${normalized}.html`);
2776
- return safePublicPath(outPath, normalized, 'index.html');
2775
+ if (flatLayout) return safePublicPath(outPath, `${normalized}.html`)
2776
+ return safePublicPath(outPath, normalized, 'index.html')
2777
2777
  }
2778
2778
 
2779
2779
  // Percent-encode generateStaticParams values the way Next encodes them into
@@ -2787,7 +2787,7 @@ function encodeStaticRouteParams(
2787
2787
  key,
2788
2788
  Array.isArray(value) ? value.map(encodeURIComponent) : encodeURIComponent(value),
2789
2789
  ]),
2790
- );
2790
+ )
2791
2791
  }
2792
2792
 
2793
2793
  function fillRoutePath(
@@ -2795,51 +2795,51 @@ function fillRoutePath(
2795
2795
  params: Record<string, RouteParamValue>,
2796
2796
  generatedParam?: string,
2797
2797
  ) {
2798
- let value = route;
2798
+ let value = route
2799
2799
  for (const [key, param] of Object.entries(params)) {
2800
- const segment = Array.isArray(param) ? param.join('/') : param;
2801
- value = value.replace(`:${key}*`, segment).replace(`:${key}`, segment);
2800
+ const segment = Array.isArray(param) ? param.join('/') : param
2801
+ value = value.replace(`:${key}*`, segment).replace(`:${key}`, segment)
2802
2802
  }
2803
2803
  if (generatedParam && params[generatedParam] !== undefined) {
2804
- const param = params[generatedParam];
2805
- const segment = Array.isArray(param) ? param.join('/') : param;
2806
- value = value.replace(':id', segment);
2804
+ const param = params[generatedParam]
2805
+ const segment = Array.isArray(param) ? param.join('/') : param
2806
+ value = value.replace(':id', segment)
2807
2807
  }
2808
- return value;
2808
+ return value
2809
2809
  }
2810
2810
 
2811
2811
  async function runWithNextBuildPhase<T>(task: () => Promise<T>): Promise<T> {
2812
2812
  // eslint-disable-next-line turbo/no-undeclared-env-vars
2813
- const previous = process.env.NEXT_PHASE;
2813
+ const previous = process.env.NEXT_PHASE
2814
2814
  // eslint-disable-next-line turbo/no-undeclared-env-vars
2815
- process.env.NEXT_PHASE = 'phase-production-build';
2815
+ process.env.NEXT_PHASE = 'phase-production-build'
2816
2816
  try {
2817
- return await task();
2817
+ return await task()
2818
2818
  } finally {
2819
2819
  // eslint-disable-next-line turbo/no-undeclared-env-vars
2820
- if (previous === undefined) delete process.env.NEXT_PHASE;
2820
+ if (previous === undefined) delete process.env.NEXT_PHASE
2821
2821
  // eslint-disable-next-line turbo/no-undeclared-env-vars
2822
- else process.env.NEXT_PHASE = previous;
2822
+ else process.env.NEXT_PHASE = previous
2823
2823
  }
2824
2824
  }
2825
2825
 
2826
2826
  /**
2827
- * The `use cache` SWR response headers for a stashed cacheLife, mirroring the register-usecache.ts
2827
+ * The `use cache` SWR response headers for a stashed cacheLife, mirroring the register/use-cache.ts
2828
2828
  * finalizer. Baked into the manifest so a pure static HIT can re-emit them from start.ts - the
2829
2829
  * finalizer only fires on a live render. Returns [] when no cacheLife applies.
2830
2830
  */
2831
2831
  function cacheLifeResponseHeaders(life: CacheLifeStash | undefined): [string, string][] {
2832
- if (!life) return [];
2833
- const headers: [string, string][] = [];
2834
- const { revalidateSeconds, expireSeconds, staleSeconds } = life;
2832
+ if (!life) return []
2833
+ const headers: [string, string][] = []
2834
+ const { revalidateSeconds, expireSeconds, staleSeconds } = life
2835
2835
  if (revalidateSeconds !== undefined && expireSeconds !== undefined) {
2836
- const swr = Math.max(0, expireSeconds - revalidateSeconds);
2837
- headers.push(['cache-control', `s-maxage=${revalidateSeconds}, stale-while-revalidate=${swr}`]);
2836
+ const swr = Math.max(0, expireSeconds - revalidateSeconds)
2837
+ headers.push(['cache-control', `s-maxage=${revalidateSeconds}, stale-while-revalidate=${swr}`])
2838
2838
  }
2839
2839
  if (staleSeconds !== undefined) {
2840
- headers.push(['x-nextjs-stale-time', String(staleSeconds)]);
2840
+ headers.push(['x-nextjs-stale-time', String(staleSeconds)])
2841
2841
  }
2842
- return headers;
2842
+ return headers
2843
2843
  }
2844
2844
 
2845
2845
  /** Effective ISR TTL: the lowest of the segment `revalidate` export and any data-cache revalidate used during the render. */
@@ -2850,13 +2850,13 @@ function combineRevalidate(
2850
2850
  const candidates = [
2851
2851
  ...(typeof segmentRevalidate === 'number' && segmentRevalidate > 0 ? [segmentRevalidate] : []),
2852
2852
  ...(collected !== undefined && collected > 0 ? [collected] : []),
2853
- ];
2854
- if (candidates.length === 0) return undefined;
2855
- return Math.min(...candidates);
2853
+ ]
2854
+ if (candidates.length === 0) return undefined
2855
+ return Math.min(...candidates)
2856
2856
  }
2857
2857
 
2858
2858
  function staticRouteHandlerPath(outPath: string, routePath: string) {
2859
- return safePublicPath(outPath, routePath.replace(/^\/+/, '') || 'index');
2859
+ return safePublicPath(outPath, routePath.replace(/^\/+/, '') || 'index')
2860
2860
  }
2861
2861
 
2862
2862
  /**
@@ -2866,21 +2866,21 @@ function staticRouteHandlerPath(outPath: string, routePath: string) {
2866
2866
  * route serves dynamically (start's static lookup only matches real files, then falls through).
2867
2867
  */
2868
2868
  function staticOutputCollision(outPath: string, file: string): string | null {
2869
- const publicPath = path.join(outPath, 'public');
2869
+ const publicPath = path.join(outPath, 'public')
2870
2870
  if (existsSync(file) && statSync(file).isDirectory()) {
2871
- return `${toPosixPath(path.relative(publicPath, file))} already exists as a directory`;
2871
+ return `${toPosixPath(path.relative(publicPath, file))} already exists as a directory`
2872
2872
  }
2873
- let dir = path.dirname(file);
2873
+ let dir = path.dirname(file)
2874
2874
  while (dir !== publicPath && dir !== path.dirname(dir)) {
2875
2875
  if (existsSync(dir)) {
2876
2876
  if (!statSync(dir).isDirectory()) {
2877
- return `${toPosixPath(path.relative(publicPath, dir))} already exists as a file`;
2877
+ return `${toPosixPath(path.relative(publicPath, dir))} already exists as a file`
2878
2878
  }
2879
- break;
2879
+ break
2880
2880
  }
2881
- dir = path.dirname(dir);
2881
+ dir = path.dirname(dir)
2882
2882
  }
2883
- return null;
2883
+ return null
2884
2884
  }
2885
2885
 
2886
2886
  /**
@@ -2896,7 +2896,7 @@ function rethrowIfProgrammingError(error: unknown): void {
2896
2896
  error instanceof SyntaxError ||
2897
2897
  (error instanceof TypeError && isBuildInternalStack(error))
2898
2898
  ) {
2899
- throw error;
2899
+ throw error
2900
2900
  }
2901
2901
  }
2902
2902
 
@@ -2905,46 +2905,44 @@ function rethrowIfProgrammingError(error: unknown): void {
2905
2905
  // internals rather than app code, so a genuine render-time `TypeError` still
2906
2906
  // degrades to skip-and-warn.
2907
2907
  function isBuildInternalStack(error: Error): boolean {
2908
- const stack = error.stack ?? '';
2909
- const firstFrame = stack.split('\n').find(line => /^\s*at\s/.test(line)) ?? '';
2910
- return /packages[/\\]pnext[/\\]src[/\\]/.test(firstFrame);
2908
+ const stack = error.stack ?? ''
2909
+ const firstFrame = stack.split('\n').find(line => /^\s*at\s/.test(line)) ?? ''
2910
+ return /packages[/\\]pnext[/\\]src[/\\]/.test(firstFrame)
2911
2911
  }
2912
2912
 
2913
2913
  // Next's NEXT_DEBUG_BUILD static-bailout diagnostic, grepped verbatim by the
2914
2914
  // e2e suites ("should output debug info for static bailouts").
2915
2915
  function logStaticBailout(routePath: string, reason: string) {
2916
- console.log(
2917
- `Static generation failed due to dynamic usage on ${routePath}, reason: ${reason}`,
2918
- );
2916
+ console.log(`Static generation failed due to dynamic usage on ${routePath}, reason: ${reason}`)
2919
2917
  }
2920
2918
 
2921
2919
  // Best-effort label for WHICH request API kept the route dynamic (Next names
2922
2920
  // the first dynamic API hit during its build attempt, e.g. "headers").
2923
2921
  async function requestApiBailoutReason(route: RouteManifestEntry): Promise<string> {
2924
2922
  for (const file of ownSourceFiles(route)) {
2925
- let source: string;
2923
+ let source: string
2926
2924
  try {
2927
- source = await readText(file);
2925
+ source = await readText(file)
2928
2926
  } catch {
2929
- continue;
2927
+ continue
2930
2928
  }
2931
- const match = /\b(headers|cookies|draftMode|connection)\s*\(/.exec(source);
2932
- if (match?.[1]) return match[1];
2929
+ const match = /\b(headers|cookies|draftMode|connection)\s*\(/.exec(source)
2930
+ if (match?.[1]) return match[1]
2933
2931
  }
2934
- return 'dynamic usage';
2932
+ return 'dynamic usage'
2935
2933
  }
2936
2934
 
2937
2935
  // The route's own source files (the closure also carries framework/compat
2938
2936
  // modules, whose bodies mention every request API and every scheduler).
2939
2937
  function ownSourceFiles(route: RouteManifestEntry): string[] {
2940
- const frameworkRoot = path.resolve(fileURLToPath(import.meta.url), '..', '..');
2938
+ const frameworkRoot = path.resolve(fileURLToPath(import.meta.url), '..', '..')
2941
2939
  return route.sourceFiles.filter(file => {
2942
- const resolved = path.resolve(file);
2940
+ const resolved = path.resolve(file)
2943
2941
  return (
2944
2942
  !resolved.includes(`${path.sep}node_modules${path.sep}`) &&
2945
2943
  !resolved.startsWith(frameworkRoot + path.sep)
2946
- );
2947
- });
2944
+ )
2945
+ })
2948
2946
  }
2949
2947
 
2950
2948
  /**
@@ -2954,15 +2952,15 @@ function ownSourceFiles(route: RouteManifestEntry): string[] {
2954
2952
  */
2955
2953
  async function schedulesDeferredWork(route: RouteManifestEntry): Promise<boolean> {
2956
2954
  for (const file of ownSourceFiles(route)) {
2957
- let source: string;
2955
+ let source: string
2958
2956
  try {
2959
- source = await readText(file);
2957
+ source = await readText(file)
2960
2958
  } catch {
2961
- continue;
2959
+ continue
2962
2960
  }
2963
- if (/\b(?:setTimeout|setImmediate|queueMicrotask)\s*\(/.test(source)) return true;
2961
+ if (/\b(?:setTimeout|setImmediate|queueMicrotask)\s*\(/.test(source)) return true
2964
2962
  }
2965
- return false;
2963
+ return false
2966
2964
  }
2967
2965
 
2968
2966
  /**
@@ -2971,33 +2969,33 @@ async function schedulesDeferredWork(route: RouteManifestEntry): Promise<boolean
2971
2969
  * what surfaces is the genuine error - this only decides who catches it.
2972
2970
  */
2973
2971
  function captureDeferredFailures(record: (error: unknown) => void) {
2974
- const globals = globalThis as unknown as Record<string, (...args: unknown[]) => unknown>;
2975
- const originals = new Map<string, (...args: unknown[]) => unknown>();
2972
+ const globals = globalThis as unknown as Record<string, (...args: unknown[]) => unknown>
2973
+ const originals = new Map<string, (...args: unknown[]) => unknown>()
2976
2974
  for (const name of ['setTimeout', 'setImmediate', 'queueMicrotask']) {
2977
- const original = globals[name];
2978
- if (typeof original !== 'function') continue;
2979
- originals.set(name, original);
2975
+ const original = globals[name]
2976
+ if (typeof original !== 'function') continue
2977
+ originals.set(name, original)
2980
2978
  globals[name] = function patched(this: unknown, callback: unknown, ...rest: unknown[]) {
2981
- if (typeof callback !== 'function') return original.call(this, callback, ...rest);
2979
+ if (typeof callback !== 'function') return original.call(this, callback, ...rest)
2982
2980
  const guarded = function guardedCallback(this: unknown, ...args: unknown[]) {
2983
2981
  try {
2984
- const value = (callback as (...inner: unknown[]) => unknown).apply(this, args);
2985
- if (isThenable(value)) void value.then(undefined, record);
2986
- return value;
2982
+ const value = (callback as (...inner: unknown[]) => unknown).apply(this, args)
2983
+ if (isThenable(value)) void value.then(undefined, record)
2984
+ return value
2987
2985
  } catch (error) {
2988
- record(error);
2986
+ record(error)
2989
2987
  }
2990
- };
2991
- return original.call(this, guarded, ...rest);
2992
- };
2988
+ }
2989
+ return original.call(this, guarded, ...rest)
2990
+ }
2993
2991
  }
2994
2992
  return () => {
2995
- for (const [name, original] of originals) globals[name] = original;
2996
- };
2993
+ for (const [name, original] of originals) globals[name] = original
2994
+ }
2997
2995
  }
2998
2996
 
2999
2997
  function isThenable(value: unknown): value is PromiseLike<unknown> {
3000
- return typeof (value as { then?: unknown } | null)?.then === 'function';
2998
+ return typeof (value as { then?: unknown } | null)?.then === 'function'
3001
2999
  }
3002
3000
 
3003
3001
  /**
@@ -3012,10 +3010,10 @@ async function probeDeferredDynamicUsage(
3012
3010
  staticMetadataFiles: StaticMetadataFile[],
3013
3011
  staticModuleMetadata: Record<string, StaticModuleMetadata>,
3014
3012
  ): Promise<void> {
3015
- const routePath = toNextRoutePattern(route.route || '/');
3016
- const failures: unknown[] = [];
3017
- const restore = captureDeferredFailures(error => failures.push(error));
3018
- beginDynamicBailoutProbe(routePath);
3013
+ const routePath = toNextRoutePattern(route.route || '/')
3014
+ const failures: unknown[] = []
3015
+ const restore = captureDeferredFailures(error => failures.push(error))
3016
+ beginDynamicBailoutProbe(routePath)
3019
3017
  try {
3020
3018
  await runWithWorkUnit('render', () =>
3021
3019
  getRenderExtensions().collectRenderMeta(
@@ -3034,37 +3032,37 @@ async function probeDeferredDynamicUsage(
3034
3032
  ),
3035
3033
  { route: route.route || '/', prerender: true },
3036
3034
  ),
3037
- );
3035
+ )
3038
3036
  // Give a 0ms timer scheduled during the render its turn before we stop
3039
3037
  // listening; anything slower keeps the route dynamic without a report.
3040
- await new Promise(resolve => setTimeout(resolve, 0));
3038
+ await new Promise(resolve => setTimeout(resolve, 0))
3041
3039
  } catch {
3042
3040
  // Ordinary bailout (or an unrelated prerender failure): the route just
3043
3041
  // stays dynamic, exactly as it already did without the probe.
3044
- return;
3042
+ return
3045
3043
  } finally {
3046
- endDynamicBailoutProbe();
3047
- restore();
3044
+ endDynamicBailoutProbe()
3045
+ restore()
3048
3046
  }
3049
3047
  for (const failure of failures) {
3050
- if (!(failure instanceof Error) || failure.name !== 'DynamicServerError') continue;
3048
+ if (!(failure instanceof Error) || failure.name !== 'DynamicServerError') continue
3051
3049
  console.error(
3052
3050
  `Error occurred prerendering page "${routePath}". Read more: https://nextjs.org/docs/messages/prerender-error`,
3053
- );
3054
- console.error(`${failure.name}: ${failure.message}`);
3051
+ )
3052
+ console.error(`${failure.name}: ${failure.message}`)
3055
3053
  }
3056
3054
  }
3057
3055
 
3058
3056
  function warnSkippedStatic(routePath: string, reason: string) {
3059
3057
  console.warn(
3060
3058
  `pnext build: skipping static output for ${routePath} (${reason}); the route will be served dynamically`,
3061
- );
3059
+ )
3062
3060
  }
3063
3061
 
3064
3062
  // pnext routes carry Express-style `:param` / `:...catchAll` segments; Next's
3065
3063
  // build diagnostics name routes in `[param]` / `[...catchAll]` form.
3066
3064
  function toNextRoutePattern(route: string): string {
3067
- return route.replace(/:\.\.\.([^/]+)/g, '[...$1]').replace(/:([^/]+)/g, '[$1]');
3065
+ return route.replace(/:\.\.\.([^/]+)/g, '[...$1]').replace(/:([^/]+)/g, '[$1]')
3068
3066
  }
3069
3067
 
3070
3068
  // A throwing after() during a prerender is tagged by the compat after() runtime
@@ -3075,26 +3073,26 @@ function isAfterPrerenderError(error: unknown): boolean {
3075
3073
  typeof error === 'object' &&
3076
3074
  error !== null &&
3077
3075
  (error as Record<symbol, unknown>)[Symbol.for('pnext.afterPrerenderError')] === true
3078
- );
3076
+ )
3079
3077
  }
3080
3078
 
3081
3079
  /** Symlink-resolved path, for comparing two spellings of one file. Missing files keep their path. */
3082
3080
  function realFilePath(file: string): string {
3083
3081
  try {
3084
- return realpathSync.native(file);
3082
+ return realpathSync.native(file)
3085
3083
  } catch {
3086
- return path.resolve(file);
3084
+ return path.resolve(file)
3087
3085
  }
3088
3086
  }
3089
3087
 
3090
3088
  function safePublicPath(outPath: string, ...segments: string[]) {
3091
- const publicPath = path.join(outPath, 'public');
3092
- const file = path.join(publicPath, ...segments);
3093
- const relative = path.relative(publicPath, file);
3089
+ const publicPath = path.join(outPath, 'public')
3090
+ const file = path.join(publicPath, ...segments)
3091
+ const relative = path.relative(publicPath, file)
3094
3092
  if (relative.startsWith('..') || path.isAbsolute(relative)) {
3095
- throw new Error(`Static output path escapes public directory: ${segments.join('/')}`);
3093
+ throw new Error(`Static output path escapes public directory: ${segments.join('/')}`)
3096
3094
  }
3097
- return file;
3095
+ return file
3098
3096
  }
3099
3097
 
3100
3098
  /**
@@ -3104,45 +3102,45 @@ function safePublicPath(outPath: string, ...segments: string[]) {
3104
3102
  * points here, and the 404 is the user's to resolve by installing partytown.
3105
3103
  */
3106
3104
  async function copyPartytownLib(config: Awaited<ReturnType<typeof loadConfig>>) {
3107
- if (!nextCompatEnabled(config)) return;
3108
- if (!buildCompat().nextScriptWorkersEnabled()) return;
3105
+ if (!nextCompatEnabled(config)) return
3106
+ if (!buildCompat().nextScriptWorkersEnabled()) return
3109
3107
 
3110
- const libDir = resolvePartytownLibDir(config.root);
3108
+ const libDir = resolvePartytownLibDir(config.root)
3111
3109
  if (!libDir) {
3112
3110
  console.warn(
3113
3111
  'pnext build: experimental.nextScriptWorkers is enabled but no Partytown package was found ' +
3114
3112
  '(@qwik.dev/partytown or @builder.io/partytown); worker scripts will 404 the library until it is installed',
3115
- );
3116
- return;
3113
+ )
3114
+ return
3117
3115
  }
3118
- const target = path.join(config.outPath, 'public', '_next', 'static', '~partytown');
3119
- await mkdir(target, { recursive: true });
3120
- await copyPublicDir(libDir, target);
3116
+ const target = path.join(config.outPath, 'public', '_next', 'static', '~partytown')
3117
+ await mkdir(target, { recursive: true })
3118
+ await copyPublicDir(libDir, target)
3121
3119
  }
3122
3120
 
3123
3121
  // Locate the Partytown package `lib/` dir from the app root, preferring the
3124
3122
  // current `@qwik.dev/partytown` over the legacy `@builder.io/partytown`. Returns
3125
3123
  // null when neither resolves.
3126
3124
  function resolvePartytownLibDir(root: string): string | null {
3127
- const requireFromRoot = createRequire(path.join(root, 'package.json'));
3125
+ const requireFromRoot = createRequire(path.join(root, 'package.json'))
3128
3126
  for (const pkg of ['@qwik.dev/partytown', '@builder.io/partytown']) {
3129
3127
  try {
3130
- const pkgJson = requireFromRoot.resolve(`${pkg}/package.json`);
3131
- const libDir = path.join(path.dirname(pkgJson), 'lib');
3132
- if (existsSync(libDir) && statSync(libDir).isDirectory()) return libDir;
3128
+ const pkgJson = requireFromRoot.resolve(`${pkg}/package.json`)
3129
+ const libDir = path.join(path.dirname(pkgJson), 'lib')
3130
+ if (existsSync(libDir) && statSync(libDir).isDirectory()) return libDir
3133
3131
  } catch {
3134
3132
  // Package not installed; try the next candidate.
3135
3133
  }
3136
3134
  }
3137
- return null;
3135
+ return null
3138
3136
  }
3139
3137
 
3140
3138
  async function copyPublicDir(from: string, to: string) {
3141
- const files = await listFiles(from);
3139
+ const files = await listFiles(from)
3142
3140
  for (const file of files) {
3143
- const relative = toPosixPath(path.relative(from, file));
3144
- const target = path.join(to, relative);
3145
- await mkdir(path.dirname(target), { recursive: true });
3146
- await copyFile(file, target);
3141
+ const relative = toPosixPath(path.relative(from, file))
3142
+ const target = path.join(to, relative)
3143
+ await mkdir(path.dirname(target), { recursive: true })
3144
+ await copyFile(file, target)
3147
3145
  }
3148
3146
  }