@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa

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 (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,228 @@
1
+ # State and Cache Control Hooks
2
+
3
+ ## State Hooks
4
+
5
+ ### useLocationState()
6
+
7
+ Read type-safe state from history:
8
+
9
+ ```tsx
10
+ "use client";
11
+ import { useLocationState, createLocationState } from "@rangojs/router/client";
12
+
13
+ // Define typed state (all export patterns supported)
14
+ // Keys are auto-injected by the Vite plugin -- no manual key needed.
15
+ export const ProductState = createLocationState<{
16
+ name: string;
17
+ price: number;
18
+ }>();
19
+
20
+ // Also valid: const ProductState = createLocationState<...>();
21
+ // export { ProductState };
22
+ // Also valid: export { ProductState as MyState };
23
+
24
+ function ProductHeader() {
25
+ const state = useLocationState(ProductState);
26
+ // { name: string; price: number } | undefined
27
+
28
+ if (state) {
29
+ return (
30
+ <h1>
31
+ {state.name} - ${state.price}
32
+ </h1>
33
+ );
34
+ }
35
+ return <h1>Loading...</h1>;
36
+ }
37
+ ```
38
+
39
+ Pass state through Link:
40
+
41
+ ```tsx
42
+ import { Link } from "@rangojs/router/client";
43
+ import { ProductState } from "./state";
44
+
45
+ <Link to="/product/123" state={[ProductState({ name: "Widget", price: 99 })]}>
46
+ View Product
47
+ </Link>;
48
+ ```
49
+
50
+ Pass typed state just in time (getter evaluated at click time, not render time):
51
+
52
+ ```tsx
53
+ "use client"; // JIT state requires a client component (getter can't cross RSC boundary)
54
+
55
+ import { Link } from "@rangojs/router/client";
56
+ import { ProductState } from "./state";
57
+
58
+ // The getter is stored lazily and only called when the user clicks the link.
59
+ // This is useful for capturing values that change after render (e.g., scroll
60
+ // position, form state, ref values).
61
+ <Link
62
+ to="/product/123"
63
+ state={[ProductState(() => ({ name: product.name, price: product.price }))]}
64
+ >
65
+ View Product
66
+ </Link>;
67
+ ```
68
+
69
+ Plain state can also be evaluated just in time (also requires a client component):
70
+
71
+ ```tsx
72
+ <Link to="/product/123" state={() => ({ from: window.location.pathname })}>
73
+ View Product
74
+ </Link>
75
+ ```
76
+
77
+ ### Flash State (read-once)
78
+
79
+ Create a location state with `{ flash: true }` for read-once state that
80
+ auto-clears after first render. Ideal for flash messages (success/error
81
+ notifications after redirect):
82
+
83
+ ```tsx
84
+ // location-states.ts
85
+ import { createLocationState } from "@rangojs/router";
86
+
87
+ export const FlashMessage = createLocationState<{ text: string }>({
88
+ flash: true,
89
+ });
90
+ ```
91
+
92
+ Read flash state with `useLocationState` (same hook as persistent state):
93
+
94
+ ```tsx
95
+ "use client";
96
+ import { useLocationState } from "@rangojs/router/client";
97
+ import { FlashMessage } from "../location-states";
98
+
99
+ function FlashBanner() {
100
+ const flash = useLocationState(FlashMessage);
101
+ // { text: string } | undefined
102
+
103
+ if (!flash) return null;
104
+ return <div className="flash">{flash.text}</div>;
105
+ }
106
+ ```
107
+
108
+ Flash behavior is determined by the definition (`{ flash: true }`), not by which
109
+ hook reads it. `useLocationState` reads the value synchronously during render,
110
+ then clears it from `history.state` via `replaceState` in a `useEffect`.
111
+ Multiple components reading the same flash definition all see the value.
112
+ Pressing back/forward will not re-show the flash since it was cleared.
113
+
114
+ Set flash state from the server via `redirect()` with state:
115
+
116
+ ```tsx
117
+ // In a route handler
118
+ import { redirect, createLocationState } from "@rangojs/router";
119
+
120
+ export const FlashMessage = createLocationState<{ text: string }>({
121
+ flash: true,
122
+ });
123
+
124
+ // Handler
125
+ (ctx) => {
126
+ return redirect("/dashboard", {
127
+ state: [FlashMessage({ text: "Item saved!" })],
128
+ });
129
+ };
130
+ ```
131
+
132
+ Or via `ctx.setLocationState()` on any response:
133
+
134
+ ```tsx
135
+ (ctx) => {
136
+ ctx.setLocationState(FlashMessage({ text: "Welcome back!" }));
137
+ return <Dashboard />;
138
+ };
139
+ ```
140
+
141
+ ### .read() (non-hook access)
142
+
143
+ Read current location state outside React components (client-side only):
144
+
145
+ ```tsx
146
+ import { FlashMessage, ProductState } from "../location-states";
147
+
148
+ // Returns TState | undefined. Returns undefined during SSR.
149
+ const flash = FlashMessage.read();
150
+ const product = ProductState.read();
151
+ ```
152
+
153
+ > **Hydration:** `.read()` returns `undefined` on the server but may return
154
+ > a real value on the first client render (history state survives reload).
155
+ > Do not call `.read()` directly during the initial render of a component;
156
+ > call it from an event handler or inside a `useEffect` post-mount. For
157
+ > reactive hydration-safe access, use `useLocationState()` instead.
158
+
159
+ ### .write() / .delete() (static, non-reactive)
160
+
161
+ Static counterparts to `.read()`. Both mutate the current history entry's
162
+ `history.state` via `replaceState`, preserving any other keys (router
163
+ bookkeeping, other location state slots). Both are client-only; they throw
164
+ when called on the server.
165
+
166
+ Neither dispatches an event, so components reading via `useLocationState`
167
+ will NOT re-render until the next navigation/popstate. Pair with `.read()`
168
+ (or a fresh mount via back/forward/reload) instead.
169
+
170
+ ```tsx
171
+ "use client";
172
+ import { ProductState } from "./state";
173
+
174
+ // Persisted across hard refresh and back/forward of this entry.
175
+ ProductState.write({ name: "Widget", price: 9.99 });
176
+
177
+ // Read later (or on next mount).
178
+ const current = ProductState.read();
179
+
180
+ // Manually clear the slot. Idempotent if it isn't set.
181
+ ProductState.delete();
182
+ ```
183
+
184
+ | Method | Updates `history.state` | Fires `useLocationState` rerender | SSR behavior |
185
+ | ----------- | ----------------------- | --------------------------------- | ------------------- |
186
+ | `.read()` | no | n/a (returns snapshot) | returns `undefined` |
187
+ | `.write()` | yes (replace this slot) | no | throws |
188
+ | `.delete()` | yes (remove this slot) | no | throws |
189
+
190
+ ## Cache Control
191
+
192
+ ### invalidateClientCache()
193
+
194
+ Force the client's caches to miss after a mutation the router can't see (a REST
195
+ call, a WebSocket push, a login). It is a plain function, not a hook, so it works
196
+ from module-level callbacks too. Imported from the root entry `@rangojs/router`,
197
+ it is selected by export conditions: in a client component it marks the caches
198
+ stale immediately; from a handler/server component it writes a rotated
199
+ `Set-Cookie` for the responding client.
200
+
201
+ ```tsx
202
+ "use client";
203
+ import { invalidateClientCache } from "@rangojs/router";
204
+
205
+ function SaveButton() {
206
+ const handleSave = async () => {
207
+ await fetch("/api/data", {
208
+ method: "POST",
209
+ body: JSON.stringify(data),
210
+ });
211
+
212
+ // Invalidate the client's caches after the mutation
213
+ invalidateClientCache();
214
+ };
215
+
216
+ return <button onClick={handleSave}>Save</button>;
217
+ }
218
+ ```
219
+
220
+ A module-level subscription works the same way (no component needed):
221
+
222
+ ```ts
223
+ import { invalidateClientCache } from "@rangojs/router";
224
+
225
+ socket.on("catalog-updated", () => invalidateClientCache());
226
+ ```
227
+
228
+ **Use cases**: REST API mutations, WebSocket updates, non-RSC data changes.
@@ -0,0 +1,135 @@
1
+ # URL Hooks
2
+
3
+ ### useParams()
4
+
5
+ Access route params from the current URL:
6
+
7
+ ```tsx
8
+ "use client";
9
+ import { useParams } from "@rangojs/router/client";
10
+
11
+ // Route: /product/:productId
12
+ function ProductPage() {
13
+ const params = useParams();
14
+ // { productId: "123" }
15
+
16
+ return <h1>Product {params.productId}</h1>;
17
+ }
18
+
19
+ // Annotate the expected shape via a generic
20
+ function ProductPageTyped() {
21
+ const { productId } = useParams<{ productId: string }>();
22
+ return <h1>Product {productId}</h1>;
23
+ }
24
+
25
+ // With selector for performance (re-renders only when selected value changes)
26
+ function ProductId() {
27
+ const productId = useParams((p) => p.productId);
28
+ return <span>ID: {productId}</span>;
29
+ }
30
+ ```
31
+
32
+ Returns merged params from all matched route segments as a `Readonly<T>` map. Updates on navigation commit (not during pending navigation).
33
+
34
+ ### usePathname()
35
+
36
+ Access the current URL pathname:
37
+
38
+ ```tsx
39
+ "use client";
40
+ import { usePathname } from "@rangojs/router/client";
41
+
42
+ function CurrentPage() {
43
+ const pathname = usePathname();
44
+ // "/product/123" (no search params)
45
+
46
+ return <span>Current path: {pathname}</span>;
47
+ }
48
+ ```
49
+
50
+ Returns the pathname string without search params or hash. Updates on navigation commit.
51
+
52
+ ### useSearchParams()
53
+
54
+ Access the current URL search params:
55
+
56
+ ```tsx
57
+ "use client";
58
+ import { useSearchParams } from "@rangojs/router/client";
59
+
60
+ function SearchResults() {
61
+ const searchParams = useSearchParams();
62
+ const query = searchParams.get("q"); // "react"
63
+ const page = searchParams.get("page"); // "2"
64
+
65
+ return (
66
+ <div>
67
+ Searching for: {query}, page {page}
68
+ </div>
69
+ );
70
+ }
71
+ ```
72
+
73
+ Returns a `ReadonlyURLSearchParams` (URLSearchParams without mutation methods). During SSR, returns empty params and syncs from the browser URL on mount.
74
+
75
+ ### useHref()
76
+
77
+ Mount-aware href for client components inside `include()` scopes:
78
+
79
+ ```tsx
80
+ "use client";
81
+ import { useHref, href, Link } from "@rangojs/router/client";
82
+
83
+ // Inside include("/shop", shopPatterns)
84
+ function ShopNav() {
85
+ const href = useHref();
86
+
87
+ return (
88
+ <>
89
+ {/* Local paths - auto-prefixed with /shop */}
90
+ <Link to={href("/cart")}>Cart</Link>
91
+ <Link to={href("/product/widget")}>Widget</Link>
92
+ </>
93
+ );
94
+ }
95
+ ```
96
+
97
+ Use `useHref()` for local navigation. Use the bare `href()` function for absolute paths.
98
+
99
+ ### useMount()
100
+
101
+ Returns the current `include()` mount path:
102
+
103
+ ```tsx
104
+ "use client";
105
+ import { useMount } from "@rangojs/router/client";
106
+
107
+ function MountInfo() {
108
+ const mount = useMount(); // "/shop" inside include("/shop", ...)
109
+ return <span>Mounted at: {mount}</span>;
110
+ }
111
+ ```
112
+
113
+ ### useReverse(routes)
114
+
115
+ Mount-aware local reverse for client components. Import the generated `routes` map from a `urls()` module's `.gen.ts` and call `reverse("name", params?)` — the leading dot is optional. Auto-fills params from `useParams()`; explicit params override.
116
+
117
+ > Per-module `*.gen.ts` files are **CLI opt-in and not Vite-watched** — run `rango generate <urls-file>` (or wire it into `predev`) and re-run it whenever the module's routes change. See `/links` for the full generated-file setup and exposure-boundary rules.
118
+
119
+ ```tsx
120
+ "use client";
121
+ import { Link, useReverse } from "@rangojs/router/client";
122
+ import { routes as blogRoutes } from "../urls/blog.gen.js";
123
+
124
+ function BlogNav() {
125
+ const reverse = useReverse(blogRoutes);
126
+ return (
127
+ <nav>
128
+ <Link to={reverse("index")}>Blog</Link>
129
+ <Link to={reverse("post", { postId: "hello" })}>Post</Link>
130
+ </nav>
131
+ );
132
+ }
133
+ ```
134
+
135
+ See `/links` for the full URL generation guide. `ctx.reverse()` is server-only; on the client, prefer `useReverse(routes)` for in-module names and pass URLs as props for cross-module ones.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: host-router
3
- description: Multi-app host routing with domain/subdomain patterns
3
+ description: Multi-app host routing with domain/subdomain patterns. Use when running multiple apps behind one domain or across subdomains, or routing requests to different apps based on hostname.
4
4
  argument-hint:
5
5
  ---
6
6
 
@@ -33,6 +33,48 @@ export default {
33
33
  };
34
34
  ```
35
35
 
36
+ ## Deploying: Cloudflare vs node/vercel
37
+
38
+ How a host router is _served_ depends on the preset, because the preset decides who owns the server entry.
39
+
40
+ | Preset | Who owns the entry | What the host module exports |
41
+ | ----------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------- |
42
+ | `cloudflare` | You (your `worker.rsc.tsx`) | `export default { fetch(request, env, ctx) { return router.match(request, { env, ctx }); } }` |
43
+ | `node` / `vercel` | rango (generated RSC entry) | `export default router;` (the `HostRouter` instance itself), or a named `export const hostRouter`/`router`. |
44
+
45
+ On `node`/`vercel`, rango generates the served RSC entry, so it needs the `HostRouter` **instance** to call `hostRouter.match()` for you. Export the instance, not a `{ fetch }` object:
46
+
47
+ ```typescript
48
+ // src/worker.rsc.tsx (node / vercel)
49
+ import { createHostRouter } from "@rangojs/router/host";
50
+
51
+ export const hostRouter = createHostRouter();
52
+ hostRouter.host(["admin.*"]).lazy(() => import("./apps/admin/handler.js"));
53
+ hostRouter.host(["."]).lazy(() => import("./apps/site/handler.js"));
54
+
55
+ // Export the instance — the generated entry serves it via hostRouter.match().
56
+ export default hostRouter;
57
+ ```
58
+
59
+ Each sub-app exports a handler exactly as on Cloudflare (no change):
60
+
61
+ ```typescript
62
+ // src/apps/admin/handler.ts
63
+ import { router } from "./router.js";
64
+ export default (request: Request, input: any) => router.fetch(request, input);
65
+ ```
66
+
67
+ Selecting the host entry — a host app has several `createRouter()` sub-apps, so single-router auto-discovery can't pick one. Either let rango auto-detect the lone `createHostRouter()` file, or point at it explicitly:
68
+
69
+ ```typescript
70
+ // vite.config.ts
71
+ rango({ preset: "vercel", hostRouter: "./src/worker.rsc.tsx" });
72
+ ```
73
+
74
+ On Vercel this is a single function running `hostRouter.match()` for every request (mirrors the Cloudflare single-worker model); `{ env, ctx }` (`process.env` + `{ waitUntil }`) is threaded unchanged to each matched sub-app's handler and `cache(env, ctx)` factory. See the `vercel` skill.
75
+
76
+ Unmatched hosts on node/vercel: because rango owns the generated entry (you have no worker `try/catch`), it catches `NoRouteMatchError` and returns **404** by default — so you do **not** need a catch-all host route. If you want different behavior (a branded 404, a redirect, a default app), register a catch-all mount as the **last** route, e.g. `host(["**"]).lazy(() => import("./apps/site/handler.js"))` — it matches any host, so the built-in 404 only fires when nothing matched at all. (Note `fallback()` is for cookie-override errors, not general unmatched hosts.)
77
+
36
78
  ## Inline handlers (`.map`) vs lazy mounts (`.lazy`)
37
79
 
38
80
  A host pattern maps to one of two things, and you pick the method by intent:
@@ -66,7 +108,7 @@ Why two methods instead of one overloaded `.map()`:
66
108
  | `.` or `*` | Any apex domain (`example.com`) |
67
109
  | `**` | Any domain (apex + all subdomains) |
68
110
  | `*.` | Any single-level subdomain (`www.example.com`) |
69
- | `**. ` | Any multi-level subdomain (`a.b.example.com`) |
111
+ | `**.` | Any multi-level subdomain (`a.b.example.com`) |
70
112
  | `example.com` | Exact domain |
71
113
  | `*.com` | Any apex `.com` domain |
72
114
  | `*.example.com` | Single subdomain of `example.com` |
@@ -134,17 +176,17 @@ router.fallback().map((request) => {
134
176
  });
135
177
  ```
136
178
 
137
- For unmatched hosts without `hostOverride`, catch `NoRouteMatchError` in your worker fetch:
179
+ For unmatched hosts without `hostOverride`, catch `NoRouteMatchError` in your worker fetch. Use the `isNoRouteMatchError()` guard rather than a bare `instanceof`: a workspace with a duplicated `@rangojs/router` copy can throw the error with a different class identity, and `instanceof` would then turn the 404 into an opaque 500.
138
180
 
139
181
  ```typescript
140
- import { NoRouteMatchError } from "@rangojs/router/host";
182
+ import { isNoRouteMatchError } from "@rangojs/router/host";
141
183
 
142
184
  export default {
143
185
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
144
186
  try {
145
187
  return await router.match(request, { env, ctx });
146
188
  } catch (err) {
147
- if (err instanceof NoRouteMatchError) {
189
+ if (isNoRouteMatchError(err)) {
148
190
  return new Response("Not Found", { status: 404 });
149
191
  }
150
192
  throw err;
@@ -189,12 +231,26 @@ Logs pattern matching, route registration, and cookie override decisions to cons
189
231
  ## Testing
190
232
 
191
233
  ```typescript
192
- import { createTestRequest, testPattern } from "@rangojs/router/host/testing";
234
+ import {
235
+ createTestRequest,
236
+ testPattern,
237
+ matchesHost,
238
+ } from "@rangojs/router/host/testing";
193
239
 
194
- // Test pattern matching
240
+ // Test pattern matching (host-only)
195
241
  testPattern("admin.*", "admin.example.com"); // true
196
242
  testPattern([".", "www.*"], "example.com"); // true
197
243
 
244
+ // Path-based patterns need the third pathname arg (defaults to "/", so a
245
+ // host-only pattern still works with two args):
246
+ testPattern("**.workers.dev/admin", "foo.workers.dev", "/admin"); // true
247
+
248
+ // Or match a pattern against a real Request (hostname + pathname from the URL):
249
+ matchesHost(
250
+ "**.workers.dev/admin",
251
+ new Request("https://foo.workers.dev/admin"),
252
+ ); // true
253
+
198
254
  // Create requests for integration tests
199
255
  const request = createTestRequest({
200
256
  host: "admin.example.com",
@@ -241,3 +297,24 @@ export default regional;
241
297
  // host-router.ts
242
298
  router.host(["**.regional.example.com"]).lazy(() => import("./apps/regional"));
243
299
  ```
300
+
301
+ ## Cross-app navigation is a full document load
302
+
303
+ A client-side navigation that crosses an app boundary (e.g. a `<Link>` or
304
+ intercepted `<a>` from the app at `/` into an app mounted at `/shop`) is a **hard
305
+ document navigation**, not a soft in-tree swap. When the server sees a partial
306
+ (SPA) request whose router id doesn't match the matched app, it returns
307
+ `X-RSC-Reload` and the client does a real document navigation to the target.
308
+
309
+ Why a reload rather than a soft swap: a soft swap can't faithfully re-establish
310
+ the target app's **document-level** state. Stylesheets shared across apps are
311
+ dropped by React 19's by-`href` resource dedup; and theme, warmup, and
312
+ prefetch-TTL are document-lifetime (captured once at load — see
313
+ `browser/app-shell.ts`), so the target app's config would never take effect. A
314
+ full document load re-establishes the target app's entire document — CSS, theme,
315
+ meta, everything — by construction. So you do **not** need to coordinate
316
+ stylesheet `href`s, `precedence`, theme config, etc. across independently-authored
317
+ apps; each app owns its own document.
318
+
319
+ **Within-app** navigation is unchanged — a normal soft SPA update (the document
320
+ stays mounted). Only crossing an app boundary triggers the reload.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: i18n
3
- description: Locale-aware routing with `include("/:locale?", ...)`, locale resolution chains, and react-intl integration
3
+ description: Locale-aware routing with `include("/:locale?", ...)`, locale resolution chains, and react-intl integration. Use when building a multi-language app, routes need a locale segment, or wiring up react-intl translations.
4
4
  argument-hint: "[topic]"
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: intercept
3
- description: Define intercept routes for modals, slide-overs, and soft navigation patterns in @rangojs/router
3
+ description: Define intercept routes for modals, slide-overs, and soft navigation patterns in @rangojs/router. Use when opening a route as a modal/overlay on top of the current page while keeping the URL shareable, or asking "how do I show this page in a modal".
4
4
  argument-hint: [@slot-name] [route-to-intercept]
5
5
  ---
6
6
 
@@ -8,6 +8,13 @@ argument-hint: [@slot-name] [route-to-intercept]
8
8
 
9
9
  Intercept routes render a different component during soft navigation (client-side) while preserving the background route. Hard navigation (direct URL) shows the full page.
10
10
 
11
+ ## Not this skill if…
12
+
13
+ - You want a slot that ALWAYS renders alongside the page (sidebar, multi-column
14
+ layout) — that is a permanent `parallel()` slot: see `/parallel`.
15
+ - You want the same component regardless of soft vs hard navigation —
16
+ intercepts only swap on soft navigation; see `/parallel`.
17
+
11
18
  ## Basic Intercept
12
19
 
13
20
  ```typescript
@@ -23,7 +30,7 @@ function ShopLayout() {
23
30
  );
24
31
  }
25
32
 
26
- export const urlpatterns = urls(({ path, layout, intercept, loader }) => [
33
+ export const urlpatterns = urls(({ path, layout, intercept, loader, loading }) => [
27
34
  layout(<ShopLayout />, () => [
28
35
  // Intercept product detail - shows modal during soft navigation
29
36
  intercept(
@@ -107,8 +114,10 @@ Use named revalidation contracts on both the outer producer and the intercept
107
114
  consumer when they share `ctx.set()` data:
108
115
 
109
116
  ```typescript
110
- export const revalidateProductShell = ({ actionId }) =>
111
- actionId?.includes("src/actions/product.ts#") || undefined;
117
+ import * as ProductActions from "./actions/product";
118
+
119
+ export const revalidateProductShell = (ctx) =>
120
+ ctx.isAction(ProductActions) || undefined;
112
121
 
113
122
  layout(ProductLayout, () => [
114
123
  revalidate(revalidateProductShell), // producer reruns
@@ -140,18 +149,41 @@ layout(ProductLayout, () => [
140
149
  ]);
141
150
  ```
142
151
 
143
- ## Conditional Intercept with when()
152
+ ## Conditional Intercept with the `when` config
144
153
 
145
- Only intercept based on navigation context:
154
+ Only intercept based on navigation context. `when` is the 4th argument
155
+ (an `InterceptConfig` object); the other use-items go in the 5th-argument
156
+ callback.
146
157
 
147
158
  ```typescript
148
159
  intercept(
149
160
  "@modal",
150
161
  "product",
151
162
  <ProductModal />,
163
+ // Only intercept when coming from a different section
164
+ { when: ({ from }) => !from.pathname.startsWith("/shop/product/") },
165
+ () => [
166
+ loader(ProductLoader),
167
+ ]
168
+ )
169
+ ```
170
+
171
+ `when` is a match-time selector receiving `{ from, to, params, segments, ... }`.
172
+ Pass an array of predicates for AND logic (all must return true). Omit `when`
173
+ entirely and the intercept always activates.
174
+
175
+ ```typescript
176
+ intercept(
177
+ "@modal",
178
+ "product",
179
+ <ProductModal />,
180
+ {
181
+ when: [
182
+ ({ from }) => from.pathname.startsWith("/shop"),
183
+ ({ params }) => params.slug !== "featured",
184
+ ],
185
+ },
152
186
  () => [
153
- // Only intercept when coming from a different section
154
- when(({ from }) => !from.pathname.startsWith("/shop/product/")),
155
187
  loader(ProductLoader),
156
188
  ]
157
189
  )
@@ -240,10 +272,13 @@ layout(ShopLayout, () => [
240
272
  ]),
241
273
 
242
274
  // This intercept is also pre-rendered at build time
243
- intercept("@modal", ".detail", <ProductModal />, () => [
244
- when(({ from }) => from.pathname.startsWith("/shop")),
245
- loader(ProductLoader),
246
- ]),
275
+ intercept(
276
+ "@modal",
277
+ ".detail",
278
+ <ProductModal />,
279
+ { when: ({ from }) => from.pathname.startsWith("/shop") },
280
+ () => [loader(ProductLoader)],
281
+ ),
247
282
  ])
248
283
  ```
249
284
 
@@ -251,8 +286,8 @@ Build-time behavior:
251
286
 
252
287
  - The intercept handler (`<ProductModal />`) is resolved with BuildContext
253
288
  - Result is stored under the key `"detail/paramHash/i"` (intercept variant)
254
- - `when()` conditions are skipped at build time (all intercepts pre-rendered unconditionally)
255
- - `when()` is still evaluated at runtime by the intercept-resolution middleware
289
+ - `when` config conditions are skipped at build time (all intercepts pre-rendered unconditionally)
290
+ - `when` is still evaluated at runtime by the intercept-resolution middleware
256
291
 
257
292
  Runtime behavior:
258
293
 
@@ -303,7 +338,6 @@ export const shopPatterns = urls(({
303
338
  intercept,
304
339
  loader,
305
340
  loading,
306
- when,
307
341
  }) => [
308
342
  layout(<ShopLayout />, () => [
309
343
  parallel({
@@ -315,8 +349,8 @@ export const shopPatterns = urls(({
315
349
  "@modal",
316
350
  "product", // Route name (without prefix)
317
351
  <ProductModalContent />,
352
+ { when: ({ from }) => !from.pathname.startsWith("/shop/product/") },
318
353
  () => [
319
- when(({ from }) => !from.pathname.startsWith("/shop/product/")),
320
354
  layout(<ModalWrapper />),
321
355
  loading(<ProductModalSkeleton />),
322
356
  loader(ProductLoader, () => [cache()]),
@@ -336,7 +370,7 @@ export const shopPatterns = urls(({
336
370
 
337
371
  ## Handler-attached `.use`
338
372
 
339
- Intercept handlers can carry their own middleware, loaders, loading state, error/notFound boundaries, and even nested `layout`/`route`/`when` defaults via `.use` — useful for self-contained modal components that travel with their own data and chrome.
373
+ Intercept handlers can carry their own middleware, loaders, loading state, error/notFound boundaries, and even nested `layout`/`route` defaults via `.use` — useful for self-contained modal components that travel with their own data and chrome. (Conditional activation is set via the `when` config on the mount-site `intercept()` call, not inside `.use`.)
340
374
 
341
375
  ```typescript
342
376
  const QuickViewModal: Handler = async (ctx) => {