@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -1,16 +1,25 @@
1
1
  ---
2
2
  name: links
3
- description: URL generation with ctx.reverse (server), href (client), useHref (mounted), useMount, and scopedReverse
4
- argument-hint: [href|useHref|useMount|scopedReverse]
3
+ description: URL generation with ctx.reverse (server default), href (client), useHref (mounted), useMount, useReverse, and scopedReverse
4
+ argument-hint: [ctx.reverse|href|useHref|useMount|useReverse|scopedReverse]
5
5
  ---
6
6
 
7
7
  # Links & URL Generation
8
8
 
9
9
  @rangojs/router provides different href APIs for server and client contexts.
10
10
 
11
+ **Default server API: `ctx.reverse()`.** Generate URLs from the handler context — it's typed, auto-fills mount params, and resolves local (`.name`) and absolute (`name.sub`) names.
12
+
13
+ **On the client, two patterns:**
14
+
15
+ 1. **Receive URLs as props / loader data / action return.** The default. The server has the full route manifest and handler context — generate URLs there and hand strings to client components.
16
+ 2. **`useReverse(routes)`.** Import a generated `routes` map from a `urls()` module's `.gen.ts` and call `reverse(".name", params?)`. Mount-aware via `useMount()`, auto-fills params from `useParams()`, fully typed from the imported map. Use this when a client component needs to generate URLs into a known module without round-tripping through the server.
17
+
18
+ `ctx.reverse()` itself is **server-only** — it depends on the full route manifest and handler context. Client components never import or call it.
19
+
11
20
  ## Server: ctx.reverse()
12
21
 
13
- Available in route handlers via HandlerContext. Resolves named routes using the full route map.
22
+ Available in route handlers via HandlerContext. Resolves named routes using the full route map. This is the default way to generate URLs on the server.
14
23
 
15
24
  ```typescript
16
25
  import { urls, scopedReverse } from "@rangojs/router";
@@ -24,27 +33,86 @@ export const shopPatterns = urls(({ path, layout }) => [
24
33
  ]);
25
34
  ```
26
35
 
27
- ### Resolution priority
36
+ ### Resolution rules
28
37
 
29
- 1. **Path-based** (`/...`) - returned as-is
30
- 2. **Absolute name** (contains dot: `blog.post`) - global lookup
31
- 3. **Local name** (`cart`) - resolved relative to current route's namespace
38
+ - **`.name`** — local route, resolved within the current `include()` scope
39
+ - **`name`** global route, from the named-routes definition
32
40
 
33
41
  ```typescript
34
42
  // Inside a handler within shopPatterns (mounted at /shop)
35
43
  path("/product/:slug", (ctx) => {
36
- ctx.reverse("cart"); // "/shop/cart" (local)
37
- ctx.reverse("product", { slug: "widget" }); // "/shop/product/widget" (local + params)
38
- ctx.reverse("blog.post", { slug: "hi" }); // "/blog/hi" (absolute)
39
- ctx.reverse("/about"); // "/about" (path-based)
44
+ ctx.reverse(".cart"); // "/shop/cart" (local)
45
+ ctx.reverse(".product", { slug: "widget" }); // "/shop/product/widget" (local + params)
46
+ ctx.reverse("blog.post", { slug: "hi" }); // "/blog/hi" (global)
40
47
 
41
48
  return <ProductPage slug={ctx.params.slug} />;
42
49
  }, { name: "product" })
43
50
  ```
44
51
 
