@wular/pnext 0.0.1

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 (371) hide show
  1. package/README.md +153 -0
  2. package/bin/pnext +67 -0
  3. package/config/lint/base.js +48 -0
  4. package/config/ts/base.json +26 -0
  5. package/config/ts/react.json +13 -0
  6. package/package.json +70 -0
  7. package/reference/compat.md +63 -0
  8. package/reference/config.md +120 -0
  9. package/reference/css.md +58 -0
  10. package/reference/dev.md +69 -0
  11. package/reference/env.md +40 -0
  12. package/reference/metadata.md +86 -0
  13. package/reference/navigation.md +149 -0
  14. package/reference/overview.md +35 -0
  15. package/reference/performance.md +97 -0
  16. package/reference/rendering.md +127 -0
  17. package/reference/routing.md +167 -0
  18. package/reference/typegen.md +64 -0
  19. package/src/api/cache.ts +80 -0
  20. package/src/api/client-cache.ts +9 -0
  21. package/src/api/client-navigation.ts +279 -0
  22. package/src/api/dynamic.tsx +102 -0
  23. package/src/api/link.tsx +119 -0
  24. package/src/api/navigation.ts +198 -0
  25. package/src/api/router/events.ts +53 -0
  26. package/src/api/router/history.ts +70 -0
  27. package/src/api/router/hub.ts +194 -0
  28. package/src/api/router/policies.ts +107 -0
  29. package/src/api/router/runtime.ts +5238 -0
  30. package/src/api/router/types.ts +299 -0
  31. package/src/api/router.ts +167 -0
  32. package/src/api/server.ts +323 -0
  33. package/src/api/suspense.ts +16 -0
  34. package/src/cache/context.ts +61 -0
  35. package/src/cli/adapters/vercel-warm.ts +375 -0
  36. package/src/cli/adapters/vercel.ts +1310 -0
  37. package/src/cli/analyze-print.ts +181 -0
  38. package/src/cli/analyze.ts +328 -0
  39. package/src/cli/boot-trace.ts +29 -0
  40. package/src/cli/build.ts +3114 -0
  41. package/src/cli/dev.ts +276 -0
  42. package/src/cli/index.ts +196 -0
  43. package/src/cli/named-bin.ts +119 -0
  44. package/src/cli/serve-ui.ts +160 -0
  45. package/src/cli/start.ts +1425 -0
  46. package/src/client/build.ts +2136 -0
  47. package/src/client/chunk-fold.ts +526 -0
  48. package/src/client/entry.ts +1525 -0
  49. package/src/client/paths.ts +22 -0
  50. package/src/client/prebuilt.ts +621 -0
  51. package/src/client/profile.ts +75 -0
  52. package/src/client/react-compiler.ts +94 -0
  53. package/src/client/reference-stub.ts +145 -0
  54. package/src/client/reference.ts +47 -0
  55. package/src/compat/actions/action-client.ts +531 -0
  56. package/src/compat/actions/action-dispatch.ts +676 -0
  57. package/src/compat/actions/action-router.ts +40 -0
  58. package/src/compat/actions/action-shared.ts +95 -0
  59. package/src/compat/actions/client-plugin.ts +135 -0
  60. package/src/compat/actions/client-stub.ts +65 -0
  61. package/src/compat/actions/config.ts +164 -0
  62. package/src/compat/actions/detect.ts +208 -0
  63. package/src/compat/actions/discovery.ts +244 -0
  64. package/src/compat/actions/early-submit.ts +40 -0
  65. package/src/compat/actions/endpoint.ts +602 -0
  66. package/src/compat/actions/flight.ts +52 -0
  67. package/src/compat/actions/form-state.ts +73 -0
  68. package/src/compat/actions/hoist.ts +485 -0
  69. package/src/compat/actions/ids.ts +39 -0
  70. package/src/compat/actions/index.ts +41 -0
  71. package/src/compat/actions/instances.ts +144 -0
  72. package/src/compat/actions/origin.ts +109 -0
  73. package/src/compat/actions/protocol.ts +125 -0
  74. package/src/compat/actions/registry.ts +74 -0
  75. package/src/compat/actions/rewrite.ts +282 -0
  76. package/src/compat/actions/serve.ts +414 -0
  77. package/src/compat/actions/server-tag.ts +21 -0
  78. package/src/compat/actions/unrecognized-error.ts +30 -0
  79. package/src/compat/adapter/build-complete.ts +257 -0
  80. package/src/compat/bundler/bun-externals.ts +53 -0
  81. package/src/compat/bundler/cjs-exports.ts +542 -0
  82. package/src/compat/bundler/config.ts +363 -0
  83. package/src/compat/bundler/externals.ts +34 -0
  84. package/src/compat/bundler/import-meta-url.ts +60 -0
  85. package/src/compat/bundler/modularize-imports.ts +119 -0
  86. package/src/compat/bundler/new-url-asset.ts +87 -0
  87. package/src/compat/bundler/optimize-package-imports.ts +273 -0
  88. package/src/compat/bundler/polyfill.ts +88 -0
  89. package/src/compat/bundler/react-compiler.ts +61 -0
  90. package/src/compat/bundler/react-profiler.tsx +25 -0
  91. package/src/compat/bundler/relay-transform.ts +116 -0
  92. package/src/compat/bundler/require-context.ts +281 -0
  93. package/src/compat/bundler/resolve-extensions.ts +75 -0
  94. package/src/compat/bundler/source-cache.ts +61 -0
  95. package/src/compat/bundler/static-imports.ts +25 -0
  96. package/src/compat/bundler/symlink-imports.ts +119 -0
  97. package/src/compat/bundler/tsconfig-paths.ts +50 -0
  98. package/src/compat/bundler/wasm.ts +153 -0
  99. package/src/compat/bundler/webpack-loaders.ts +685 -0
  100. package/src/compat/bundler/worker.ts +278 -0
  101. package/src/compat/cache/build-flags.ts +81 -0
  102. package/src/compat/cache/build-prerender-errors.ts +163 -0
  103. package/src/compat/cache/custom-handler.ts +159 -0
  104. package/src/compat/cache/fetch-patch.ts +745 -0
  105. package/src/compat/cache/handler.ts +100 -0
  106. package/src/compat/cache/modern-handler.ts +275 -0
  107. package/src/compat/cache/resume-data-cache.ts +143 -0
  108. package/src/compat/cache/revalidate.ts +759 -0
  109. package/src/compat/cache/runtime-error.ts +124 -0
  110. package/src/compat/cache/use-cache-transform.ts +961 -0
  111. package/src/compat/cache/use-cache.ts +1695 -0
  112. package/src/compat/cache-control.ts +269 -0
  113. package/src/compat/client/base-path.ts +64 -0
  114. package/src/compat/client/css-order.ts +36 -0
  115. package/src/compat/client/errors/control-flow.ts +92 -0
  116. package/src/compat/client/errors/error-boundary.ts +222 -0
  117. package/src/compat/client/errors/global-error.ts +238 -0
  118. package/src/compat/client/errors/install.ts +217 -0
  119. package/src/compat/client/errors/lazy.ts +53 -0
  120. package/src/compat/client/errors/primitive-throw.ts +126 -0
  121. package/src/compat/client/errors/soft-refresh.ts +14 -0
  122. package/src/compat/client/link-status.ts +86 -0
  123. package/src/compat/client/nav-compat-runtime.ts +57 -0
  124. package/src/compat/client/nav-compat.ts +42 -0
  125. package/src/compat/client/navigation-scroll.ts +154 -0
  126. package/src/compat/client/optimistic-routing.ts +206 -0
  127. package/src/compat/client/prefetch-cache.ts +111 -0
  128. package/src/compat/client/route-announcer.ts +72 -0
  129. package/src/compat/client/segment-cache-policy.ts +159 -0
  130. package/src/compat/client/segment-cache.ts +1077 -0
  131. package/src/compat/client/segment-prefetch.ts +375 -0
  132. package/src/compat/client/trailing-slash.ts +24 -0
  133. package/src/compat/css/chunking.ts +254 -0
  134. package/src/compat/css/inline-css.ts +73 -0
  135. package/src/compat/css/lightningcss.ts +90 -0
  136. package/src/compat/css/modules.ts +373 -0
  137. package/src/compat/css/nonce.ts +30 -0
  138. package/src/compat/css/sass-plugin.ts +65 -0
  139. package/src/compat/css/sass.ts +392 -0
  140. package/src/compat/css/styled-jsx-runtime.ts +80 -0
  141. package/src/compat/css/styled-jsx.ts +49 -0
  142. package/src/compat/edge-runtime.ts +71 -0
  143. package/src/compat/export/client.ts +112 -0
  144. package/src/compat/export/index.ts +272 -0
  145. package/src/compat/export/standalone.ts +207 -0
  146. package/src/compat/image-optimizer/cache.ts +119 -0
  147. package/src/compat/image-optimizer/detect.ts +143 -0
  148. package/src/compat/image-optimizer/index.ts +601 -0
  149. package/src/compat/image-optimizer/source.ts +243 -0
  150. package/src/compat/index.ts +458 -0
  151. package/src/compat/lifecycle/after-scope.ts +86 -0
  152. package/src/compat/lifecycle/after.ts +173 -0
  153. package/src/compat/lifecycle/error-funnel.ts +306 -0
  154. package/src/compat/lifecycle/error-serialize.ts +87 -0
  155. package/src/compat/lifecycle/error-ui.ts +167 -0
  156. package/src/compat/lifecycle/instrumentation-client.ts +138 -0
  157. package/src/compat/lifecycle/instrumentation.ts +277 -0
  158. package/src/compat/lifecycle/node-console.ts +19 -0
  159. package/src/compat/lifecycle/testmode.ts +263 -0
  160. package/src/compat/mdx/compile.ts +219 -0
  161. package/src/compat/mdx/next-mdx-stub.ts +46 -0
  162. package/src/compat/mdx/plugin.ts +37 -0
  163. package/src/compat/metadata-route-artifacts.ts +458 -0
  164. package/src/compat/metadata.ts +295 -0
  165. package/src/compat/middleware/manifest.ts +210 -0
  166. package/src/compat/misc/action-return.ts +173 -0
  167. package/src/compat/next/cache.ts +211 -0
  168. package/src/compat/next/canonical-url.ts +35 -0
  169. package/src/compat/next/client-cache.ts +57 -0
  170. package/src/compat/next/client-navigation.ts +313 -0
  171. package/src/compat/next/client-only.ts +3 -0
  172. package/src/compat/next/client-script.tsx +215 -0
  173. package/src/compat/next/client-server.ts +39 -0
  174. package/src/compat/next/config-loader.ts +569 -0
  175. package/src/compat/next/config.ts +29 -0
  176. package/src/compat/next/constants.cjs +6 -0
  177. package/src/compat/next/constants.ts +6 -0
  178. package/src/compat/next/custom-server.ts +236 -0
  179. package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
  180. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
  181. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
  182. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
  183. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
  184. package/src/compat/next/dynamic.tsx +46 -0
  185. package/src/compat/next/error.tsx +148 -0
  186. package/src/compat/next/font/cache.ts +171 -0
  187. package/src/compat/next/font/google.ts +2 -0
  188. package/src/compat/next/font/index.ts +8 -0
  189. package/src/compat/next/font/local.ts +5 -0
  190. package/src/compat/next/font/runtime-client.ts +71 -0
  191. package/src/compat/next/font/runtime.ts +974 -0
  192. package/src/compat/next/font/shared.ts +281 -0
  193. package/src/compat/next/form.tsx +156 -0
  194. package/src/compat/next/head.tsx +10 -0
  195. package/src/compat/next/headers.ts +247 -0
  196. package/src/compat/next/image/config.ts +196 -0
  197. package/src/compat/next/image/optimizer.ts +96 -0
  198. package/src/compat/next/image/patterns.ts +103 -0
  199. package/src/compat/next/image/shared.ts +141 -0
  200. package/src/compat/next/image/static-metadata.ts +283 -0
  201. package/src/compat/next/image/validate.ts +269 -0
  202. package/src/compat/next/image-client.tsx +215 -0
  203. package/src/compat/next/image-props.ts +575 -0
  204. package/src/compat/next/image-usage.ts +102 -0
  205. package/src/compat/next/image.tsx +56 -0
  206. package/src/compat/next/index.ts +1 -0
  207. package/src/compat/next/legacy-image.tsx +97 -0
  208. package/src/compat/next/link-usage.ts +29 -0
  209. package/src/compat/next/link-validation-transform.ts +200 -0
  210. package/src/compat/next/link.tsx +466 -0
  211. package/src/compat/next/navigation.cjs +21 -0
  212. package/src/compat/next/navigation.ts +188 -0
  213. package/src/compat/next/offline.ts +51 -0
  214. package/src/compat/next/og.ts +324 -0
  215. package/src/compat/next/optimistic-route-state.ts +188 -0
  216. package/src/compat/next/preferred-region.ts +39 -0
  217. package/src/compat/next/redirects.ts +131 -0
  218. package/src/compat/next/resource-hints.ts +136 -0
  219. package/src/compat/next/rewrites.ts +350 -0
  220. package/src/compat/next/root-params.ts +142 -0
  221. package/src/compat/next/router.cjs +49 -0
  222. package/src/compat/next/router.ts +143 -0
  223. package/src/compat/next/script.tsx +355 -0
  224. package/src/compat/next/server-only.ts +3 -0
  225. package/src/compat/next/server.ts +28 -0
  226. package/src/compat/next/svgr.ts +58 -0
  227. package/src/compat/next/telemetry.ts +77 -0
  228. package/src/compat/next/user-agent.ts +100 -0
  229. package/src/compat/next/web-vitals.ts +56 -0
  230. package/src/compat/otel/api.ts +95 -0
  231. package/src/compat/otel/client-trace-metadata.ts +71 -0
  232. package/src/compat/otel/fetch-span.ts +77 -0
  233. package/src/compat/otel/tracer.ts +944 -0
  234. package/src/compat/pages/client-plugin.ts +108 -0
  235. package/src/compat/pages/index.ts +527 -0
  236. package/src/compat/pages/router-state.ts +94 -0
  237. package/src/compat/ppr/io.ts +38 -0
  238. package/src/compat/ppr/missing-root-params.ts +105 -0
  239. package/src/compat/ppr/root-params-scan.ts +164 -0
  240. package/src/compat/ppr/root-params-transform.ts +75 -0
  241. package/src/compat/ppr/root-params.ts +129 -0
  242. package/src/compat/ppr/segment-config-incompat.ts +34 -0
  243. package/src/compat/protocol.ts +202 -0
  244. package/src/compat/react/client.ts +59 -0
  245. package/src/compat/react/compiler-runtime.ts +60 -0
  246. package/src/compat/react/dom-client.ts +115 -0
  247. package/src/compat/react/dom-react-server.ts +20 -0
  248. package/src/compat/react/dom-server.ts +40 -0
  249. package/src/compat/react/dom.ts +154 -0
  250. package/src/compat/react/preact.ts +522 -0
  251. package/src/compat/react/react-server.ts +84 -0
  252. package/src/compat/react/router-shim.ts +26 -0
  253. package/src/compat/react/server-component-use.ts +48 -0
  254. package/src/compat/react/server-inserted-html.ts +87 -0
  255. package/src/compat/react/server.ts +156 -0
  256. package/src/compat/react/view-transition.ts +60 -0
  257. package/src/compat/register/actions.ts +875 -0
  258. package/src/compat/register/boot.ts +141 -0
  259. package/src/compat/register/build-tier.ts +11 -0
  260. package/src/compat/register/build.ts +182 -0
  261. package/src/compat/register/bundler.ts +587 -0
  262. package/src/compat/register/cache.ts +85 -0
  263. package/src/compat/register/client-errors.ts +18 -0
  264. package/src/compat/register/config.ts +17 -0
  265. package/src/compat/register/css-extras.ts +120 -0
  266. package/src/compat/register/edge-runtime.ts +6 -0
  267. package/src/compat/register/errors.ts +46 -0
  268. package/src/compat/register/export.ts +23 -0
  269. package/src/compat/register/font.ts +36 -0
  270. package/src/compat/register/hooks.ts +34 -0
  271. package/src/compat/register/image.ts +133 -0
  272. package/src/compat/register/index.ts +111 -0
  273. package/src/compat/register/instrumentation-client.ts +35 -0
  274. package/src/compat/register/lifecycle.ts +86 -0
  275. package/src/compat/register/mdx.ts +48 -0
  276. package/src/compat/register/middleware.ts +36 -0
  277. package/src/compat/register/misc.ts +44 -0
  278. package/src/compat/register/otel.ts +288 -0
  279. package/src/compat/register/pages-api.ts +473 -0
  280. package/src/compat/register/ppr.ts +56 -0
  281. package/src/compat/register/protocol.ts +57 -0
  282. package/src/compat/register/proxy.ts +127 -0
  283. package/src/compat/register/render.ts +268 -0
  284. package/src/compat/register/routing.ts +410 -0
  285. package/src/compat/register/segment.ts +1903 -0
  286. package/src/compat/register/static-image.ts +21 -0
  287. package/src/compat/register/typed-routes.ts +35 -0
  288. package/src/compat/register/usecache.ts +131 -0
  289. package/src/compat/register/validation.ts +56 -0
  290. package/src/compat/segment/loading-boundary.ts +113 -0
  291. package/src/compat/segment/page-slot.ts +200 -0
  292. package/src/compat/segment/tree.ts +481 -0
  293. package/src/compat/segment/vary-key.ts +102 -0
  294. package/src/compat/segment/vary-params.ts +551 -0
  295. package/src/compat/static-params.ts +33 -0
  296. package/src/compat/tsconfig-defaults.ts +301 -0
  297. package/src/compat/typecheck/index.ts +1481 -0
  298. package/src/compat/typecheck/worker.ts +26 -0
  299. package/src/compat/typed-routes/index.ts +92 -0
  300. package/src/compat/typed-routes/manifest.ts +356 -0
  301. package/src/compat/typed-routes/typegen.ts +566 -0
  302. package/src/compat/validation/errors.ts +159 -0
  303. package/src/compat/validation/index.ts +1770 -0
  304. package/src/compat/validation/prerender-diagnostics.ts +1508 -0
  305. package/src/compat-bootstrap.ts +67 -0
  306. package/src/config.ts +218 -0
  307. package/src/css/build.ts +697 -0
  308. package/src/css/index.ts +2 -0
  309. package/src/css/postcss.ts +236 -0
  310. package/src/css/worker.ts +34 -0
  311. package/src/dev/client-actions.ts +35 -0
  312. package/src/dev/client-chunk-store.ts +92 -0
  313. package/src/dev/client-key-cache.ts +178 -0
  314. package/src/dev/global-css-cache.ts +212 -0
  315. package/src/dev/imports.ts +2430 -0
  316. package/src/dev/module-cache.ts +721 -0
  317. package/src/dev/module-generations.ts +38 -0
  318. package/src/dev/module-transform.ts +188 -0
  319. package/src/dev/node-module-bundle-cache.ts +63 -0
  320. package/src/dev/restart-cache.ts +10 -0
  321. package/src/dev/route-bundle-key-cache.ts +154 -0
  322. package/src/dev/route-facts-cache.ts +223 -0
  323. package/src/dev/server.ts +1710 -0
  324. package/src/dynamic/source.ts +307 -0
  325. package/src/dynamic/tree-shake.ts +262 -0
  326. package/src/env.ts +92 -0
  327. package/src/extensions.ts +1898 -0
  328. package/src/index.ts +34 -0
  329. package/src/internal.ts +43 -0
  330. package/src/islands/boundary-error.ts +8 -0
  331. package/src/islands/static-children.ts +37 -0
  332. package/src/islands/static-slots.ts +106 -0
  333. package/src/ppr-postpone.ts +24 -0
  334. package/src/ppr.ts +784 -0
  335. package/src/proxy.ts +752 -0
  336. package/src/render/hooks.ts +384 -0
  337. package/src/render/index.ts +1 -0
  338. package/src/render/island-context.ts +47 -0
  339. package/src/render/metadata.ts +857 -0
  340. package/src/render/renderer.ts +7391 -0
  341. package/src/render/resource-hints.ts +44 -0
  342. package/src/render/slots.tsx +679 -0
  343. package/src/request/context.ts +396 -0
  344. package/src/resolve/engine.ts +219 -0
  345. package/src/resolve/imports.ts +1104 -0
  346. package/src/resolve/scan-facts.ts +474 -0
  347. package/src/resolve/source-text.ts +86 -0
  348. package/src/routing/forwarded.ts +41 -0
  349. package/src/routing/handler.ts +271 -0
  350. package/src/routing/href.ts +203 -0
  351. package/src/routing/metadata.ts +1018 -0
  352. package/src/routing/request-runtime.ts +43 -0
  353. package/src/routing/routes.ts +2560 -0
  354. package/src/routing/slots.ts +432 -0
  355. package/src/runtime/server.ts +3453 -0
  356. package/src/runtime/vendor.ts +1160 -0
  357. package/src/style-modules.d.ts +9 -0
  358. package/src/typegen.ts +151 -0
  359. package/src/types.ts +725 -0
  360. package/src/utils/ansi.ts +9 -0
  361. package/src/utils/content-type.ts +31 -0
  362. package/src/utils/decode.ts +7 -0
  363. package/src/utils/dev-profile.ts +31 -0
  364. package/src/utils/error-log.ts +29 -0
  365. package/src/utils/fs-cache.ts +31 -0
  366. package/src/utils/fs.ts +119 -0
  367. package/src/utils/html.ts +46 -0
  368. package/src/utils/serialize.ts +378 -0
  369. package/src/utils/source.ts +35 -0
  370. package/src/utils/verbose.ts +39 -0
  371. package/tsconfig.json +10 -0
