@wular/pnext 0.1.5 → 0.1.7

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 (302) hide show
  1. package/bin/pnext +9 -2
  2. package/dist/server/_chunks/aliases-j47a10ce.js +44 -0
  3. package/dist/server/_chunks/analyze-arswqe6s.js +445 -0
  4. package/dist/server/_chunks/boot-af0mv3dy.js +36 -0
  5. package/dist/server/_chunks/build-spy3dxp4.js +84 -0
  6. package/dist/server/_chunks/build-t9h0hec3.js +3550 -0
  7. package/dist/server/_chunks/build-tier-m7yvqz61.js +2472 -0
  8. package/dist/server/_chunks/compile-53j02j27.js +22 -0
  9. package/dist/server/_chunks/config-1yzwexq6.js +27 -0
  10. package/dist/server/_chunks/config-n2z7yxps.js +22 -0
  11. package/dist/server/_chunks/create-meec9hs6.js +134 -0
  12. package/dist/server/_chunks/dev-fjmqg6en.js +220 -0
  13. package/dist/server/_chunks/esbuild-yyktfrtt.js +15 -0
  14. package/dist/server/_chunks/generate-n9sh6hzt.js +21 -0
  15. package/dist/server/_chunks/index-0374j2b9.js +66 -0
  16. package/dist/server/_chunks/index-06nyqntx.js +213 -0
  17. package/dist/server/_chunks/index-1011cscr.js +5263 -0
  18. package/dist/server/_chunks/index-11sfk4hb.js +170 -0
  19. package/dist/server/_chunks/index-20ppawv7.js +297 -0
  20. package/dist/server/_chunks/index-2dv4y4wd.js +1065 -0
  21. package/dist/server/_chunks/index-2vz0wzdn.js +59 -0
  22. package/dist/server/_chunks/index-2wfv4kvx.js +182 -0
  23. package/dist/server/_chunks/index-357na4a8.js +109 -0
  24. package/dist/server/_chunks/index-3b3at80h.js +109 -0
  25. package/dist/server/_chunks/index-3tf1xjsp.js +6166 -0
  26. package/dist/server/_chunks/index-3xbr4eyv.js +105 -0
  27. package/dist/server/_chunks/index-4399bvd2.js +18 -0
  28. package/dist/server/_chunks/index-4hgjrqh7.js +851 -0
  29. package/dist/server/_chunks/index-4q2gbfwk.js +2530 -0
  30. package/dist/server/_chunks/index-4t92hdxd.js +172 -0
  31. package/dist/server/_chunks/index-4zzebszd.js +10 -0
  32. package/dist/server/_chunks/index-5gyr4emz.js +107 -0
  33. package/dist/server/_chunks/index-5hr4062s.js +70 -0
  34. package/dist/server/_chunks/index-68m3mwbd.js +84 -0
  35. package/dist/server/_chunks/index-6b009mt0.js +56 -0
  36. package/dist/server/_chunks/index-6e4fqv30.js +588 -0
  37. package/dist/server/_chunks/index-6gex3pfa.js +15 -0
  38. package/dist/server/_chunks/index-6kp9r0d9.js +11 -0
  39. package/dist/server/_chunks/index-6q7zvrap.js +134 -0
  40. package/dist/server/_chunks/index-73z4esab.js +38 -0
  41. package/dist/server/_chunks/index-79ngvam8.js +60 -0
  42. package/dist/server/_chunks/index-7wc9n8be.js +25 -0
  43. package/dist/server/_chunks/index-8k7pfejn.js +1599 -0
  44. package/dist/server/_chunks/index-8kcxdfya.js +206 -0
  45. package/dist/server/_chunks/index-91x12ykq.js +274 -0
  46. package/dist/server/_chunks/index-9ch50vtq.js +18 -0
  47. package/dist/server/_chunks/index-9hx2641q.js +28 -0
  48. package/dist/server/_chunks/index-9n2914ww.js +535 -0
  49. package/dist/server/_chunks/index-9ych9ma8.js +207 -0
  50. package/dist/server/_chunks/index-b6wtkdp4.js +24 -0
  51. package/dist/server/_chunks/index-bcws5eh2.js +35 -0
  52. package/dist/server/_chunks/index-c0g2wf3z.js +92 -0
  53. package/dist/server/_chunks/index-cf4rvxv6.js +95 -0
  54. package/dist/server/_chunks/index-cjgqs13g.js +65 -0
  55. package/dist/server/_chunks/index-ckkdpht5.js +41 -0
  56. package/dist/server/_chunks/index-cyfednwb.js +87 -0
  57. package/dist/server/_chunks/index-danncfhy.js +311 -0
  58. package/dist/server/_chunks/index-dg0f05zs.js +199 -0
  59. package/dist/server/_chunks/index-e3a6yx84.js +18 -0
  60. package/dist/server/_chunks/index-e88tyv35.js +45 -0
  61. package/dist/server/_chunks/index-eajaspjk.js +1253 -0
  62. package/dist/server/_chunks/index-eeaanp2e.js +483 -0
  63. package/dist/server/_chunks/index-egykcqg3.js +101 -0
  64. package/dist/server/_chunks/index-en7n9f7m.js +22 -0
  65. package/dist/server/_chunks/index-ezgj0tr1.js +88 -0
  66. package/dist/server/_chunks/index-fkp000p1.js +590 -0
  67. package/dist/server/_chunks/index-fn1chkkd.js +214 -0
  68. package/dist/server/_chunks/index-fp8r0sqs.js +64 -0
  69. package/dist/server/_chunks/index-ggk2j965.js +9432 -0
  70. package/dist/server/_chunks/index-grta8ap3.js +238 -0
  71. package/dist/server/_chunks/index-h2a70tg4.js +7 -0
  72. package/dist/server/_chunks/index-h4xy648y.js +51 -0
  73. package/dist/server/_chunks/index-hkdfj58q.js +92 -0
  74. package/dist/server/_chunks/index-j2zrcp1k.js +66 -0
  75. package/dist/server/_chunks/index-jcsjq3d1.js +20 -0
  76. package/dist/server/_chunks/index-jepjvcrf.js +198 -0
  77. package/dist/server/_chunks/index-jks0x0a1.js +14 -0
  78. package/dist/server/_chunks/index-jx7bqvw1.js +57 -0
  79. package/dist/server/_chunks/index-k1g3epw0.js +1299 -0
  80. package/dist/server/_chunks/index-knp0s912.js +7 -0
  81. package/dist/server/_chunks/index-kq8kpzye.js +472 -0
  82. package/dist/server/_chunks/index-kqyy1d4f.js +50 -0
  83. package/dist/server/_chunks/index-kresp2n8.js +445 -0
  84. package/dist/server/_chunks/index-ms9v9tnx.js +31 -0
  85. package/dist/server/_chunks/index-n4hxpng2.js +11 -0
  86. package/dist/server/_chunks/index-ncnes8c6.js +17 -0
  87. package/dist/server/_chunks/index-nqh3xhdc.js +57 -0
  88. package/dist/server/_chunks/index-nyrba2kr.js +4130 -0
  89. package/dist/server/_chunks/index-p77b4kcx.js +296 -0
  90. package/dist/server/_chunks/index-pym1cezq.js +39 -0
  91. package/dist/server/_chunks/index-q738kkhj.js +100 -0
  92. package/dist/server/_chunks/index-qjj6gddn.js +54 -0
  93. package/dist/server/_chunks/index-qm6kmhqs.js +110 -0
  94. package/dist/server/_chunks/index-qn4z5s3c.js +819 -0
  95. package/dist/server/_chunks/index-qpwsgt3a.js +158 -0
  96. package/dist/server/_chunks/index-r97ptz1q.js +97 -0
  97. package/dist/server/_chunks/index-ras0sdab.js +26 -0
  98. package/dist/server/_chunks/index-rcs322t1.js +19 -0
  99. package/dist/server/_chunks/index-rn8dyr1f.js +126 -0
  100. package/dist/server/_chunks/index-rt9qq1va.js +4 -0
  101. package/dist/server/_chunks/index-rthrsed5.js +356 -0
  102. package/dist/server/_chunks/index-sctcgyvv.js +185 -0
  103. package/dist/server/_chunks/index-sjpckxqn.js +213 -0
  104. package/dist/server/_chunks/index-sq3kvz3g.js +200 -0
  105. package/dist/server/_chunks/index-stbk3jhn.js +31 -0
  106. package/dist/server/_chunks/index-sv8m6789.js +266 -0
  107. package/dist/server/_chunks/index-tgcakdxc.js +125 -0
  108. package/dist/server/_chunks/index-tgsfjmep.js +74 -0
  109. package/dist/server/_chunks/index-tq0pc7eg.js +154 -0
  110. package/dist/server/_chunks/index-tq1f96v7.js +51 -0
  111. package/dist/server/_chunks/index-tqn2qnnn.js +241 -0
  112. package/dist/server/_chunks/index-tvqkq6e6.js +128 -0
  113. package/dist/server/_chunks/index-v1ags1na.js +132 -0
  114. package/dist/server/_chunks/index-vvmmknm1.js +1431 -0
  115. package/dist/server/_chunks/index-vvq7rqtn.js +329 -0
  116. package/dist/server/_chunks/index-vz0km5qk.js +33 -0
  117. package/dist/server/_chunks/index-w6h8m6vp.js +138 -0
  118. package/dist/server/_chunks/index-web361b8.js +138 -0
  119. package/dist/server/_chunks/index-wpfhfbs4.js +194 -0
  120. package/dist/server/_chunks/index-x0y005nh.js +283 -0
  121. package/dist/server/_chunks/index-x3yn4ywm.js +22 -0
  122. package/dist/server/_chunks/index-x93hv0sc.js +271 -0
  123. package/dist/server/_chunks/index-xvj80c3a.js +30 -0
  124. package/dist/server/_chunks/index-xywjg2vc.js +100 -0
  125. package/dist/server/_chunks/index-y2jr2n72.js +152 -0
  126. package/dist/server/_chunks/index-y36a0ybw.js +57 -0
  127. package/dist/server/_chunks/index-ye9hmsvf.js +47 -0
  128. package/dist/server/_chunks/index-yhxkxghe.js +56 -0
  129. package/dist/server/_chunks/index-ym67zqxp.js +130 -0
  130. package/dist/server/_chunks/index-ytjvpbm2.js +736 -0
  131. package/dist/server/_chunks/index-yz54pxrh.js +960 -0
  132. package/dist/server/_chunks/index-z4z9zv3b.js +733 -0
  133. package/dist/server/_chunks/index-z84ab1w0.js +1270 -0
  134. package/dist/server/_chunks/index-z88qv16t.js +11 -0
  135. package/dist/server/_chunks/index-zqpqyg8p.js +666 -0
  136. package/dist/server/_chunks/loader-7yw0j98z.js +78 -0
  137. package/dist/server/_chunks/modules-jn8bmbn8.js +74 -0
  138. package/dist/server/_chunks/pipeline-yy9jhydf.js +832 -0
  139. package/dist/server/_chunks/production-jar9ztm3.js +44 -0
  140. package/dist/server/_chunks/proxy-config-j84dgksn.js +21 -0
  141. package/dist/server/_chunks/proxy-w1d0wmsn.js +33 -0
  142. package/dist/server/_chunks/router-k1reahfn.js +498 -0
  143. package/dist/server/_chunks/routes-2s277fc6.js +64 -0
  144. package/dist/server/_chunks/run-pvd8bs1t.js +696 -0
  145. package/dist/server/_chunks/server-mt4a994c.js +1835 -0
  146. package/dist/server/_chunks/tsconfig-defaults-1nb3308g.js +20 -0
  147. package/dist/server/_chunks/typegen-0qr0459b.js +35 -0
  148. package/dist/server/api/client-cache.js +14 -0
  149. package/dist/server/cli/index.js +378 -0
  150. package/dist/server/cli/start.js +137 -0
  151. package/dist/server/client/reference.js +16 -0
  152. package/dist/server/compat/next/cache.js +56 -0
  153. package/dist/server/compat/next/canonical-url.js +13 -0
  154. package/dist/server/compat/next/client-cache.js +57 -0
  155. package/dist/server/compat/next/client-navigation.js +232 -0
  156. package/dist/server/compat/next/client-only.js +1 -0
  157. package/dist/server/compat/next/client-script.js +178 -0
  158. package/dist/server/compat/next/client-server.js +34 -0
  159. package/dist/server/compat/next/config-headers.js +32 -0
  160. package/dist/server/compat/next/config-loader.js +25 -0
  161. package/dist/server/compat/next/config.js +10 -0
  162. package/dist/server/compat/next/constants.js +18 -0
  163. package/dist/server/compat/next/custom-server.js +10 -0
  164. package/dist/server/compat/next/dist/client/components/app-router-headers.js +48 -0
  165. package/dist/server/compat/next/dist/server/web/spec-extension/revalidate.js +27 -0
  166. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-cache.js +25 -0
  167. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-no-store.js +25 -0
  168. package/dist/server/compat/next/dynamic.js +32 -0
  169. package/dist/server/compat/next/error.js +112 -0
  170. package/dist/server/compat/next/font/cache.js +14 -0
  171. package/dist/server/compat/next/font/google.js +3 -0
  172. package/dist/server/compat/next/font/index.js +1 -0
  173. package/dist/server/compat/next/font/local.js +22 -0
  174. package/dist/server/compat/next/font/runtime-client.js +33 -0
  175. package/dist/server/compat/next/font/runtime.js +41 -0
  176. package/dist/server/compat/next/font/shared.js +22 -0
  177. package/dist/server/compat/next/form.js +129 -0
  178. package/dist/server/compat/next/head.js +10 -0
  179. package/dist/server/compat/next/headers.js +30 -0
  180. package/dist/server/compat/next/image/client.js +11 -0
  181. package/dist/server/compat/next/image/config.js +18 -0
  182. package/dist/server/compat/next/image/optimizer.js +25 -0
  183. package/dist/server/compat/next/image/patterns.js +12 -0
  184. package/dist/server/compat/next/image/props.js +16 -0
  185. package/dist/server/compat/next/image/shared.js +86 -0
  186. package/dist/server/compat/next/image/static-metadata.js +32 -0
  187. package/dist/server/compat/next/image/validate.js +8 -0
  188. package/dist/server/compat/next/image.js +59 -0
  189. package/dist/server/compat/next/index.js +1 -0
  190. package/dist/server/compat/next/legacy-image.js +78 -0
  191. package/dist/server/compat/next/legacy-request-apis.js +24 -0
  192. package/dist/server/compat/next/link-transform.js +14 -0
  193. package/dist/server/compat/next/link.js +14 -0
  194. package/dist/server/compat/next/navigation.js +190 -0
  195. package/dist/server/compat/next/offline.js +45 -0
  196. package/dist/server/compat/next/og.js +8 -0
  197. package/dist/server/compat/next/preferred-region.js +8 -0
  198. package/dist/server/compat/next/redirects.js +30 -0
  199. package/dist/server/compat/next/resource-hints.js +30 -0
  200. package/dist/server/compat/next/rewrites.js +41 -0
  201. package/dist/server/compat/next/root-params.js +121 -0
  202. package/dist/server/compat/next/route-state.js +23 -0
  203. package/dist/server/compat/next/router.js +103 -0
  204. package/dist/server/compat/next/script.js +37 -0
  205. package/dist/server/compat/next/server-only.js +1 -0
  206. package/dist/server/compat/next/server.js +131 -0
  207. package/dist/server/compat/next/svgr.js +14 -0
  208. package/dist/server/compat/next/telemetry.js +10 -0
  209. package/dist/server/compat/next/usage.js +13 -0
  210. package/dist/server/compat/next/user-agent.js +12 -0
  211. package/dist/server/compat/next/web-vitals.js +35 -0
  212. package/dist/server/compat/react/action-state.js +11 -0
  213. package/dist/server/compat/react/client-lite.js +83 -0
  214. package/dist/server/compat/react/client.js +131 -0
  215. package/dist/server/compat/react/compiler-runtime.js +42 -0
  216. package/dist/server/compat/react/dom-client.js +106 -0
  217. package/dist/server/compat/react/dom-react-server.js +37 -0
  218. package/dist/server/compat/react/dom-server.js +40 -0
  219. package/dist/server/compat/react/dom.js +52 -0
  220. package/dist/server/compat/react/hooks-extra.js +12 -0
  221. package/dist/server/compat/react/internals.js +14 -0
  222. package/dist/server/compat/react/parity.js +11 -0
  223. package/dist/server/compat/react/preact.js +113 -0
  224. package/dist/server/compat/react/react-server.js +133 -0
  225. package/dist/server/compat/react/router-shim.js +8 -0
  226. package/dist/server/compat/react/server-component-use.js +14 -0
  227. package/dist/server/compat/react/server-inserted-html.js +14 -0
  228. package/dist/server/compat/react/server.js +194 -0
  229. package/dist/server/compat/react/style-values.js +10 -0
  230. package/dist/server/compat/react/use.js +10 -0
  231. package/dist/server/compat/react/view-transition.js +12 -0
  232. package/dist/server/generation.json +1 -0
  233. package/package.json +4 -1
  234. package/src/api/dynamic.tsx +2 -0
  235. package/src/api/server.ts +24 -6
  236. package/src/cli/adapters/vercel-warm.ts +315 -61
  237. package/src/cli/adapters/vercel.ts +348 -71
  238. package/src/cli/build.ts +58 -33
  239. package/src/cli/index.ts +23 -10
  240. package/src/cli/serve/entry.ts +21 -159
  241. package/src/cli/serve/immutable.ts +31 -0
  242. package/src/cli/serve/pipeline.ts +82 -84
  243. package/src/cli/start.ts +3 -1
  244. package/src/client/chunk-fold.ts +19 -6
  245. package/src/client/react-tier.ts +3 -2
  246. package/src/client/reference-stub.ts +21 -2
  247. package/src/compat/actions/client-plugin.ts +4 -2
  248. package/src/compat/actions/config.ts +6 -5
  249. package/src/compat/actions/extensions.ts +2 -3
  250. package/src/compat/aliases.ts +8 -1
  251. package/src/compat/bundler/config.ts +88 -0
  252. package/src/compat/bundler/optimize-package-imports.ts +86 -26
  253. package/src/compat/bundler/relay-transform.ts +15 -3
  254. package/src/compat/cache-control.ts +16 -8
  255. package/src/compat/css/modules.ts +18 -5
  256. package/src/compat/lifecycle/error-funnel.ts +1 -1
  257. package/src/compat/lifecycle/instrumentation.ts +117 -24
  258. package/src/compat/lifecycle/testmode.ts +3 -2
  259. package/src/compat/next/config-loader.ts +137 -13
  260. package/src/compat/next/font/runtime.ts +22 -2
  261. package/src/compat/next/og.ts +3 -3
  262. package/src/compat/next/usage.ts +3 -2
  263. package/src/compat/pages/api.ts +83 -14
  264. package/src/compat/pages/router.ts +30 -14
  265. package/src/compat/register/bundler.ts +3 -1
  266. package/src/compat/register/middleware.ts +1 -1
  267. package/src/compat/register/otel.ts +5 -9
  268. package/src/compat/register/render.ts +3 -1
  269. package/src/compat/register/use-cache.ts +3 -1
  270. package/src/compat/release.ts +46 -0
  271. package/src/compat/segment/serve.ts +1 -1
  272. package/src/compat/validation/validate.ts +10 -7
  273. package/src/config.ts +51 -7
  274. package/src/css/assets.ts +213 -0
  275. package/src/css/build.ts +174 -238
  276. package/src/dev/server.ts +27 -8
  277. package/src/extensions.ts +4 -0
  278. package/src/render/hooks.ts +13 -0
  279. package/src/render/renderer.ts +52 -32
  280. package/src/request/context.ts +9 -0
  281. package/src/resolve/dynamic.ts +3 -1
  282. package/src/resolve/imports.ts +12 -52
  283. package/src/resolve/package-json.ts +56 -0
  284. package/src/resolve/scan-facts.ts +65 -0
  285. package/src/routing/href.ts +10 -3
  286. package/src/routing/match.ts +287 -0
  287. package/src/routing/metadata-files.ts +19 -5
  288. package/src/routing/proxy-config.ts +137 -0
  289. package/src/routing/proxy.ts +35 -134
  290. package/src/routing/routes.ts +22 -281
  291. package/src/runtime/fingerprint.ts +23 -8
  292. package/src/runtime/load.ts +170 -0
  293. package/src/runtime/loader.ts +215 -43
  294. package/src/runtime/module-cache.ts +13 -0
  295. package/src/runtime/modules.ts +370 -81
  296. package/src/runtime/prebundle.ts +81 -0
  297. package/src/runtime/production.ts +391 -0
  298. package/src/runtime/release-graph.ts +334 -0
  299. package/src/runtime/vendor-build.ts +724 -13
  300. package/src/runtime/vendor.ts +16 -3
  301. package/src/types.ts +2 -0
  302. package/src/utils/fs.ts +89 -3