52
+ ### Local names (dot-prefixed)
53
+
54
+ Prefix a name with `.` to resolve it within the current `include()` scope. The route is looked up using the include's mount namespace.
55
+
56
+ ```typescript
57
+ // urls/magazine.tsx — mounted at include("/magazine", magazinePatterns, { name: "magazine" })
58
+ (ctx) => {
59
+ ctx.reverse(".article", { slug: "design" }); // "/magazine/design"
60
+ ctx.reverse(".author.posts", { authorSlug: "alice" }); // "/magazine/author/alice/posts"
61
+ ctx.reverse(".index"); // "/magazine"
62
+ ctx.reverse(".blog.index"); // THROWS — no magazine.blog.index
63
+ };
64
+ ```
65
+
66
+ ### Auto-fill of mount params
67
+
68
+ When routes are mounted via a parameterized `include()`, `ctx.reverse()` automatically fills mount params from `ctx.params`. Inner handlers don't need to pass params that are already known from the current URL match. Explicitly passed params override auto-filled values.
69
+
70
+ ```typescript
71
+ // urls/tenant.tsx — mounted at include("/tenant/:tenantId", tenantPatterns, { name: "tenant" })
72
+ export const tenantPatterns = urls(({ path }) => [
73
+ path("/", (ctx) => {
74
+ // tenantId is auto-filled from ctx.params — no need to pass it
75
+ ctx.reverse(".settings"); // "/tenant/acme/settings" (when visiting /tenant/acme)
76
+ ctx.reverse(".user", { userId: "u1" }); // "/tenant/acme/users/u1" (tenantId auto-filled, userId explicit)
77
+ ctx.reverse(".settings", { tenantId: "other" }); // "/tenant/other/settings" (explicit override)
78
+
79
+ // Global names also get auto-filled params
80
+ ctx.reverse("tenant.settings"); // "/tenant/acme/settings"
81
+ return <TenantIndex />;
82
+ }, { name: "index" }),
83
+ path("/settings", SettingsPage, { name: "settings" }),
84
+ path("/users/:userId", UserPage, { name: "user" }),
85
+ ]);
86
+ ```
87
+
88
+ Auto-fill uses `{ ...ctx.params, ...hrefParams }` — current request params are defaults, explicit params win. Params not needed by the target route are silently ignored.
89
+
90
+ ### Global names (unprefixed)
91
+
92
+ Unprefixed names resolve against the full named-routes map (the generated `router.named-routes.gen.ts`).
93
+
94
+ ```typescript
95
+ (ctx) => {
96
+ ctx.reverse("magazine.index"); // "/magazine"
97
+ ctx.reverse("blog.post", { slug: "hello" }); // "/blog/hello"
98
+ };
99
+ ```
100
+
101
+ ### reverse with search params
102
+
103
+ When a route has a `search` schema, pass a typed search object as the third argument:
104
+
105
+ ```typescript
106
+ path("/search", (ctx) => {
107
+ // Generates: /search?q=react&page=2
108
+ const url = ctx.reverse("search", {}, { q: "react", page: 2 });
109
+ return <Link to={url}>Search React</Link>;
110
+ }, { name: "search", search: { q: "string", page: "number?" } })
111
+ ```
112
+
45
113
  ### scopedReverse() - type-safe ctx.reverse
46
114
 
47
- Wraps `ctx.reverse` with local route type information for autocomplete and validation:
115
+ Wraps `ctx.reverse` with local route type information for autocomplete and validation. Runtime behavior is identical to `ctx.reverse` — `scopedReverse` is a type-only cast. The same dot-prefix rule applies: local names use `.name`, global names use `name.sub`.
48
116
 
49
117
  ```typescript
50
118
  import { scopedReverse } from "@rangojs/router";
@@ -52,18 +120,83 @@ import { scopedReverse } from "@rangojs/router";
52
120
  path("/product/:slug", (ctx) => {
53
121
  const reverse = scopedReverse<typeof shopPatterns>(ctx.reverse);
54
122
 
55
- reverse("cart"); // Type-safe local name
56
- reverse("product", { slug: "widget" }); // Type-safe with params
57
- reverse("blog.post"); // Absolute names (dot notation) always allowed
58
- reverse("/about"); // Path-based always allowed
123
+ reverse(".cart"); // Local name (dot-prefixed) resolves in include scope
124
+ reverse(".product", { slug: "widget" }); // Local name with params
125
+ reverse("blog.post", { slug: "hi" }); // Global name (dotted) full route map
59
126
 
60
127
  return <ProductPage slug={ctx.params.slug} />;
61
128
  }, { name: "product" })
62
129
  ```
63
130
 