@@ -0,0 +1,269 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { nextCompatEnabled } from '../compat';
4
+ import type { ResolvedConfig } from '../config';
5
+ import type { ResponseFinalizer, ResponseFinalizerContext } from '../extensions';
6
+ import { getNextConfig } from './next/config-loader';
7
+ import {
8
+ evaluateConditions,
9
+ parseConditions,
10
+ substitute,
11
+ type RewriteCondition,
12
+ type RewriteRequestContext,
13
+ } from './next/rewrites';
14
+
15
+ const DEFAULT_EXPIRE_TIME = 31536000;
16
+
17
+ interface HeaderRule {
18
+ regex: RegExp;
19
+ paramNames: string[];
20
+ headers: [string, string][];
21
+ has: RewriteCondition[];
22
+ missing: RewriteCondition[];
23
+ }
24
+
25
+ interface HeaderEntry {
26
+ source?: string;
27
+ headers?: unknown;
28
+ has?: unknown;
29
+ missing?: unknown;
30
+ }
31
+
32
+ interface RevalidateRule {
33
+ regex: RegExp;
34
+ revalidateSeconds: number;
35
+ }
36
+
37
+ interface ConfigHeader {
38
+ key?: unknown;
39
+ value?: unknown;
40
+ }
41
+
42
+ export function cacheControlFinalizers(config: ResolvedConfig): ResponseFinalizer[] {
43
+ if (!nextCompatEnabled(config)) return [];
44
+
45
+ let rules: Promise<HeaderRule[]> | undefined;
46
+ let pagesRevalidateRules: RevalidateRule[] | undefined;
47
+ const finalizer: ResponseFinalizer = async ctx => {
48
+ rules ??= loadHeaderRules();
49
+ applyConfigHeaders(ctx, await rules);
50
+ pagesRevalidateRules ??= loadPagesRevalidateRules(config.root);
51
+ applyDefaultIsrHeader(ctx, pagesRevalidateRules);
52
+ };
53
+ return [finalizer];
54
+ }
55
+
56
+ async function loadHeaderRules(): Promise<HeaderRule[]> {
57
+ const producer = getNextConfig().headers;
58
+ if (typeof producer !== 'function') return [];
59
+
60
+ let entries: unknown;
61
+ try {
62
+ entries = await (producer as () => unknown)();
63
+ } catch {
64
+ return [];
65
+ }
66
+ if (!Array.isArray(entries)) return [];
67
+
68
+ const rules: HeaderRule[] = [];
69
+ for (const entry of entries as HeaderEntry[]) {
70
+ if (!entry?.source || !Array.isArray(entry.headers)) continue;
71
+ const compiled = compileHeaderSource(entry.source);
72
+ if (!compiled) continue;
73
+ const has = parseConditions(entry.has);
74
+ const missing = parseConditions(entry.missing);
75
+ if (!has || !missing) continue;
76
+ const headers = headerPairs(entry.headers);
77
+ if (headers.length === 0) continue;
78
+ rules.push({ ...compiled, headers, has, missing });
79
+ }
80
+ return rules;
81
+ }
82
+
83
+ /**
84
+ * Compile a next.config `headers`/`redirects` source pattern into a pathname regex. Mirrors the
85
+ * rewrites source compiler for `:param` tokens but, unlike it, escapes literal regex-special characters
86
+ * in plain segments (the `.` in `/favicon.ico`, say) instead of rejecting the rule - Next's
87
+ * path-to-regexp treats those as literals. Raw regex bodies still only appear inside `:param(...)`.
88
+ */
89
+ function compileHeaderSource(source: string): { regex: RegExp; paramNames: string[] } | null {
90
+ const paramNames: string[] = [];
91
+ const parts: string[] = [];
92
+ for (const segment of source.split('/').filter(Boolean)) {
93
+ const custom = /^:([A-Za-z_$][\w$]*)\((.*)\)$/.exec(segment);
94
+ if (custom?.[1] !== undefined && custom[2] !== undefined) {
95
+ paramNames.push(custom[1]);
96
+ parts.push(`(${custom[2]})`);
97
+ continue;
98
+ }
99
+ const catchAll = /^:([A-Za-z_$][\w$]*)[*+]$/.exec(segment);
100
+ if (catchAll?.[1]) {
101
+ paramNames.push(catchAll[1]);
102
+ parts.push('(.*)');
103
+ continue;
104
+ }
105
+ const optional = /^:([A-Za-z_$][\w$]*)\?$/.exec(segment);
106
+ if (optional?.[1]) {
107
+ paramNames.push(optional[1]);
108
+ parts.push('([^/]*)');
109
+ continue;
110
+ }
111
+ const param = /^:([A-Za-z_$][\w$]*)$/.exec(segment);
112
+ if (param?.[1]) {
113
+ paramNames.push(param[1]);
114
+ parts.push('([^/]+)');
115
+ continue;
116
+ }
117
+ // Plain literal segment: escape regex-special characters so `.` etc. match
118
+ // literally rather than acting as regex metacharacters.
119
+ parts.push(segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
120
+ }
121
+ return { regex: new RegExp(`^/${parts.join('/')}/?$`), paramNames };
122
+ }
123
+
124
+ function headerPairs(headers: unknown[]): [string, string][] {
125
+ const pairs: [string, string][] = [];
126
+ for (const header of headers as ConfigHeader[]) {
127
+ if (typeof header?.key !== 'string' || typeof header.value !== 'string') continue;
128
+ pairs.push([header.key, header.value]);
129
+ }
130
+ return pairs;
131
+ }
132
+
133
+ function applyConfigHeaders(ctx: ResponseFinalizerContext, rules: readonly HeaderRule[]): void {
134
+ if (rules.length === 0) return;
135
+ const url = ctx.request.url;
136
+ const requestCtx: RewriteRequestContext = {
137
+ host: url.host,
138
+ headers: ctx.request.headers,
139
+ cookies: parseCookieHeader(ctx.request.headers.get('cookie')),
140
+ query: url.searchParams,
141
+ };
142
+
143
+ // Config `Set-Cookie` headers must be appended (each entry is its own header,
144
+ // never overwriting) and must precede the response's own Set-Cookie headers —
145
+ // Next applies config headers at the router layer before the route's own
146
+ // cookies (set-cookies suite asserts [...nextConfigHeaders, ...cookies]).
147
+ const configSetCookies: string[] = [];
148
+ for (const rule of rules) {
149
+ const match = rule.regex.exec(url.pathname);
150
+ if (!match) continue;
151
+ const params: Record<string, string> = {};
152
+ rule.paramNames.forEach((name, index) => {
153
+ params[name] = match[index + 1] ?? '';
154
+ });
155
+ const conditionParams = evaluateConditions(rule, requestCtx);
156
+ if (conditionParams === null) continue;
157
+ Object.assign(params, conditionParams);
158
+ for (const [key, value] of rule.headers) {
159
+ const resolved = substitute(value, params).pathname;
160
+ if (key.toLowerCase() === 'set-cookie') configSetCookies.push(resolved);
161
+ else ctx.headers.set(key, resolved);
162
+ }
163
+ }
164
+ if (configSetCookies.length > 0) {
165
+ const existing =
166
+ (ctx.headers as Headers & { getSetCookie?: () => string[] }).getSetCookie?.() ?? [];
167
+ ctx.headers.delete('set-cookie');
168
+ for (const cookie of configSetCookies) ctx.headers.append('set-cookie', cookie);
169
+ for (const cookie of existing) ctx.headers.append('set-cookie', cookie);
170
+ }
171
+ }
172
+
173
+ function applyDefaultIsrHeader(
174
+ ctx: ResponseFinalizerContext,
175
+ pagesRevalidateRules: readonly RevalidateRule[],
176
+ ): void {
177
+ const pagesRevalidateSeconds = pagesRevalidateRules.find(rule =>
178
+ rule.regex.test(ctx.request.url.pathname),
179
+ )?.revalidateSeconds;
180
+ const existing = ctx.headers.get('cache-control');
181
+ // An RSC payload is never browser-cacheable: the flight response embeds the
182
+ // render's data, so a cached copy replays stale content after a revalidation
183
+ // (actions "should invalidate client cache when path is revalidated"). Match
184
+ // the RSC document path in register-segment, which stamps `private, no-store`.
185
+ if (isRscPayload(ctx)) {
186
+ if (existing === null || isBrowserCacheable(existing)) {
187
+ ctx.headers.set('cache-control', 'private, no-store');
188
+ }
189
+ return;
190
+ }
191
+ if (existing && (pagesRevalidateSeconds === undefined || existing !== 'no-store')) return;
192
+ const revalidateSeconds =
193
+ pagesRevalidateSeconds ??
194
+ (ctx.routeMode === 'isr' ? ctx.hints?.revalidateSeconds : undefined);
195
+ if (typeof revalidateSeconds !== 'number' || revalidateSeconds <= 0) return;
196
+ const config = getNextConfig();
197
+ const expireTime =
198
+ typeof config.expireTime === 'number' ? config.expireTime : DEFAULT_EXPIRE_TIME;
199
+ const swr = Math.max(0, expireTime - revalidateSeconds);
200
+ ctx.headers.set('cache-control', `s-maxage=${revalidateSeconds}, stale-while-revalidate=${swr}`);
201
+ }
202
+
203
+ /** A flight response: core's `data` route kind, or an `_rsc` cache-busted URL. */
204
+ function isRscPayload(ctx: ResponseFinalizerContext): boolean {
205
+ return ctx.routeKind === 'data' || ctx.request.url.searchParams.has('_rsc');
206
+ }
207
+
208
+ /** True for a cache-control a browser may reuse (no `no-store`/`no-cache`). */
209
+ function isBrowserCacheable(value: string): boolean {
210
+ return !/\bno-store\b|\bno-cache\b/i.test(value);
211
+ }
212
+
213
+ function loadPagesRevalidateRules(root: string): RevalidateRule[] {
214
+ const pagesDir = path.join(root, 'pages');
215
+ if (!existsSync(pagesDir)) return [];
216
+ const rules: RevalidateRule[] = [];
217
+ for (const file of walkFiles(pagesDir)) {
218
+ const source = readFileSync(file, 'utf8');
219
+ if (!/\bgetStaticProps\b/.test(source)) continue;
220
+ const match = /\brevalidate\s*:\s*(\d+)/.exec(source);
221
+ if (!match?.[1]) continue;
222
+ const regex = pagesRouteRegex(path.relative(pagesDir, file));
223
+ if (!regex) continue;
224
+ rules.push({ regex, revalidateSeconds: Number(match[1]) });
225
+ }
226
+ return rules;
227
+ }
228
+
229
+ function walkFiles(dir: string): string[] {
230
+ const files: string[] = [];
231
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
232
+ const file = path.join(dir, entry.name);
233
+ if (entry.isDirectory()) files.push(...walkFiles(file));
234
+ else if (/\.(?:tsx|ts|jsx|js|mjs)$/.test(entry.name)) files.push(file);
235
+ }
236
+ return files;
237
+ }
238
+
239
+ function pagesRouteRegex(relative: string): RegExp | undefined {
240
+ const withoutExt = relative.replace(/\.(?:tsx|ts|jsx|js|mjs)$/, '').split(path.sep).join('/');
241
+ const route = withoutExt === 'index' ? '' : withoutExt.replace(/\/index$/, '');
242
+ const parts = route.split('/').filter(Boolean);
243
+ const pattern = parts
244
+ .map(part => {
245
+ if (/^\[\[\.\.\.[^\]]+\]\]$/.test(part)) return '(?:/.*)?';
246
+ if (/^\[\.\.\.[^\]]+\]$/.test(part)) return '/.+';
247
+ if (/^\[[^\]]+\]$/.test(part)) return '/[^/]+';
248
+ return `/${escapeRegex(part)}`;
249
+ })
250
+ .join('');
251
+ return new RegExp(`^${pattern || '/'}\\/?$`);
252
+ }
253
+
254
+ function escapeRegex(value: string): string {
255
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
256
+ }
257
+
258
+ function parseCookieHeader(header: string | null): Record<string, string> {
259
+ const cookies: Record<string, string> = {};
260
+ if (!header) return cookies;
261
+ for (const part of header.split(';')) {
262
+ const index = part.indexOf('=');
263
+ if (index === -1) continue;
264
+ const name = part.slice(0, index).trim();
265
+ if (!name) continue;
266
+ cookies[name] = decodeURIComponent(part.slice(index + 1).trim());
267
+ }
268
+ return cookies;
269
+ }
@@ -0,0 +1,64 @@
1
+ // Client basePath helpers (COMPAT - ships to the browser).
2
+ //
3
+ // The server injects `window.__PNEXT_BASE_PATH__` when `next.config.basePath` is set. The core router
4
+ // treats every same-origin URL as soft-navigable, but under a basePath an absolute same-host URL
5
+ // *outside* the basePath (a `basePath:false` rewrite target, say) must be a real MPA navigation, not a
6
+ // soft swap. These helpers give the core router that boundary check without a next.config dependency.
7
+ //
8
+ // Core reads them through a registered hook. A pure-core app never sets the global, so basePath() is ''
9
+ // and every same-origin URL stays soft-navigable exactly as before.
10
+
11
+ declare global {
12
+ interface Window {
13
+ __PNEXT_BASE_PATH__?: string;
14
+ }
15
+ var __PNEXT_BASE_PATH__: string | undefined;
16
+ }
17
+
18
+ /**
19
+ * The configured basePath ('' when unset). Isomorphic: the client reads the
20
+ * `window` global the server injects (register-render basePathScript); the
21
+ * server reads the `globalThis` global set when next.config is loaded
22
+ * (config-loader setNextConfig). Neither branch imports the esbuild-backed
23
+ * config loader, so this stays safe to ship to the browser.
24
+ */
25
+ export function basePath(): string {
26
+ if (process.browser || typeof window !== 'undefined') return window.__PNEXT_BASE_PATH__ ?? '';
27
+ return globalThis.__PNEXT_BASE_PATH__ ?? '';
28
+ }
29
+
30
+ /**
31
+ * Prefix a root-relative in-app path with the configured basePath (Next's
32
+ * addBasePath). Absolute URLs, protocol-relative URLs, non-root-relative
33
+ * strings, and paths already under the basePath are returned unchanged. A
34
+ * no-basePath app is always a no-op.
35
+ */
36
+ export function addBasePath(path: string): string {
37
+ const base = basePath();
38
+ if (!base) return path;
39
+ if (!path.startsWith('/') || path.startsWith('//')) return path;
40
+ if (path === '/') return base;
41
+ if (path === base || path.startsWith(`${base}/`)) return path;
42
+ return `${base}${path}`;
43
+ }
44
+
45
+ /** Strip the configured basePath prefix from an in-app path (inverse of addBasePath). */
46
+ export function stripBasePath(path: string): string {
47
+ const base = basePath();
48
+ if (!base) return path;
49
+ if (path === base) return '/';
50
+ if (path.startsWith(`${base}/`)) return path.slice(base.length);
51
+ return path;
52
+ }
53
+
54
+ /**
55
+ * True when `url` is same-origin but falls outside the configured basePath - i.e. the router must
56
+ * hard-navigate to it (a `basePath:false` target). Always false when no basePath is configured.
57
+ */
58
+ export function isOutsideBasePath(url: URL): boolean {
59
+ const base = basePath();
60
+ if (!base) return false;
61
+ if ((process.browser || typeof window !== 'undefined') && url.origin !== window.location.origin) return false;
62
+ const path = url.pathname;
63
+ return path !== base && !path.startsWith(`${base}/`);
64
+ }
@@ -0,0 +1,36 @@
1
+ // Next preserves the incoming document's stylesheet precedence on soft nav.
2
+ // Shared links otherwise retain their old position while newly-needed links
3
+ // append, which reverses ties in the CSS cascade after a route transition.
4
+ export function reconcileNextStylesheets(incoming: Document) {
5
+ const order = new Map<string, number>();
6
+ for (const link of incoming.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"][href]')) {
7
+ const href = absoluteHref(link);
8
+ if (href && nextStylesheet(href) && !order.has(href)) order.set(href, order.size);
9
+ }
10
+ if (order.size < 2) return;
11
+
12
+ const current = [...document.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"][href]')].filter(
13
+ link => order.has(link.href),
14
+ );
15
+ if (current.length < 2) return;
16
+
17
+ const rank = (link: HTMLLinkElement) => order.get(link.href) ?? 0;
18
+ if (current.every((link, index) => index === 0 || rank(current[index - 1]!) <= rank(link))) return;
19
+
20
+ const sorted = [...current].sort((left, right) => rank(left) - rank(right));
21
+ let previous = sorted[0]!;
22
+ for (const link of sorted.slice(1)) {
23
+ if (previous.nextSibling !== link) previous.after(link);
24
+ previous = link;
25
+ }
26
+ }
27
+
28
+ function absoluteHref(link: HTMLLinkElement) {
29
+ const href = link.getAttribute('href');
30
+ return href ? new URL(href, location.href).href : undefined;
31
+ }
32
+
33
+ function nextStylesheet(href: string) {
34
+ const url = new URL(href, location.href);
35
+ return url.origin === location.origin && url.pathname.startsWith('/assets/');
36
+ }
@@ -0,0 +1,92 @@
1
+ // Client-side control-flow error handling (COMPAT).
2
+ //
3
+ // notFound()/forbidden()/unauthorized()/redirect() called during a CLIENT-side render throw the same
4
+ // control-flow errors the server throws. React routes them to the nearest error boundary, but - exactly
5
+ // like Next - a boundary must NOT render its error UI for one: it must RE-THROW so the control error
6
+ // propagates. Client-side there is no server render pass to catch it, so this runtime intercepts the
7
+ // propagated error and drives the browser to the correct outcome:
8
+ //
9
+ // - notFound / forbidden / unauthorized: soft-navigate to the CURRENT url so the server re-renders
10
+ // it. On that render the same control-flow call fires server-side (deterministic for the same
11
+ // inputs) and the server resolves the nearest boundary with the right status.
12
+ // - redirect / permanentRedirect: navigate to the target (soft for same-origin, hard for
13
+ // cross-origin), matching redirect()'s browser path.
14
+ //
15
+ // Detection reuses the core control-flow predicates so the digest scheme stays single-sourced.
16
+
17
+ import {
18
+ isForbiddenError,
19
+ isNotFoundError,
20
+ isRedirectError,
21
+ isUnauthorizedError,
22
+ type PNextRedirectError,
23
+ } from '../../../api/navigation';
24
+ import { isPostpone } from '../../../ppr-postpone';
25
+ import { softNavigate } from '../../../api/router';
26
+
27
+ /** True for a Next control-flow error that a boundary must re-throw, not render. */
28
+ export function isControlFlowError(error: unknown): boolean {
29
+ return (
30
+ isRedirectError(error) ||
31
+ isNotFoundError(error) ||
32
+ isForbiddenError(error) ||
33
+ isUnauthorizedError(error) ||
34
+ isPostpone(error)
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Drive the browser to the outcome a client-side control-flow error demands.
40
+ * Returns true when the error was a control-flow error (and has been handled),
41
+ * false otherwise (a real error the caller must surface to a boundary).
42
+ */
43
+ export function handleControlFlowError(error: unknown): boolean {
44
+ if (isRedirectError(error)) {
45
+ applyRedirect(error);
46
+ return true;
47
+ }
48
+ if (isNotFoundError(error) || isForbiddenError(error) || isUnauthorizedError(error)) {
49
+ // Re-render the current URL server-side: the same control-flow call fires
50
+ // during that render and the server resolves the matching boundary + status.
51
+ // replace:true keeps history intact (the URL did not change); scroll:false
52
+ // avoids a jump. Fall back to a full reload if the soft-nav itself fails.
53
+ if (process.browser || typeof window !== 'undefined') {
54
+ void softNavigate(location.href, { replace: true, scroll: false }).catch(() => {
55
+ location.reload();
56
+ });
57
+ }
58
+ return true;
59
+ }
60
+ if (isPostpone(error)) {
61
+ // A postpone escaping to the client has nothing to resume against here; a
62
+ // reload re-renders from the server. Treated as handled so it never lands
63
+ // in an error boundary as a "real" error.
64
+ if (process.browser || typeof window !== 'undefined') location.reload();
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+
70
+ function applyRedirect(error: PNextRedirectError): void {
71
+ if (!process.browser && typeof window === 'undefined') return;
72
+ const target = error.location;
73
+ let sameOrigin = false;
74
+ try {
75
+ sameOrigin = new URL(target, location.href).origin === location.origin;
76
+ } catch {
77
+ sameOrigin = false;
78
+ }
79
+ const replace = error.status === 303 || error.status === 307 || error.status === 308;
80
+ if (sameOrigin) {
81
+ void softNavigate(target, { replace }).catch(() => {
82
+ hardNavigate(target, replace);
83
+ });
84
+ } else {
85
+ hardNavigate(target, replace);
86
+ }
87
+ }
88
+
89
+ function hardNavigate(target: string, replace: boolean): void {
90
+ if (replace) location.replace(target);
91
+ else location.assign(target);
92
+ }
@@ -0,0 +1,222 @@
1
+ // Client-side error boundary (COMPAT).
2
+ //
3
+ // A preact class error boundary wrapping a hydrated route/island subtree so a CLIENT-side throw
4
+ // post-hydration is caught and rendered by the route's `error.js` instead of tearing down the whole
5
+ // preact tree. Mirrors Next's boundary:
6
+ //
7
+ // - Control-flow errors are RE-THROWN, never rendered - the install layer's window handler drives
8
+ // them to the correct navigation outcome. A boundary that rendered UI for these would swallow the
9
+ // control flow.
10
+ // - Client-thrown errors have NO digest (only server errors carry one), so the error prop is a plain
11
+ // Error whose message is the throw's String(value); a thrown undefined/null/string passes through
12
+ // unwrapped, matching Next's client channel.
13
+ // - reset() clears the caught error and re-renders the children in place, recovering a
14
+ // client-component error whose failure lived in the hydrated subtree. unstable_retry() additionally
15
+ // soft-refreshes, which is what a data-dependent (server) failure needs.
16
+ //
17
+ // When no route error component is available the boundary mounts global-error.
18
+
19
+ import { Component, h, type ComponentChildren, type ComponentType } from 'preact';
20
+ import { isControlFlowError, handleControlFlowError } from './control-flow';
21
+ import { isNotFoundError } from '../../../api/navigation';
22
+ import { untagPrimitiveThrow } from './primitive-throw';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // KNOWN GAP (preact core, not fully fixable from an options hook): a CLIENT
26
+ // `throw undefined` / `throw null` can arrive here as a synthetic TypeError when
27
+ // preact dereferences `thrown.then` before boundary interception.
28
+ // Normalize that sentinel back to raw nullish values so error UIs render as
29
+ // `undefined`/`null`, matching Next behavior.
30
+ // ---------------------------------------------------------------------------
31
+
32
+ export type RouteErrorComponent = ComponentType<{
33
+ error: unknown;
34
+ reset: () => void;
35
+ unstable_retry?: () => void;
36
+ }>;
37
+ export type RouteNotFoundComponent = ComponentType<Record<string, never>>;
38
+
39
+ interface BoundaryProps {
40
+ /** The route's nearest error.js component; when absent the boundary escalates. */
41
+ errorComponent?: RouteErrorComponent;
42
+ notFoundComponent?: RouteNotFoundComponent;
43
+ /**
44
+ * Next's default 404 UI, handed in by the generated runtime - and emitted by it ONLY when some route
45
+ * can reach this fallback (no not-found.* of its own). An app whose boundary routes all have one
46
+ * never ships error-ui.ts.
47
+ */
48
+ builtInNotFound?: RouteNotFoundComponent;
49
+ /** Soft-refresh invoked by reset() after clearing the error. */
50
+ onReset?: () => void;
51
+ children?: unknown;
52
+ }
53
+
54
+ interface BoundaryState {
55
+ // Raw thrown value passed to the error component. A client `throw undefined`/
56
+ // `null`/a string reaches error.js unwrapped (Next passes the raw value on the
57
+ // client channel), so `hasError` — NOT truthiness of `error` — gates rendering:
58
+ // a falsy `undefined`/`null` throw is still a caught error, not "no error".
59
+ error: unknown;
60
+ hasError: boolean;
61
+ notFound: boolean;
62
+ }
63
+
64
+ /**
65
+ * Normalize a client throw into the Error the boundary component receives.
66
+ * Client-side non-Error throws pass through unwrapped (String(value)); no digest
67
+ * is ever attached (digests are a server-error-only signal).
68
+ */
69
+ export function toClientError(value: unknown): Error {
70
+ if (value instanceof Error) return value;
71
+ // `throw undefined` / `throw null` / `throw 'msg'` → message is String(value).
72
+ return new Error(String(value));
73
+ }
74
+
75
+ function normalizeBoundaryError(error: unknown): unknown {
76
+ // Preact-core interop tag (see primitive-throw.ts): a `throw undefined`/`null`
77
+ // arrives here re-thrown as a tagged Error so it survived preact's own
78
+ // `e.then` suspense check without crashing. Unwrap back to the raw value.
79
+ const untagged = untagPrimitiveThrow(error);
80
+ if (untagged !== error) return untagged;
81
+ if (error instanceof Error) {
82
+ // Fallback for any crash that slips through un-tagged (defense in depth).
83
+ if (error.message === "Cannot read properties of undefined (reading 'then')") return undefined;
84
+ if (error.message === "Cannot read properties of null (reading 'then')") return null;
85
+ }
86
+ return error;
87
+ }
88
+
89
+ // A boundary that catches (or escalates) a real error marks it here so the
90
+ // window-listener last resort (install.ts) knows the error was already handled
91
+ // by the nearest-boundary path and must NOT also drive it to global-error.
92
+ // WeakSet-keyed (only object throws are markable; a primitive throw that reaches
93
+ // the window is treated as unhandled, matching "the boundary couldn't hold it").
94
+ const handledErrors = new WeakSet<object>();
95
+
96
+ export function markErrorHandled(error: unknown): void {
97
+ if (error !== null && typeof error === 'object') handledErrors.add(error);
98
+ }
99
+
100
+ export function wasErrorHandled(error: unknown): boolean {
101
+ return error !== null && typeof error === 'object' && handledErrors.has(error);
102
+ }
103
+
104
+ export class ClientErrorBoundary extends Component<BoundaryProps, BoundaryState> {
105
+ state: BoundaryState = { error: null, hasError: false, notFound: false };
106
+
107
+ static getDerivedStateFromError(error: unknown): Partial<BoundaryState> {
108
+ if (isNotFoundError(error)) return { notFound: true, hasError: false, error: null };
109
+ // Control-flow errors must not be captured as state — let componentDidCatch
110
+ // re-throw so they propagate past this boundary.
111
+ if (isControlFlowError(error)) return {};
112
+ // Pass the RAW thrown value to error.js (no Error wrapping): a client
113
+ // `throw undefined`/`null`/'msg' renders as `${error}` = 'undefined'/'null'/'msg'.
114
+ return { error: normalizeBoundaryError(error), hasError: true };
115
+ }
116
+
117
+ componentDidCatch(error: unknown): void {
118
+ if (isNotFoundError(error)) {
119
+ markErrorHandled(error);
120
+ return;
121
+ }
122
+ if (isControlFlowError(error)) {
123
+ // Re-throw past this boundary: React/Next semantics for control errors.
124
+ // The install layer's window handler catches the propagated error and
125
+ // performs the navigation; we still throw so no error UI renders here.
126
+ handleControlFlowError(error);
127
+ throw error;
128
+ }
129
+ // A real error the boundary can render (it has a local error.js): mark it so the window-listener
130
+ // last resort ignores it. When there is NO errorComponent this boundary re-throws in render()
131
+ // instead of rendering anything - leave the error UNMARKED so an outer VDOM boundary can still
132
+ // catch it, and so the window-listener last resort recognizes a truly-uncaught error and mounts
133
+ // global-error.
134
+ if (this.props.errorComponent) markErrorHandled(error);
135
+ }
136
+
137
+ /**
138
+ * `reset()` re-renders the boundary's children in place - no server round trip. That is the whole
139
+ * difference from `unstable_retry()`, and it matters beyond the request count: a refresh swaps the
140
+ * body, so every node the reset subtree just rendered is replaced a moment later, and anything
141
+ * holding one of those nodes (a click in flight, a test's element handle) loses it.
142
+ */
143
+ reset = (): void => {
144
+ this.setState({ error: null, hasError: false, notFound: false });
145
+ };
146
+
147
+ /**
148
+ * Re-run the SERVER render, then re-render: recovers data-dependent failures. A CLIENT-component
149
+ * error carries no digest (digests are a server-only signal) - its failure lives in the hydrated
150
+ * tree, so re-rendering in place recovers it and a soft-refresh would only swap the body, detaching
151
+ * the very nodes a retry-then-interact loop reaches next. Only a server error's stale data needs the
152
+ * round trip.
153
+ */
154
+ unstable_retry = (): void => {
155
+ const digest = (this.state.error as { digest?: unknown } | null)?.digest;
156
+ this.setState({ error: null, hasError: false, notFound: false });
157
+ if (typeof digest === 'string' && digest.length > 0) this.props.onReset?.();
158
+ };
159
+
160
+ // A client-side notFound() that renders a LOCAL not-found.js has no server
161
+ // round trip, so the `<meta name="robots" content="noindex">` the server
162
+ // would emit for a not-found render is missing. Mirror it on the client:
163
+ // inject the tag while the local boundary is showing, remove it on recovery.
164
+ private addedNoindex = false;
165
+
166
+ private syncNotFoundRobots(): void {
167
+ if (typeof document === 'undefined') return;
168
+ const shouldShow = this.state.notFound && Boolean(this.props.notFoundComponent);
169
+ if (shouldShow && !this.addedNoindex) {
170
+ // Don't stomp an existing robots tag (SSR/metadata already set one).
171
+ if (!document.querySelector('meta[name="robots"]')) {
172
+ const meta = document.createElement('meta');
173
+ meta.setAttribute('name', 'robots');
174
+ meta.setAttribute('content', 'noindex');
175
+ meta.setAttribute('data-pnext-notfound-robots', '');
176
+ document.head.appendChild(meta);
177
+ this.addedNoindex = true;
178
+ }
179
+ } else if (!shouldShow && this.addedNoindex) {
180
+ document.querySelector('meta[name="robots"][data-pnext-notfound-robots]')?.remove();
181
+ this.addedNoindex = false;
182
+ }
183
+ }
184
+
185
+ componentDidMount(): void {
186
+ this.syncNotFoundRobots();
187
+ }
188
+
189
+ componentDidUpdate(): void {
190
+ this.syncNotFoundRobots();
191
+ }
192
+
193
+ componentWillUnmount(): void {
194
+ if (this.addedNoindex) {
195
+ document.querySelector('meta[name="robots"][data-pnext-notfound-robots]')?.remove();
196
+ this.addedNoindex = false;
197
+ }
198
+ }
199
+
200
+ render(): ComponentChildren {
201
+ if (this.state.notFound) {
202
+ const NotFoundComponent = this.props.notFoundComponent ?? this.props.builtInNotFound;
203
+ return NotFoundComponent ? h(NotFoundComponent, {}) : null;
204
+ }
205
+ if (!this.state.hasError) return this.props.children;
206
+ const { error } = this.state;
207
+ const ErrorComponent = this.props.errorComponent;
208
+ if (!ErrorComponent) {
209
+ // Preserve primitive throws for global-error.js directly (window `error`
210
+ // loses raw null/undefined/string). Bubble Error objects to the next outer
211
+ // ClientErrorBoundary so nearest in-tree boundary semantics stay intact.
212
+ if (error === null || typeof error !== 'object') {
213
+ // global-error rides the deferred tier (see ./lazy) — a primitive throw
214
+ // with no error.js is the one path that reaches it from a render.
215
+ void import('./global-error').then(m => m.escalateToGlobalError(error));
216
+ return null;
217
+ }
218
+ throw error as Error;
219
+ }
220
+ return h(ErrorComponent, { error, reset: this.reset, unstable_retry: this.unstable_retry });
221
+ }
222
+ }