@@ -13,7 +13,15 @@
13
13
  // export / top-level await / dynamic import() and CJS+ESM authoring styles.
14
14
  // ---------------------------------------------------------------------------
15
15
 
16
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
16
+ import {
17
+ existsSync,
18
+ mkdirSync,
19
+ readFileSync,
20
+ readdirSync,
21
+ renameSync,
22
+ rmSync,
23
+ writeFileSync,
24
+ } from 'node:fs'
17
25
  import { createRequire } from 'node:module'
18
26
  import path from 'node:path'
19
27
  import { pathToFileURL } from 'node:url'
@@ -43,6 +51,12 @@ const CONFIG_BASENAMES = [
43
51
  ]
44
52
 
45
53
  let store: NextConfigObject = {}
54
+ let storeReleased = false
55
+
56
+ /** Whether the stored config came from a production release, which no consumer may re-read from source. */
57
+ export function nextConfigReleased(): boolean {
58
+ return storeReleased
59
+ }
46
60
 
47
61
  /** The full loaded next.config object; empty until loadNextConfig runs. */
48
62
  export function getNextConfig(): NextConfigObject {
@@ -188,19 +202,104 @@ async function importConfigModule(
188
202
  return importConfigBundle(outfile, cjs)
189
203
  }
190
204
 
205
+ /**
206
+ * Compile the app's next.config into `outDir`, plus the cache handlers it names, so a production server
207
+ * imports neither from source. Names carry a content hash: a rebuilt release never reuses the previous
208
+ * generation's module instance. Paths are relative to the bundle, so the release can move.
209
+ */
210
+ export async function emitNextConfigBundle(root: string, outDir: string) {
211
+ const configPath = findConfigPath(root)
212
+ if (!configPath) return undefined
213
+ const cjs = cjsConfigFormat(configPath, root)
214
+ const [file, ...handlerFiles] = await Promise.all([
215
+ hashedOutput(outDir, 'next.config', cjs ? '.cjs' : '.mjs', outfile =>
216
+ bundleConfig({ configPath, root, outDir, outfile, cjs, relocatable: true }),
217
+ ),
218
+ ...cacheHandlerFiles(store).map(([name, source]) =>
219
+ hashedOutput(outDir, name, '.cjs', outfile =>
220
+ build({
221
+ entryPoints: [source],
222
+ outfile,
223
+ bundle: true,
224
+ platform: 'node',
225
+ format: 'cjs',
226
+ target: 'esnext',
227
+ packages: 'external',
228
+ logLevel: 'silent',
229
+ }),
230
+ ).then(output => [name, output] as const),
231
+ ),
232
+ ])
233
+ return { file, handlers: Object.fromEntries(handlerFiles as [string, string][]) }
234
+ }
235
+
236
+ /** Write through `emit` to a temporary name, then publish as `<name>.<content hash><ext>`. */
237
+ async function hashedOutput(
238
+ outDir: string,
239
+ name: string,
240
+ ext: string,
241
+ emit: (outfile: string) => Promise<unknown>,
242
+ ) {
243
+ const temporary = path.join(outDir, `${name}.${process.pid}.tmp${ext}`)
244
+ await emit(temporary)
245
+ const hash = bun.hash(readFileSync(temporary)).toString(36)
246
+ const outfile = path.join(outDir, `${name}.${hash}${ext}`)
247
+ renameSync(temporary, outfile)
248
+ return outfile
249
+ }
250
+
251
+ /** The configured cache handler sources, by the name their release bundle takes. */
252
+ function cacheHandlerFiles(config: NextConfigObject): [string, string][] {
253
+ const handlers = config.cacheHandlers as Record<string, unknown> | undefined
254
+ return [
255
+ ['cache-handler', config.cacheHandler],
256
+ [
257
+ 'cache-handler-default',
258
+ handlers && typeof handlers === 'object' ? handlers.default : undefined,
259
+ ],
260
+ ].filter(
261
+ (entry): entry is [string, string] =>
262
+ typeof entry[1] === 'string' && path.isAbsolute(entry[1]) && existsSync(entry[1]),
263
+ )
264
+ }
265
+
266
+ /** Point a released config's cache handlers at the bundles its build emitted. */
267
+ function releasedCacheHandlers(
268
+ config: NextConfigObject,
269
+ handlers: Record<string, string>,
270
+ ): NextConfigObject {
271
+ const handler = handlers['cache-handler']
272
+ const fallback = handlers['cache-handler-default']
273
+ return {
274
+ ...config,
275
+ ...(handler ? { cacheHandler: handler } : {}),
276
+ ...(fallback
277
+ ? { cacheHandlers: { ...(config.cacheHandlers as object), default: fallback } }
278
+ : {}),
279
+ }
280
+ }
281
+
282
+ const toPosix = (file: string) => file.split(path.sep).join('/')
283
+
191
284
  function bundleConfig({
192
285
  configPath,
193
286
  root,
194
287
  outDir,
195
288
  outfile,
196
289
  cjs,
290
+ relocatable = false,
197
291
  }: {
198
292
  configPath: string
199
293
  root: string
200
294
  outDir: string
201
295
  outfile: string
202
296
  cjs: boolean
297
+ /** A release bundle: locate the config from the bundle itself, never from the build machine's paths. */
298
+ relocatable?: boolean
203
299
  }) {
300
+ const configUrl = relocatable
301
+ ? `new URL(${JSON.stringify(toPosix(path.relative(outDir, configPath)))}, ${cjs ? '__pnextNodeRequire("node:url").pathToFileURL(__filename)' : 'import.meta.url'}).href`
302
+ : JSON.stringify(pathToFileURL(configPath).href)
204
303
  return build({
205
304
  entryPoints: [configPath],
206
305
  outfile,
@@ -220,12 +319,20 @@ function bundleConfig({
220
319
  ? {
221
320
  js:
222
321
  'var __pnextNodeRequire = module.require.bind(module);\n' +
223
- `var require = __pnextNodeRequire("node:module").createRequire(${JSON.stringify(pathToFileURL(configPath).href)});`,
322
+ `var __pnextConfigUrl = ${configUrl};\n` +
323
+ 'var __pnextConfigFile = __pnextNodeRequire("node:url").fileURLToPath(__pnextConfigUrl);\n' +
324
+ 'var __pnextConfigDir = __pnextNodeRequire("node:path").dirname(__pnextConfigFile);\n' +
325
+ 'var require = __pnextNodeRequire("node:module").createRequire(__pnextConfigUrl);',
224
326
  }
225
327
  : {
226
328
  js:
227
329
  'import { createRequire as __pnextCreateRequire } from "node:module";\n' +
228
- `const require = __pnextCreateRequire(${JSON.stringify(pathToFileURL(configPath).href)});`,
330
+ 'import { fileURLToPath as __pnextFileURLToPath } from "node:url";\n' +
331
+ 'import { dirname as __pnextDirname } from "node:path";\n' +
332
+ `const __pnextConfigUrl = ${configUrl};\n` +
333
+ 'const __pnextConfigFile = __pnextFileURLToPath(__pnextConfigUrl);\n' +
334
+ 'const __pnextConfigDir = __pnextDirname(__pnextConfigFile);\n' +
335
+ 'const require = __pnextCreateRequire(__pnextConfigUrl);',
229
336
  },
230
337
  // Relative/absolute imports (incl. .ts/.cts/.mts/.cjs/.mjs and tsconfig
231
338
  // `paths` aliases) are bundled in. esbuild reads `paths`/`extends` and
@@ -235,9 +342,9 @@ function bundleConfig({
235
342
  // import.meta.url in ESM configs (node-api-esm) must point at the config's
236
343
  // original location, not the emitted file under .pnext/config.
237
344
  define: {
238
- __dirname: JSON.stringify(path.dirname(configPath)),
239
- __filename: JSON.stringify(configPath),
240
- 'import.meta.url': JSON.stringify(pathToFileURL(configPath).href),
345
+ __dirname: '__pnextConfigDir',
346
+ __filename: '__pnextConfigFile',
347
+ 'import.meta.url': '__pnextConfigUrl',
241
348
  },
242
349
  logLevel: 'silent',
243
350
  sourcemap: false,
@@ -647,20 +754,36 @@ function validateDevIndicatorPosition(config: NextConfigObject): void {
647
754
  */
648
755
  export async function loadNextConfig(
649
756
  root: string,
650
- options: { dev?: boolean; serve?: boolean; warnings?: boolean } = {},
757
+ options: {
758
+ dev?: boolean
759
+ serve?: boolean
760
+ warnings?: boolean
761
+ bundle?: string | null
762
+ handlers?: Record<string, string>
763
+ } = {},
651
764
  ): Promise<LoadNextConfigResult> {
652
- const configPath = findConfigPath(root)
765
+ // A production server names its compiled config, or null when the app had none.
766
+ storeReleased = options.bundle !== undefined
767
+ const configPath = options.bundle === undefined ? findConfigPath(root) : options.bundle
653
768
  if (!configPath) {
654
769
  setNextConfig({})
655
770
  emitSessionStartedTelemetry({})
656
771
  return { config: {}, overrides: {} }
657
772
  }
773
+ if (options.bundle && !existsSync(options.bundle)) {
774
+ // The release names this file; serving defaults in its place would drop headers, redirects and more.
775
+ throw new Error(
776
+ `pnext: the build's compiled next.config (${options.bundle}) is missing. Run 'pnext build' again.`,
777
+ )
778
+ }
658
779
  let exported: NextConfigExport
659
780
  try {
660
- exported = await importConfigModule(configPath, root, {
661
- dev: Boolean(options.dev),
662
- serve: Boolean(options.serve),
663
- })
781
+ exported = options.bundle
782
+ ? await importConfigBundle(options.bundle, options.bundle.endsWith('.cjs'))
783
+ : await importConfigModule(configPath, root, {
784
+ dev: Boolean(options.dev),
785
+ serve: Boolean(options.serve),
786
+ })
664
787
  } catch (error) {
665
788
  // A next.config that pulls in an optional wrapper dependency not installed
666
789
  // in this app (e.g. @next/mdx when deps are declared but not installed)
@@ -677,10 +800,11 @@ export async function loadNextConfig(
677
800
  }
678
801
  throw error
679
802
  }
680
- const config = normalizeCacheHandlerPath(
803
+ const resolved = normalizeCacheHandlerPath(
681
804
  await resolveConfigExport(exported, Boolean(options.dev)),
682
805
  root,
683
806
  )
807
+ const config = options.bundle ? releasedCacheHandlers(resolved, options.handlers ?? {}) : resolved
684
808
  // Internal build workers need the resolved config but must not surface a
685
809
  // second copy of CLI warnings already emitted by the owning build process.
686
810
  validateConfig(config, configPath, options.warnings !== false)
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url'
8
8
  import type { ResolvedConfig } from '../../../config'
9
9
  import { onExtensionHostReset, registerResponseFinalizers } from '../../../extensions'
10
10
  import { getWorkUnit } from '../../../request/context'
11
+ import { servedRelease } from '../../../runtime/production'
11
12
  import type { MetadataLink } from '../../../types'
12
13
  import { persistFont, persistedFont } from './cache'
13
14
  import { escapeRegex } from '../../../utils/code'
@@ -383,7 +384,23 @@ function registerFont(definition: FontDefinition) {
383
384
  fontStorage.getStore()?.fonts.set(definition.key, definition)
384
385
  }
385
386
 
386
- async function resolveFont(definition: FontDefinition, context: FontResolveContext) {
387
+ /** Every declared font, resolved for a release (its files emitted under the out dir). */
388
+ export async function releaseFonts(config: ResolvedConfig): Promise<Record<string, ResolvedFont>> {
389
+ const fonts: Record<string, ResolvedFont> = {}
390
+ for (const font of declaredFonts.values()) {
391
+ const { sources: _sources, ...resolved } = await resolveFont(font, { config })
392
+ fonts[font.key] = resolved
393
+ }
394
+ return fonts
395
+ }
396
+
397
+ async function resolveFont(
398
+ definition: FontDefinition,
399
+ context: FontResolveContext,
400
+ ): Promise<ResolvedFont> {
401
+ // A production release serves the build's resolution: font sources are not read at request time.
402
+ const released = servedRelease()?.fonts?.[definition.key]
403
+ if (released) return released
387
404
  const key = `${context.config.outPath}:${context.dev ? 'dev' : 'build'}:${definition.key}`
388
405
  let resolved = resolvedFontCache.get(key)
389
406
  if (!resolved) {
@@ -506,7 +523,10 @@ async function resolveLocalFont(
506
523
  ? false
507
524
  : true
508
525
  const preload = request.options.preload !== false
509
- const sources = normalizeLocalSources(request.options, request.callerFile)
526
+ const sources = normalizeLocalSources(
527
+ request.options,
528
+ request.callerFile && path.resolve(context.config.root, request.callerFile),
529
+ )
510
530
  const emitted = await Promise.all(
511
531
  sources.map(async source => ({
512
532
  ...source,
@@ -134,13 +134,13 @@ async function renderWithSatori(
134
134
  element: unknown,
135
135
  options: ImageResponseOptions,
136
136
  ): Promise<Uint8Array | undefined> {
137
+ // Fonts first: without one satori cannot lay out text, so loading it would be wasted work.
138
+ const fonts = normalizeFonts(options.fonts)
139
+ if (fonts.length === 0) return undefined
137
140
  const satori = await loadOptional<SatoriModule>('satori')
138
141
  const resvg = await loadOptional<ResvgModule>('@resvg/resvg-js')
139
142
  if (!satori || !resvg) return undefined
140
143
 
141
- const fonts = normalizeFonts(options.fonts)
142
- if (fonts.length === 0) return undefined
143
-
144
144
  const width = dimension(options.width, DEFAULT_WIDTH, 'width')
145
145
  const height = dimension(options.height, DEFAULT_HEIGHT, 'height')
146
146
 
@@ -1,5 +1,6 @@
1
- import { existsSync, readdirSync, readFileSync, type Dirent } from 'node:fs'
1
+ import { existsSync, readdirSync, type Dirent } from 'node:fs'
2
2
  import path from 'node:path'
3
+ import { readSourceSync } from '../../resolve/source-text'
3
4
 
4
5
  /**
5
6
  * Build-time app facts that let compat drop code an app cannot reach. The fact here: whether any
@@ -95,7 +96,7 @@ function scan(dir: string, match: (source: string) => boolean): boolean {
95
96
  if (!sourceFile.test(entry.name)) continue
96
97
  let source: string
97
98
  try {
98
- source = readFileSync(file, 'utf8')
99
+ source = readSourceSync(file)
99
100
  } catch {
100
101
  continue
101
102
  }
@@ -2,15 +2,19 @@
2
2
  // shims plus the API-route and data-fetch (`/_next/data`) dispatch. Registered by
3
3
  // ../register/pages-api.ts, which wires the exports below into the extension registries.
4
4
 
5
- import { existsSync, readFileSync } from 'node:fs'
5
+ import { type Dirent, existsSync, readFileSync, readdirSync } from 'node:fs'
6
6
  import { Writable } from 'node:stream'
7
7
  import path from 'node:path'
8
- import { devServerModuleHref } from '../../runtime/modules'
9
- import { pagesApiBundleTargetForRuntime } from '../../runtime/loader'
8
+ import {
9
+ devServerModuleHref,
10
+ pagesApiBundleTargetForRuntime,
11
+ serverBundleTargetForRuntime,
12
+ } from '../../runtime/load'
13
+ import { productionRelease } from '../../runtime/production'
14
+ import type { ResolvedConfig } from '../../config'
10
15
  import { getRequestExtensions, type RequestInterceptor } from '../../extensions'
11
16
  import { getRequestRuntime } from '../../routing/request-environment'
12
- import { selectRouteForRequest } from '../../routing/routes'
13
- import { serverBundleTargetForRuntime } from '../../runtime/loader'
17
+ import { selectRouteForRequest } from '../../routing/match'
14
18
  import { setWorkUnitRoute } from '../../request/context'
15
19
  import { canonicalUrlHref } from '../next/canonical-url'
16
20
  import { markPathRevalidated, normalizeRevalidatePath } from '../cache/revalidate'
@@ -65,7 +69,7 @@ export const pagesDataInterceptor: RequestInterceptor = async request => {
65
69
  return dataResponse({ notFound: true }, 404, url)
66
70
  }
67
71
  const route = selection.route
68
- if (!isMaterializedPagesRoute(route.file)) return undefined
72
+ if (!isMaterializedPagesRoute(runtime.config, route.file)) return undefined
69
73
 
70
74
  const module = (await import(
71
75
  await devServerModuleHref(
@@ -150,8 +154,19 @@ function dataResponse(body: Record<string, unknown>, status: number, url: URL):
150
154
 
151
155
  // The materialized hybrid app hosts BOTH routers; only pages wrappers
152
156
  // re-export from source-pages/, so sniff the (tiny, generated) wrapper body —
153
- // app routes must never serve _next/data.
154
- function isMaterializedPagesRoute(file: string): boolean {
157
+ // app routes must never serve _next/data. A release carries the answer.
158
+ const releasedPagesRoutes = new WeakMap<object, Set<string>>()
159
+
160
+ export function isMaterializedPagesRoute(config: ResolvedConfig, file: string): boolean {
161
+ const released = productionRelease(config)?.compat?.pagesRoutes
162
+ if (released) {
163
+ let routes = releasedPagesRoutes.get(released)
164
+ if (!routes) {
165
+ routes = new Set(released.map(route => path.join(config.root, route)))
166
+ releasedPagesRoutes.set(released, routes)
167
+ }
168
+ return routes.has(file)
169
+ }
155
170
  if (!file.split(path.sep).join('/').includes('pnext-pages-compat/')) return false
156
171
  try {
157
172
  return readFileSync(file, 'utf8').includes('source-pages/')
@@ -160,6 +175,29 @@ function isMaterializedPagesRoute(file: string): boolean {
160
175
  }
161
176
  }
162
177
 
178
+ /** The materialized pages-router wrappers of a pages-compat app, root-relative, for its release. */
179
+ export function pagesRouteFacts(config: ResolvedConfig): string[] {
180
+ if (!config.appPath.includes('pnext-pages-compat')) return []
181
+ const routes: string[] = []
182
+ const visit = (dir: string) => {
183
+ let entries: Dirent[]
184
+ try {
185
+ entries = readdirSync(dir, { withFileTypes: true })
186
+ } catch {
187
+ return
188
+ }
189
+ for (const entry of entries) {
190
+ const file = path.join(dir, entry.name)
191
+ if (entry.isDirectory()) visit(file)
192
+ else if (isMaterializedPagesRoute(config, file)) {
193
+ routes.push(path.relative(config.root, file).split(path.sep).join('/'))
194
+ }
195
+ }
196
+ }
197
+ visit(config.appPath)
198
+ return routes
199
+ }
200
+
163
201
  function pagesDataRequestShim(request: Request, url: URL) {
164
202
  return {
165
203
  method: request.method,
@@ -194,11 +232,16 @@ function pagesDataResponseShim() {
194
232
 
195
233
  export const pagesApiInterceptor: RequestInterceptor = async (request, { config }) => {
196
234
  const url = new URL(request.url)
197
- const file = pagesApiFile(config.root, url.pathname)
235
+ const released = productionRelease(config)?.compat?.pagesApi
236
+ const exists = released
237
+ ? (candidate: string) => path.relative(config.root, candidate) in released
238
+ : existsSync
239
+ const file = pagesApiFile(config.root, url.pathname, exists)
198
240
  if (!file) return undefined
241
+ const runtime = released ? released[path.relative(config.root, file)] : apiRuntime(file)
199
242
  const imported = (await import(
200
243
  await devServerModuleHref(config, file, 'build', {
201
- conditionTarget: pagesApiBundleTargetForRuntime(apiRuntime(file)),
244
+ conditionTarget: pagesApiBundleTargetForRuntime(runtime),
202
245
  })
203
246
  )) as PagesApiModule
204
247
  if (typeof imported.default !== 'function') return undefined
@@ -208,7 +251,7 @@ export const pagesApiInterceptor: RequestInterceptor = async (request, { config
208
251
  // nextUrl, text() — Request prototype getters a spread would lose); node
209
252
  // handlers get the IncomingMessage-shaped shim.
210
253
  const handlerRequest =
211
- apiRuntime(file) === 'edge' ? toNextRequest(request) : createPagesApiRequest(request, url)
254
+ runtime === 'edge' ? toNextRequest(request) : createPagesApiRequest(request, url)
212
255
  const finished = withPagesApiHandlerSpan(url.pathname, () =>
213
256
  Promise.resolve(imported.default!(handlerRequest, responder.res)),
214
257
  ).catch(error => responder.fail(error))
@@ -226,7 +269,33 @@ export const pagesApiInterceptor: RequestInterceptor = async (request, { config
226
269
 
227
270
  const STREAM_STARTED = Symbol('pnext.pagesApiStreamStarted')
228
271
 
229
- function pagesApiFile(root: string, pathname: string): string | undefined {
272
+ /** Every pages API source with its runtime (`nodejs` unless declared), root-relative: what a release serves. */
273
+ export function pagesApiFacts(root: string): Record<string, string> {
274
+ const facts: Record<string, string> = {}
275
+ const visit = (dir: string) => {
276
+ let entries: Dirent[]
277
+ try {
278
+ entries = readdirSync(dir, { withFileTypes: true })
279
+ } catch {
280
+ return
281
+ }
282
+ for (const entry of entries) {
283
+ const file = path.join(dir, entry.name)
284
+ if (entry.isDirectory()) visit(file)
285
+ else if (pageExtensions.some(ext => entry.name.endsWith(`.${ext}`))) {
286
+ facts[path.relative(root, file)] = apiRuntime(file) ?? 'nodejs'
287
+ }
288
+ }
289
+ }
290
+ visit(path.join(root, 'pages', 'api'))
291
+ return facts
292
+ }
293
+
294
+ function pagesApiFile(
295
+ root: string,
296
+ pathname: string,
297
+ exists: (file: string) => boolean,
298
+ ): string | undefined {
230
299
  if (pathname !== '/api' && !pathname.startsWith('/api/')) return undefined
231
300
  let relative: string
232
301
  try {
@@ -239,9 +308,9 @@ function pagesApiFile(root: string, pathname: string): string | undefined {
239
308
  if (base !== baseDir && !base.startsWith(`${baseDir}${path.sep}`)) return undefined
240
309
  for (const ext of pageExtensions) {
241
310
  const file = `${base}.${ext}`
242
- if (existsSync(file)) return file
311
+ if (exists(file)) return file
243
312
  const index = path.join(base, `index.${ext}`)
244
- if (existsSync(index)) return index
313
+ if (exists(index)) return index
245
314
  }
246
315
  return undefined
247
316
  }
@@ -57,7 +57,10 @@ function isAppConvention(name: string): boolean {
57
57
  * outside the source app preserves native App routes and avoids hybrid-route
58
58
  * conflict validation; linked sources and node_modules keep normal resolution.
59
59
  */
60
- export async function materializePagesApp(root: string): Promise<string | undefined> {
60
+ export async function materializePagesApp(
61
+ root: string,
62
+ base = path.join(tmpdir(), 'pnext-pages-compat'),
63
+ ): Promise<string | undefined> {
61
64
  const pagesPath = path.join(root, 'pages')
62
65
  if (!existsSync(pagesPath)) return undefined
63
66
 
@@ -71,11 +74,7 @@ export async function materializePagesApp(root: string): Promise<string | undefi
71
74
  // one shared directory let one process scan a half-materialized tree ("page.js doesn't have a root
72
75
  // layout" on a build that passes on the next run). Naming the directory after what goes in it makes
73
76
  // the second writer a no-op, and staging + rename means a reader sees the whole tree or nothing.
74
- const generatedRoot = path.join(
75
- tmpdir(),
76
- 'pnext-pages-compat',
77
- materializationKey(root, nativeApp, pages, apiRoutes),
78
- )
77
+ const generatedRoot = path.join(base, materializationKey(root, nativeApp, pages, apiRoutes))
79
78
  const appPath = path.join(generatedRoot, 'app')
80
79
  if (existsSync(appPath)) return appPath
81
80
 
@@ -88,10 +87,20 @@ export async function materializePagesApp(root: string): Promise<string | undefi
88
87
  await rm(staging, { recursive: true, force: true })
89
88
  if (!existsSync(appPath)) throw new Error(`pnext: could not materialize ${root}/pages`)
90
89
  }
91
- void sweepStaleMaterializations(path.dirname(generatedRoot))
90
+ void sweepStaleMaterializations(
91
+ base,
92
+ isInsideApp(root, generatedRoot) ? path.basename(generatedRoot) : undefined,
93
+ )
92
94
  return appPath
93
95
  }
94
96
 
97
+ const isInsideApp = (root: string, dir: string) => !path.relative(root, dir).startsWith('..')
98
+
99
+ /** A materialized tree's link to app source: relative when the tree lives inside the app. */
100
+ function materializedLinkTarget(root: string, generatedRoot: string, target: string) {
101
+ return isInsideApp(root, generatedRoot) ? path.relative(generatedRoot, target) : target
102
+ }
103
+
95
104
  /**
96
105
  * Everything the materialized tree is derived from: the pages facts (already parsed out of the
97
106
  * sources), the native app tree's shape, and this module — whose `wrapperSource` writes the files.
@@ -134,10 +143,12 @@ function materializationKey(
134
143
  // a dev server holds its materialized tree open for as long as it runs.
135
144
  const STALE_MATERIALIZATION_MS = 7 * 24 * 60 * 60_000
136
145
 
137
- async function sweepStaleMaterializations(base: string) {
146
+ // A build's own output keeps only its `current` tree: nothing else serves from it.
147
+ async function sweepStaleMaterializations(base: string, current?: string) {
138
148
  try {
139
- const cutoff = Date.now() - STALE_MATERIALIZATION_MS
149
+ const cutoff = current ? Infinity : Date.now() - STALE_MATERIALIZATION_MS
140
150
  for (const entry of await readdir(base)) {
151
+ if (current && (entry === current || entry.endsWith('.staging'))) continue
141
152
  const dir = path.join(base, entry)
142
153
  const info = statSync(dir, { throwIfNoEntry: false })
143
154
  if (info && info.mtimeMs < cutoff) await rm(dir, { recursive: true, force: true })
@@ -158,23 +169,28 @@ async function buildMaterializedApp(
158
169
  const appPath = path.join(generatedRoot, 'app')
159
170
  const sourcePages = path.join(generatedRoot, 'source-pages')
160
171
  const sourceApp = path.join(generatedRoot, 'source-app')
172
+ // Inside a build's output the links stay relative, so a moved build names no build-machine path.
173
+ const link = (target: string, at: string) =>
174
+ symlink(materializedLinkTarget(root, generatedRoot, target), at, 'dir')
161
175
 
162
176
  await rm(generatedRoot, { recursive: true, force: true })
163
177
  await mkdir(appPath, { recursive: true })
164
- await symlink(pagesPath, sourcePages, 'dir')
178
+ await link(pagesPath, sourcePages)
179
+ // Inside the app, packages resolve up to the app's own node_modules under their real paths.
165
180
  const nodeModules = path.join(root, 'node_modules')
166
- if (existsSync(nodeModules))
167
- await symlink(nodeModules, path.join(generatedRoot, 'node_modules'), 'dir')
181
+ if (existsSync(nodeModules) && !isInsideApp(root, generatedRoot)) {
182
+ await link(nodeModules, path.join(generatedRoot, 'node_modules'))
183
+ }
168
184
 
169
185
  // Static assets referenced from materialized routes (e.g. an app route's
170
186
  // `new URL('../../public/vercel.png', import.meta.url)` or a `public/*` asset
171
187
  // import) resolve relative to the generated root; without the symlink the
172
188
  // bundler can't find them and the route 500s.
173
189
  const publicDir = path.join(root, 'public')
174
- if (existsSync(publicDir)) await symlink(publicDir, path.join(generatedRoot, 'public'), 'dir')
190
+ if (existsSync(publicDir)) await link(publicDir, path.join(generatedRoot, 'public'))
175
191
 
176
192
  if (existsSync(nativeApp)) {
177
- await symlink(nativeApp, sourceApp, 'dir')
193
+ await link(nativeApp, sourceApp)
178
194
  await materializeNativeApp(sourceApp, appPath)
179
195
  }
180
196
  if (!existsSync(path.join(appPath, 'layout.js'))) {
@@ -46,7 +46,7 @@ import {
46
46
  setResolveExtensions,
47
47
  setTsConfigPath,
48
48
  } from '../../resolve/imports'
49
- import { setServerBundleConditions } from '../../runtime/loader'
49
+ import { setServerBundleConditions } from '../../runtime/load'
50
50
  import { escapeRegex, specifierSniffTokens } from '../../utils/code'
51
51
  import {
52
52
  cacheComponentsEnabled,
@@ -59,6 +59,7 @@ import {
59
59
  relayCompilerConfig,
60
60
  removeConsoleTargets,
61
61
  resolveExtensions,
62
+ builtInServerExternalPackages,
62
63
  serverExternalPackages,
63
64
  swcEnvUsageMode,
64
65
  tsconfigPath,
@@ -160,6 +161,7 @@ export function registerBundlerExtensions(config: ResolvedConfig): void {
160
161
  // Linked workspace packages are deliberately absent: esbuild resolves their
161
162
  // symlinks to real paths, so they never reach a filter as node_modules.
162
163
  transpiled: () => [...transpile].filter(name => !externals.has(name)),
164
+ releaseExternal: name => builtInServerExternalPackages().has(name) && !transpile.has(name),
163
165
  esmExternals,
164
166
  })
165
167
  setExternalLoadResolver(({ root, fromFile, specifier, target }) => {
@@ -14,7 +14,7 @@ import {
14
14
  type ProxyConfig,
15
15
  type ProxyModule,
16
16
  } from '../../routing/proxy'
17
- import { devServerModuleHref } from '../../runtime/modules'
17
+ import { devServerModuleHref } from '../../runtime/load'
18
18
  import { pathToFileHref, type ResolvedConfig } from '../../config'
19
19
  import { toPosixPath } from '../../utils/fs'
20
20
  import type { RouteManifestEntry } from '../../types'
@@ -14,8 +14,7 @@
14
14
  // opentelemetry suite. Everything is inert unless next compat is on AND @opentelemetry/api resolves from
15
15
  // the app.
16
16
 
17
- import { readFileSync } from 'node:fs'
18
- import path from 'node:path'
17
+ import { isMaterializedPagesRoute } from '../pages/api'
19
18
  import type { ResolvedConfig } from '../../config'
20
19
  import type { RouteManifestEntry } from '../../types'
21
20
  import {
@@ -30,7 +29,7 @@ import {
30
29
  } from '../../extensions'
31
30
  import { nextCompatEnabled } from '../aliases'
32
31
  import { getRequestRuntime } from '../../routing/request-environment'
33
- import { selectRouteForRequest } from '../../routing/routes'
32
+ import { selectRouteForRequest } from '../../routing/match'
34
33
  import { queueAfterTask } from '../../request/context'
35
34
  import {
36
35
  getDocumentScriptExtensions,
@@ -247,14 +246,11 @@ function routeEntryFor(routePattern: string): RouteManifestEntry | undefined {
247
246
  // stable generator-owned marker; cache per file (routes don't change at start).
248
247
  const pagesCompatFileCache = new Map<string, boolean>()
249
248
  function isPagesCompatEntry(entry: RouteManifestEntry | undefined): boolean {
250
- if (!entry?.file.includes(`${path.sep}pnext-pages-compat${path.sep}`)) return false
249
+ const config = getRequestRuntime()?.config
250
+ if (!entry || !config) return false
251
251
  let cached = pagesCompatFileCache.get(entry.file)
252
252
  if (cached === undefined) {
253
- try {
254
- cached = readFileSync(entry.file, 'utf8').includes('source-pages/')
255
- } catch {
256
- cached = false
257
- }
253
+ cached = isMaterializedPagesRoute(config, entry.file)
258
254
  pagesCompatFileCache.set(entry.file, cached)
259
255
  }
260
256
  return cached
@@ -32,7 +32,7 @@ import {
32
32
  setStreamRouteExtensions,
33
33
  setSuspenseExtensions,
34
34
  } from '../../render/hooks'
35
- import { withAssetPrefix } from '../../css/build'
35
+ import { withAssetPrefix } from '../../css/assets'
36
36
  import { polyfillsChunkPath } from '../bundler/polyfill'
37
37
  import { nextRouterShimScript } from '../react/router-shim'
38
38
  import { renderPartytownHeadScripts } from '../next/script'
@@ -43,6 +43,7 @@ import {
43
43
  fontPreloadDomScript,
44
44
  fontSizeAdjustMeta,
45
45
  prewarmFonts,
46
+ releaseFonts,
46
47
  runWithFontScope,
47
48
  } from '../next/font/runtime'
48
49
  import {
@@ -117,6 +118,7 @@ export function registerRenderExtensions(config: ResolvedConfig): void {
117
118
  runWithFontScope,
118
119
  collectFontAssets: fontAssets,
119
120
  prewarmFontAssets: prewarmFonts,
121
+ releaseFonts,
120
122
  })
121
123
  setDocumentScriptExtensions({
122
124
  documentBodyScripts: cfg =>