131
+ `reverse()` does not accept raw path strings (`"/about"`). For static paths in client components, use `href("/about")`; on the server, look up the route by name.
132
+
133
+ ## Client components: receive URLs as props
134
+
135
+ `ctx.reverse()` is not available inside `"use client"` modules — there is no handler context in the browser bundle. For in-module names, prefer `useReverse(routes)` (see below) and import the relevant `urls/*.gen.js`. For cross-module URLs or one-off names, generate the URL on the server and hand it to the client component using one of these three patterns:
136
+
137
+ 1. Pass as a prop from a server component:
138
+
139
+ ```tsx
140
+ // server
141
+ function BlogPostPage(ctx: HandlerContext) {
142
+ return <ShareButton url={ctx.reverse(".post", { slug: ctx.params.slug })} />;
143
+ }
144
+ ```
145
+
146
+ ```tsx
147
+ "use client";
148
+
149
+ export function ShareButton({ url }: { url: string }) {
150
+ return (
151
+ <button onClick={() => navigator.clipboard.writeText(url)}>Share</button>
152
+ );
153
+ }
154
+ ```
155
+
156
+ 2. Return from a loader (attached to the route via the DSL):
157
+
158
+ ```tsx
159
+ // server — loaders/nav.ts
160
+ export const NavLoader = createLoader((ctx) => ({
161
+ home: ctx.reverse("home"),
162
+ blog: ctx.reverse("blog.index"),
163
+ }));
164
+
165
+ // server — urls.tsx: attach the loader so useLoader has data in context
166
+ const urlpatterns = urls(({ path, loader }) => [
167
+ path("/", HomePage, { name: "home" }, () => [loader(NavLoader)]),
168
+ ]);
169
+ ```
170
+
171
+ ```tsx
172
+ "use client";
173
+
174
+ function Nav() {
175
+ const { data } = useLoader(NavLoader);
176
+ return <Link to={data.home}>Home</Link>;
177
+ }
178
+ ```
179
+
180
+ `useLoader()` requires the loader to be attached to an active route. If you need on-demand fetching instead, use `useFetchLoader()`.
181
+
182
+ 3. Return from a server action:
183
+
184
+ ```tsx
185
+ "use server";
186
+
187
+ export async function getProductUrl(slug: string) {
188
+ const ctx = getRequestContext();
189
+ return ctx.reverse("product", { slug });
190
+ }
191
+ ```
192
+
193
+ See `/server-actions` for the full action surface (`getRequestContext()` is the same context middleware and handlers use).
194
+
195
+ For static path strings (not named routes), client components can use `href()` — see below.
196
+
64
197
  ## Client: href()
65
198
 
66
- Plain function for absolute path-based URLs. No hook needed - works anywhere.
199
+ Plain function for absolute path-based URLs. No hook needed - works anywhere in client components. `href()` validates paths at compile time, but does **not** resolve named routes — for named routes, use one of the patterns above.
67
200
 
68
201
  ```typescript
69
202
  "use client";
@@ -80,7 +213,9 @@ function GlobalNav() {
80
213
  }
81
214
  ```
82
215
 
83
- `href()` is an identity function at runtime but provides compile-time validation via `ValidPaths` type. Paths are validated against registered route patterns using `PatternToPath`.
216
+ `href()` provides compile-time validation via `ValidPaths` type. Paths are validated against registered route patterns using `PatternToPath`.
217
+
218
+ `href()` is a raw path helper — it is **not** basename-aware. It returns the path as-is (or with the include mount prefix via `useHref()`). For basename-aware navigation, use `Link`, `useRouter().push()`, or `reverse()`, which auto-prefix root-relative paths with the router's basename.
84
219
 
85
220
  ## Client: useHref()
86
221
 
@@ -124,15 +259,159 @@ function MountInfo() {
124
259
 
125
260
  `useMount()` reads from `MountContext`, which is automatically set by `include()` in the segment tree.
126
261
 
262
+ ## Client: useReverse(routes)
263
+
264
+ Hook that returns a typed local reverse function for a `routes` map imported from a generated `.gen.ts` next to a `urls()` module. The route map is the **exposure boundary** — `useReverse` only knows about names in that map, never the full app manifest.
265
+
266
+ ```tsx
267
+ "use client";
268
+ import { Link, useReverse } from "@rangojs/router/client";
269
+ import { routes as blogRoutes } from "../urls/blog.gen.js";
270
+
271
+ export function BlogNav() {
272
+ const reverse = useReverse(blogRoutes);
273
+
274
+ return (
275
+ <nav>
276
+ <Link to={reverse(".index")}>Blog</Link>
277
+ <Link to={reverse(".post", { postId: "hello" })}>Post</Link>
278
+ </nav>
279
+ );
280
+ }
281
+ ```
282
+
283
+ ### How it resolves
284
+
285
+ 1. Strips the leading `.` and looks up the name in the imported `routes` map.
286
+ 2. Joins the local pattern with the surrounding `useMount()` value — the include's URL pattern.
287
+ 3. Substitutes params: explicit params from the call, then auto-filled from `useParams()` for anything still unresolved (mount params like `:tenantId` flow in this way).
288
+ 4. Appends a query string if a search object is passed and the route has a `search` schema.
289
+
290
+ ### Mount-relativity
291
+
292
+ Patterns in the generated `routes` map are **mount-relative** — they're the patterns as defined inside the `urls()` module, _not_ the full app paths. Mount-joining happens at runtime via `useMount()`, so the same component works under any include:
293
+
294
+ ```typescript
295
+ // urls/blog.tsx
296
+ export const blogPatterns = urls(({ path }) => [
297
+ path("/", BlogIndex, { name: "index" }),
298
+ path("/:postId", BlogPost, { name: "post" }),
299
+ ]);
300
+
301
+ // Generated urls/blog.gen.ts
302
+ // export const routes = { index: "/", post: "/:postId" } as const;
303
+
304
+ // urls.tsx — same module mounted twice
305
+ include("/news", blogPatterns, { name: "news" }), // <BlogNav> renders /news, /news/hello
306
+ include("/journal", blogPatterns, { name: "diary" }), // <BlogNav> renders /journal, /journal/hello
307
+ ```
308
+
309
+ The `/` pattern under a non-root mount collapses cleanly: under `/news`, `reverse(".index")` returns `/news` (no trailing slash), matching `ctx.reverse(".index")` on the server.
310
+
311
+ ### Auto-filled params (mount params)
312
+
313
+ When the include itself carries `:params`, those are auto-filled from `useParams()` so the caller doesn't have to thread them through:
314
+
315
+ ```typescript
316
+ // urls.tsx
317
+ include("/tenant/:tenantId", clientReversePatterns, { name: "tenant" });
318
+ ```
319
+
320
+ ```tsx
321
+ // At /tenant/acme/posts/p1, useParams() = { tenantId: "acme", postId: "p1" }
322
+ const reverse = useReverse(clientReverseRoutes);
323
+
324
+ reverse(".index"); // "/tenant/acme"
325
+ reverse(".post", { postId: "p2" }); // "/tenant/acme/posts/p2" (tenantId auto-filled)
326
+ reverse(".post", { tenantId: "other", postId: "p2" }); // "/tenant/other/posts/p2" (explicit override)
327
+ ```
328
+
329
+ Auto-fill follows soft navigation — when the matched route changes, `useReverse` re-renders with the new params.
330
+
331
+ ### Search schemas
332
+
333
+ Routes declared with a `search` schema accept a typed search object as the third argument:
334
+
335
+ ```typescript
336
+ // urls/blog.tsx
337
+ path("/search", SearchPage, {
338
+ name: "search",
339
+ search: { q: "string", page: "number?" },
340
+ }),
341
+
342
+ // Generated as: search: { path: "/search", search: { q: "string", page: "number?" } }
343
+ ```
344
+
345
+ ```tsx
346
+ const reverse = useReverse(blogRoutes);
347
+ reverse(".search", {}, { q: "hello world", page: 2 });
348
+ // "/news/search?q=hello%20world&page=2"
349
+ ```
350
+
351
+ ### Errors
352
+
353
+ - Unknown name: throws `Unknown local route: ".not-a-route"`.
354
+ - Missing required param: throws `Missing param "postId" for route ".detail"`.
355
+
356
+ Both happen synchronously during `reverse()` — wrap calls in try/catch (or an ErrorBoundary if the throw happens during render) when you need to surface them as UI.
357
+
358
+ ### Names are dot-only on the client
359
+
360
+ `useReverse` accepts only `.name` (and dotted variants like `.nested.index`). There is no global namespace on the client — the import IS the scope. To link into a different module, import that module's `routes`:
361
+
362
+ ```tsx
363
+ import { routes as blogRoutes } from "../urls/blog.gen.js";
364
+ import { routes as shopRoutes } from "../urls/shop.gen.js";
365
+
366
+ function CrossNav() {
367
+ const blog = useReverse(blogRoutes);
368
+ const shop = useReverse(shopRoutes);
369
+ return (
370
+ <nav>
371
+ <Link to={blog(".index")}>Blog</Link>
372
+ <Link to={shop(".cart")}>Cart</Link>
373
+ </nav>
374
+ );
375
+ }
376
+ ```
377
+
378
+ ### Codegen
379
+
380
+ Each `urls()` module gets a sibling `.gen.ts` with the local route names and patterns, produced by `rango generate`:
381
+
382
+ ```bash
383
+ pnpm exec rango generate src/urls/blog.tsx
384
+ # or generate everything under a directory:
385
+ pnpm exec rango generate src/urls --static
386
+ ```
387
+
388
+ Don't edit the file by hand — re-run codegen when patterns change.
389
+
390
+ **Today the Vite plugin only regenerates the router-level `*.named-routes.gen.ts`.** Per-module `urls/*.gen.ts` files are emitted only by the CLI (or `writePerModuleRouteTypesForFile` programmatically). Commit the generated files and re-run `rango generate` whenever a `urls()` module's `path()`/`include()` shape changes. A common workflow is to wire it into a `predev` script:
391
+
392
+ ```jsonc
393
+ // package.json
394
+ {
395
+ "scripts": {
396
+ "predev": "rango generate src",
397
+ "dev": "vite",
398
+ },
399
+ }
400
+ ```
401
+
127
402
  ## When to use what
128
403
 
129
- | Context | API | Resolves | Use for |
130
- |---------|-----|----------|---------|
131
- | Server handler | `ctx.reverse("name")` | Named routes (local + absolute) | Server-side URL generation |
132
- | Server handler | `scopedReverse<T>(ctx.reverse)` | Same, with type safety | Type-safe server URLs |
133
- | Client component | `href("/path")` | Absolute paths | Global navigation |
134
- | Client component | `useHref()` | Mount-prefixed paths | Local navigation inside `include()` |
135
- | Client component | `useMount()` | Raw mount path | Custom mount-aware logic |
404
+ | Context | API | Resolves | Use for |
405
+ | ---------------- | -------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------- |
406
+ | Server handler | `ctx.reverse("name")` | Named routes (local + absolute) | **Default** server-side URL generation |
407
+ | Server handler | `scopedReverse<T>(ctx.reverse)` | Same, with type safety | Type-safe server URLs |
408
+ | Client component | `useReverse(routes)` | Local names from an imported `routes` map | Typed in-module URL generation without round-tripping the server |
409
+ | Client component | (URL passed as prop / loader data / action return) | Named routes | Cross-module URLs or one-off names you don't want to import |
410
+ | Client component | `href("/path")` | Absolute paths (static strings) | Static navigation where no named-route lookup is needed |
411
+ | Client component | `useHref()` | Mount-prefixed paths | Local navigation inside `include()` |
412
+ | Client component | `useMount()` | Raw mount path | Custom mount-aware logic |
413
+
414
+ > `ctx.reverse()` is server-only. On the client, either generate URLs on the server and pass them in, or import the `routes` map and use `useReverse(routes)` for in-module names.
136
415
 
137
416
  ## Complete example: mounted module
138
417