@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.81

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 (316) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +5091 -941
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +61 -52
  7. package/skills/breadcrumbs/SKILL.md +250 -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 +167 -0
  14. package/skills/handler-use/SKILL.md +362 -0
  15. package/skills/hooks/SKILL.md +340 -72
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/intercept/SKILL.md +151 -8
  18. package/skills/layout/SKILL.md +122 -3
  19. package/skills/links/SKILL.md +92 -31
  20. package/skills/loader/SKILL.md +404 -44
  21. package/skills/middleware/SKILL.md +205 -37
  22. package/skills/migrate-nextjs/SKILL.md +560 -0
  23. package/skills/migrate-react-router/SKILL.md +765 -0
  24. package/skills/mime-routes/SKILL.md +128 -0
  25. package/skills/parallel/SKILL.md +263 -1
  26. package/skills/prerender/SKILL.md +685 -0
  27. package/skills/rango/SKILL.md +87 -16
  28. package/skills/response-routes/SKILL.md +411 -0
  29. package/skills/route/SKILL.md +281 -14
  30. package/skills/router-setup/SKILL.md +210 -32
  31. package/skills/tailwind/SKILL.md +129 -0
  32. package/skills/theme/SKILL.md +9 -8
  33. package/skills/typesafety/SKILL.md +328 -89
  34. package/skills/use-cache/SKILL.md +324 -0
  35. package/src/__internal.ts +102 -4
  36. package/src/bin/rango.ts +321 -0
  37. package/src/browser/action-coordinator.ts +97 -0
  38. package/src/browser/action-response-classifier.ts +99 -0
  39. package/src/browser/app-version.ts +14 -0
  40. package/src/browser/event-controller.ts +92 -64
  41. package/src/browser/history-state.ts +80 -0
  42. package/src/browser/intercept-utils.ts +52 -0
  43. package/src/browser/link-interceptor.ts +24 -4
  44. package/src/browser/logging.ts +55 -0
  45. package/src/browser/merge-segment-loaders.ts +20 -12
  46. package/src/browser/navigation-bridge.ts +317 -560
  47. package/src/browser/navigation-client.ts +206 -68
  48. package/src/browser/navigation-store.ts +73 -55
  49. package/src/browser/navigation-transaction.ts +297 -0
  50. package/src/browser/network-error-handler.ts +61 -0
  51. package/src/browser/partial-update.ts +343 -316
  52. package/src/browser/prefetch/cache.ts +216 -0
  53. package/src/browser/prefetch/fetch.ts +206 -0
  54. package/src/browser/prefetch/observer.ts +65 -0
  55. package/src/browser/prefetch/policy.ts +48 -0
  56. package/src/browser/prefetch/queue.ts +160 -0
  57. package/src/browser/prefetch/resource-ready.ts +77 -0
  58. package/src/browser/rango-state.ts +112 -0
  59. package/src/browser/react/Link.tsx +253 -74
  60. package/src/browser/react/NavigationProvider.tsx +91 -11
  61. package/src/browser/react/context.ts +11 -0
  62. package/src/browser/react/filter-segment-order.ts +11 -0
  63. package/src/browser/react/index.ts +12 -12
  64. package/src/browser/react/location-state-shared.ts +95 -53
  65. package/src/browser/react/location-state.ts +60 -15
  66. package/src/browser/react/mount-context.ts +6 -1
  67. package/src/browser/react/nonce-context.ts +23 -0
  68. package/src/browser/react/shallow-equal.ts +27 -0
  69. package/src/browser/react/use-action.ts +29 -51
  70. package/src/browser/react/use-client-cache.ts +5 -3
  71. package/src/browser/react/use-handle.ts +30 -126
  72. package/src/browser/react/use-href.tsx +2 -2
  73. package/src/browser/react/use-link-status.ts +6 -5
  74. package/src/browser/react/use-navigation.ts +44 -65
  75. package/src/browser/react/use-params.ts +75 -0
  76. package/src/browser/react/use-pathname.ts +47 -0
  77. package/src/browser/react/use-router.ts +76 -0
  78. package/src/browser/react/use-search-params.ts +56 -0
  79. package/src/browser/react/use-segments.ts +80 -97
  80. package/src/browser/response-adapter.ts +73 -0
  81. package/src/browser/rsc-router.tsx +214 -58
  82. package/src/browser/scroll-restoration.ts +127 -52
  83. package/src/browser/segment-reconciler.ts +243 -0
  84. package/src/browser/segment-structure-assert.ts +16 -0
  85. package/src/browser/server-action-bridge.ts +510 -603
  86. package/src/browser/shallow.ts +6 -1
  87. package/src/browser/types.ts +141 -48
  88. package/src/browser/validate-redirect-origin.ts +29 -0
  89. package/src/build/generate-manifest.ts +235 -24
  90. package/src/build/generate-route-types.ts +39 -0
  91. package/src/build/index.ts +13 -0
  92. package/src/build/route-trie.ts +291 -0
  93. package/src/build/route-types/ast-helpers.ts +25 -0
  94. package/src/build/route-types/ast-route-extraction.ts +98 -0
  95. package/src/build/route-types/codegen.ts +102 -0
  96. package/src/build/route-types/include-resolution.ts +418 -0
  97. package/src/build/route-types/param-extraction.ts +48 -0
  98. package/src/build/route-types/per-module-writer.ts +128 -0
  99. package/src/build/route-types/router-processing.ts +618 -0
  100. package/src/build/route-types/scan-filter.ts +85 -0
  101. package/src/build/runtime-discovery.ts +231 -0
  102. package/src/cache/background-task.ts +34 -0
  103. package/src/cache/cache-key-utils.ts +44 -0
  104. package/src/cache/cache-policy.ts +125 -0
  105. package/src/cache/cache-runtime.ts +342 -0
  106. package/src/cache/cache-scope.ts +167 -309
  107. package/src/cache/cf/cf-cache-store.ts +571 -17
  108. package/src/cache/cf/index.ts +13 -3
  109. package/src/cache/document-cache.ts +116 -77
  110. package/src/cache/handle-capture.ts +81 -0
  111. package/src/cache/handle-snapshot.ts +41 -0
  112. package/src/cache/index.ts +1 -15
  113. package/src/cache/memory-segment-store.ts +191 -13
  114. package/src/cache/profile-registry.ts +73 -0
  115. package/src/cache/read-through-swr.ts +134 -0
  116. package/src/cache/segment-codec.ts +256 -0
  117. package/src/cache/taint.ts +153 -0
  118. package/src/cache/types.ts +72 -122
  119. package/src/client.rsc.tsx +3 -1
  120. package/src/client.tsx +135 -301
  121. package/src/component-utils.ts +4 -4
  122. package/src/components/DefaultDocument.tsx +5 -1
  123. package/src/context-var.ts +156 -0
  124. package/src/debug.ts +19 -9
  125. package/src/errors.ts +108 -2
  126. package/src/handle.ts +55 -29
  127. package/src/handles/MetaTags.tsx +73 -20
  128. package/src/handles/breadcrumbs.ts +66 -0
  129. package/src/handles/index.ts +1 -0
  130. package/src/handles/meta.ts +30 -13
  131. package/src/host/cookie-handler.ts +21 -15
  132. package/src/host/errors.ts +8 -8
  133. package/src/host/index.ts +4 -7
  134. package/src/host/pattern-matcher.ts +27 -27
  135. package/src/host/router.ts +61 -39
  136. package/src/host/testing.ts +8 -8
  137. package/src/host/types.ts +15 -7
  138. package/src/host/utils.ts +1 -1
  139. package/src/href-client.ts +119 -29
  140. package/src/index.rsc.ts +155 -19
  141. package/src/index.ts +251 -30
  142. package/src/internal-debug.ts +11 -0
  143. package/src/loader.rsc.ts +26 -157
  144. package/src/loader.ts +27 -10
  145. package/src/network-error-thrower.tsx +3 -1
  146. package/src/outlet-provider.tsx +45 -0
  147. package/src/prerender/param-hash.ts +37 -0
  148. package/src/prerender/store.ts +186 -0
  149. package/src/prerender.ts +524 -0
  150. package/src/reverse.ts +354 -0
  151. package/src/root-error-boundary.tsx +41 -29
  152. package/src/route-content-wrapper.tsx +7 -4
  153. package/src/route-definition/dsl-helpers.ts +1121 -0
  154. package/src/route-definition/helper-factories.ts +200 -0
  155. package/src/route-definition/helpers-types.ts +478 -0
  156. package/src/route-definition/index.ts +55 -0
  157. package/src/route-definition/redirect.ts +101 -0
  158. package/src/route-definition/resolve-handler-use.ts +149 -0
  159. package/src/route-definition.ts +1 -1428
  160. package/src/route-map-builder.ts +217 -123
  161. package/src/route-name.ts +53 -0
  162. package/src/route-types.ts +77 -8
  163. package/src/router/content-negotiation.ts +215 -0
  164. package/src/router/debug-manifest.ts +72 -0
  165. package/src/router/error-handling.ts +9 -9
  166. package/src/router/find-match.ts +160 -0
  167. package/src/router/handler-context.ts +438 -86
  168. package/src/router/intercept-resolution.ts +402 -0
  169. package/src/router/lazy-includes.ts +237 -0
  170. package/src/router/loader-resolution.ts +356 -128
  171. package/src/router/logging.ts +251 -0
  172. package/src/router/manifest.ts +163 -35
  173. package/src/router/match-api.ts +555 -0
  174. package/src/router/match-context.ts +5 -3
  175. package/src/router/match-handlers.ts +440 -0
  176. package/src/router/match-middleware/background-revalidation.ts +108 -93
  177. package/src/router/match-middleware/cache-lookup.ts +460 -10
  178. package/src/router/match-middleware/cache-store.ts +98 -26
  179. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  180. package/src/router/match-middleware/segment-resolution.ts +80 -6
  181. package/src/router/match-pipelines.ts +10 -45
  182. package/src/router/match-result.ts +135 -35
  183. package/src/router/metrics.ts +240 -15
  184. package/src/router/middleware-cookies.ts +55 -0
  185. package/src/router/middleware-types.ts +220 -0
  186. package/src/router/middleware.ts +324 -369
  187. package/src/router/navigation-snapshot.ts +182 -0
  188. package/src/router/pattern-matching.ts +211 -43
  189. package/src/router/prerender-match.ts +502 -0
  190. package/src/router/preview-match.ts +98 -0
  191. package/src/router/request-classification.ts +310 -0
  192. package/src/router/revalidation.ts +137 -38
  193. package/src/router/route-snapshot.ts +245 -0
  194. package/src/router/router-context.ts +41 -21
  195. package/src/router/router-interfaces.ts +484 -0
  196. package/src/router/router-options.ts +618 -0
  197. package/src/router/router-registry.ts +24 -0
  198. package/src/router/segment-resolution/fresh.ts +748 -0
  199. package/src/router/segment-resolution/helpers.ts +268 -0
  200. package/src/router/segment-resolution/loader-cache.ts +199 -0
  201. package/src/router/segment-resolution/revalidation.ts +1379 -0
  202. package/src/router/segment-resolution/static-store.ts +67 -0
  203. package/src/router/segment-resolution.ts +21 -0
  204. package/src/router/segment-wrappers.ts +291 -0
  205. package/src/router/telemetry-otel.ts +299 -0
  206. package/src/router/telemetry.ts +300 -0
  207. package/src/router/timeout.ts +148 -0
  208. package/src/router/trie-matching.ts +239 -0
  209. package/src/router/types.ts +78 -3
  210. package/src/router.ts +740 -4252
  211. package/src/rsc/handler-context.ts +45 -0
  212. package/src/rsc/handler.ts +907 -797
  213. package/src/rsc/helpers.ts +140 -6
  214. package/src/rsc/index.ts +0 -20
  215. package/src/rsc/loader-fetch.ts +229 -0
  216. package/src/rsc/manifest-init.ts +90 -0
  217. package/src/rsc/nonce.ts +14 -0
  218. package/src/rsc/origin-guard.ts +141 -0
  219. package/src/rsc/progressive-enhancement.ts +393 -0
  220. package/src/rsc/response-error.ts +37 -0
  221. package/src/rsc/response-route-handler.ts +347 -0
  222. package/src/rsc/rsc-rendering.ts +246 -0
  223. package/src/rsc/runtime-warnings.ts +42 -0
  224. package/src/rsc/server-action.ts +358 -0
  225. package/src/rsc/ssr-setup.ts +128 -0
  226. package/src/rsc/types.ts +46 -11
  227. package/src/search-params.ts +230 -0
  228. package/src/segment-content-promise.ts +67 -0
  229. package/src/segment-loader-promise.ts +122 -0
  230. package/src/segment-system.tsx +134 -36
  231. package/src/server/context.ts +341 -61
  232. package/src/server/cookie-store.ts +190 -0
  233. package/src/server/fetchable-loader-store.ts +37 -0
  234. package/src/server/handle-store.ts +113 -15
  235. package/src/server/loader-registry.ts +24 -64
  236. package/src/server/request-context.ts +607 -81
  237. package/src/server.ts +35 -130
  238. package/src/ssr/index.tsx +103 -30
  239. package/src/static-handler.ts +126 -0
  240. package/src/theme/ThemeProvider.tsx +21 -15
  241. package/src/theme/ThemeScript.tsx +5 -5
  242. package/src/theme/constants.ts +5 -2
  243. package/src/theme/index.ts +4 -14
  244. package/src/theme/theme-context.ts +4 -30
  245. package/src/theme/theme-script.ts +21 -18
  246. package/src/types/boundaries.ts +158 -0
  247. package/src/types/cache-types.ts +198 -0
  248. package/src/types/error-types.ts +192 -0
  249. package/src/types/global-namespace.ts +100 -0
  250. package/src/types/handler-context.ts +791 -0
  251. package/src/types/index.ts +88 -0
  252. package/src/types/loader-types.ts +210 -0
  253. package/src/types/route-config.ts +170 -0
  254. package/src/types/route-entry.ts +120 -0
  255. package/src/types/segments.ts +150 -0
  256. package/src/types.ts +1 -1623
  257. package/src/urls/include-helper.ts +207 -0
  258. package/src/urls/index.ts +53 -0
  259. package/src/urls/path-helper-types.ts +372 -0
  260. package/src/urls/path-helper.ts +364 -0
  261. package/src/urls/pattern-types.ts +107 -0
  262. package/src/urls/response-types.ts +116 -0
  263. package/src/urls/type-extraction.ts +372 -0
  264. package/src/urls/urls-function.ts +98 -0
  265. package/src/urls.ts +1 -802
  266. package/src/use-loader.tsx +161 -81
  267. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  268. package/src/vite/discovery/discover-routers.ts +348 -0
  269. package/src/vite/discovery/prerender-collection.ts +439 -0
  270. package/src/vite/discovery/route-types-writer.ts +258 -0
  271. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  272. package/src/vite/discovery/state.ts +117 -0
  273. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  274. package/src/vite/index.ts +15 -1133
  275. package/src/vite/plugin-types.ts +103 -0
  276. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  277. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  278. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  279. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  280. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  281. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  282. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  283. package/src/vite/plugins/expose-id-utils.ts +299 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  290. package/src/vite/plugins/performance-tracks.ts +88 -0
  291. package/src/vite/plugins/refresh-cmd.ts +127 -0
  292. package/src/vite/plugins/use-cache-transform.ts +323 -0
  293. package/src/vite/plugins/version-injector.ts +83 -0
  294. package/src/vite/plugins/version-plugin.ts +266 -0
  295. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +462 -0
  298. package/src/vite/router-discovery.ts +977 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  304. package/src/vite/utils/prerender-utils.ts +221 -0
  305. package/src/vite/utils/shared-utils.ts +170 -0
  306. package/CLAUDE.md +0 -43
  307. package/src/browser/lru-cache.ts +0 -69
  308. package/src/browser/request-controller.ts +0 -164
  309. package/src/cache/memory-store.ts +0 -253
  310. package/src/href-context.ts +0 -33
  311. package/src/href.ts +0 -255
  312. package/src/server/route-manifest-cache.ts +0 -173
  313. package/src/vite/expose-handle-id.ts +0 -209
  314. package/src/vite/expose-loader-id.ts +0 -426
  315. package/src/vite/expose-location-state-id.ts +0 -177
  316. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -15,17 +15,49 @@ argument-hint: [setup]
15
15
  import { createRouter } from "@rangojs/router";
16
16
  import { urlpatterns } from "./urls";
17
17
 
18
- const router = createRouter<AppEnv>({
18
+ const router = createRouter<AppBindings>({
19
19
  document: Document,
20
- urls: urlpatterns,
21
- });
20
+ }).routes(urlpatterns);
22
21
 
23
- // Server-side named-route href (type-safe via routeMap)
24
- export const href = router.href;
22
+ // Server-side named-route reverse (type-safe via routeMap)
23
+ export const reverse = router.reverse;
25
24
 
26
25
  export default router;
27
26
  ```
28
27
 
28
+ ### Which global type should I use?
29
+
30
+ Use the generated route map by default. Manual `RegisteredRoutes` augmentation
31
+ is only needed when you want the richer `typeof router.routeMap` shape
32
+ available globally.
33
+
34
+ - `GeneratedRouteMap` — auto-registered by `router.named-routes.gen.ts`
35
+ Use for `Handler<"name">`, `Prerender<"name">`, server `ctx.reverse()`,
36
+ and named-route param/search inference.
37
+ - `typeof router.routeMap` — the real merged route map from your router
38
+ instance, including response-route metadata such as `{ path, response }`.
39
+ - `RegisteredRoutes` — manual global hook for exposing `typeof router.routeMap`
40
+ to utilities like `href()`, `ValidPaths`, and `PathResponse`.
41
+
42
+ Recommended setup:
43
+
44
+ ```typescript
45
+ // router.tsx
46
+ import { createRouter } from "@rangojs/router";
47
+ import { urlpatterns } from "./urls";
48
+ import type { AppBindings, AppVars } from "./env";
49
+
50
+ export const router = createRouter<AppBindings>({}).routes(urlpatterns);
51
+
52
+ declare global {
53
+ namespace RSCRouter {
54
+ interface Env extends AppBindings {}
55
+ interface Vars extends AppVars {}
56
+ interface RegisteredRoutes extends typeof router.routeMap {}
57
+ }
58
+ }
59
+ ```
60
+
29
61
  ## Route Definition with Type-Safe Names
30
62
 
31
63
  ```typescript
@@ -45,22 +77,35 @@ export const urlpatterns = urls(({ path, layout }) => [
45
77
 
46
78
  ## Type-Safe href()
47
79
 
48
- ### Server: ctx.href + scopedHref
80
+ ### Server: ctx.reverse with route names
49
81
 
50
- In route handlers, use `scopedHref()` for local route name autocomplete:
82
+ In route handlers, `ctx.reverse()` uses two namespaces:
83
+
84
+ - **`.name`** — local route, resolved within the current `include()` scope
85
+ - **`name`** — global route, from the named-routes definition
51
86
 
52
87
  ```typescript
53
- import { scopedHref } from "@rangojs/router";
88
+ import type { Handler } from "@rangojs/router";
54
89
 
55
- path("/product/:slug", (ctx) => {
56
- const href = scopedHref<typeof shopPatterns>(ctx.href);
90
+ export const ProductHandler: Handler<"shop.product"> = (ctx) => {
91
+ ctx.reverse(".cart"); // Local: /shop/cart
92
+ ctx.reverse(".product", { slug: "widget" }); // Local: /shop/product/widget
93
+ ctx.reverse("blog.post", { slug: "1" }); // Global: /blog/1
94
+ };
95
+ ```
57
96
 
58
- href("cart"); // Type-safe local name
59
- href("product", { slug: "widget" }); // Type-safe with params
60
- href("blog.post"); // Absolute names always allowed
97
+ For type-safe local names, generate a route types file with `npx rango generate urls/shop.tsx`
98
+ and pass it as the second generic to `Handler` or `Prerender`:
61
99
 
62
- return <ProductPage slug={ctx.params.slug} />;
63
- }, { name: "product" })
100
+ ```typescript
101
+ import type { Handler } from "@rangojs/router";
102
+ import type { routes } from "./shop.gen.js";
103
+
104
+ export const ProductHandler: Handler<"shop.product", routes> = (ctx) => {
105
+ ctx.reverse(".cart"); // Type-safe local name
106
+ ctx.reverse(".product", { slug: "widget" }); // Type-safe local with params
107
+ ctx.reverse("blog.post", { slug: "hi" }); // Type-safe global name
108
+ };
64
109
  ```
65
110
 
66
111
  ### Client: href + useHref
@@ -82,6 +127,17 @@ function ShopNav() {
82
127
  }
83
128
  ```
84
129
 
130
+ `href()` and path-based response utilities read from `RegisteredRoutes`, so if
131
+ you want them typed globally you should augment:
132
+
133
+ ```typescript
134
+ declare global {
135
+ namespace RSCRouter {
136
+ interface RegisteredRoutes extends typeof router.routeMap {}
137
+ }
138
+ }
139
+ ```
140
+
85
141
  See `/links` for full URL generation guide.
86
142
 
87
143
  ## Environment Type Setup
@@ -90,50 +146,57 @@ Define your app's environment for type-safe bindings and variables:
90
146
 
91
147
  ```typescript
92
148
  // env.ts
93
- import type { RouterEnv } from "@rangojs/router";
94
149
 
95
- // Cloudflare bindings
96
- interface AppBindings {
150
+ // Cloudflare bindings — passed as TEnv to createRouter<TEnv>()
151
+ export interface AppBindings {
97
152
  DB: D1Database;
98
153
  KV: KVNamespace;
99
154
  CACHE: KVNamespace;
100
155
  AI: Ai;
101
156
  }
102
157
 
103
- // Variables set by middleware
104
- interface AppVariables {
158
+ // Variables set by middleware — declared via module augmentation
159
+ export interface AppVariables {
105
160
  user?: { id: string; email: string; role: string };
106
161
  requestId?: string;
107
162
  permissions?: string[];
108
163
  }
109
-
110
- // Combined environment type
111
- export type AppEnv = RouterEnv<AppBindings, AppVariables>;
112
164
  ```
113
165
 
114
166
  ### Using Environment Types
115
167
 
116
168
  ```typescript
117
169
  // router.tsx
118
- import type { AppEnv } from "./env";
170
+ import type { AppBindings, AppVariables } from "./env";
119
171
 
120
- const router = createRouter<AppEnv>({
172
+ const router = createRouter<AppBindings>({
121
173
  document: Document,
122
- urls: urlpatterns,
123
- });
174
+ }).routes(urlpatterns);
175
+
176
+ // Register bindings and variables globally for implicit typing
177
+ declare global {
178
+ namespace RSCRouter {
179
+ interface Env extends AppBindings {}
180
+ interface Vars extends AppVariables {}
181
+ }
182
+ }
124
183
 
125
- // middleware - typed ctx.env.Variables
126
- import { createMiddleware } from "@rangojs/router";
184
+ // middleware - typed via ctx.set / ctx.get
185
+ import type { Middleware } from "@rangojs/router";
127
186
 
128
- export const authMiddleware = createMiddleware(async (ctx, next) => {
129
- ctx.env.Variables.user = { id: "123", email: "user@example.com", role: "admin" };
187
+ export const authMiddleware: Middleware = async (ctx, next) => {
188
+ ctx.set("user", {
189
+ id: "123",
190
+ email: "user@example.com",
191
+ role: "admin",
192
+ });
130
193
  await next();
131
- });
194
+ };
132
195
 
133
196
  // loaders - typed context
134
- export const UserLoader = createLoader("user", async (ctx) => {
135
- const db = ctx.env.Bindings.DB; // D1Database
136
- const userId = ctx.env.Variables.user?.id;
197
+ export const UserLoader = createLoader(async (ctx) => {
198
+ const db = ctx.env.DB; // D1Database (plain bindings)
199
+ const userId = ctx.get("user")?.id; // from RSCRouter.Vars
137
200
  return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
138
201
  });
139
202
  ```
@@ -146,7 +209,8 @@ Register environment types globally for implicit typing:
146
209
  // router.tsx
147
210
  declare global {
148
211
  namespace RSCRouter {
149
- interface Env extends AppEnv {}
212
+ interface Env extends AppBindings {}
213
+ interface Vars extends AppVariables {}
150
214
  }
151
215
  }
152
216
  ```
@@ -155,21 +219,113 @@ Now handlers have typed context without explicit imports:
155
219
 
156
220
  ```typescript
157
221
  // In loaders
158
- export const DashboardLoader = createLoader("dashboard", async (ctx) => {
159
- // ctx.env.Variables.user is typed from global Env
160
- // ctx.params is typed from route pattern
161
- const user = ctx.env.Variables.user;
222
+ export const DashboardLoader = createLoader(async (ctx) => {
223
+ // ctx.env.DB is typed from global RSCRouter.Env
224
+ // ctx.get("user") is typed from global RSCRouter.Vars
225
+ const user = ctx.get("user");
162
226
  return { user };
163
227
  });
164
228
  ```
165
229
 
230
+ ## Typed Search Params
231
+
232
+ Add a `search` schema to `path()` options for type-safe query parameters:
233
+
234
+ ```typescript
235
+ // Route definition with search schema
236
+ path("/search", SearchPage, {
237
+ name: "search",
238
+ search: { q: "string", page: "number?", sort: "string?" },
239
+ });
240
+ ```
241
+
242
+ ### Handler with typed search params
243
+
244
+ `Handler<"name">` automatically resolves route params and search params from the
245
+ global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
246
+
247
+ ```typescript
248
+ // pages/search.tsx
249
+ import type { Handler } from "@rangojs/router";
250
+
251
+ export const SearchPage: Handler<"search"> = (ctx) => {
252
+ // ctx.search is typed: { q: string; page?: number; sort?: string }
253
+ const { q, page, sort } = ctx.search;
254
+ return <SearchResults q={q} page={page} sort={sort} />;
255
+ };
256
+ ```
257
+
258
+ This avoids circular references because `Handler` defaults to `GeneratedRouteMap`
259
+ (from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
260
+
261
+ You can also pass an explicit route map for per-module isolation (opt-in,
262
+ after running `npx rango generate`):
263
+
264
+ ```typescript
265
+ import type { Handler } from "@rangojs/router";
266
+ import type { routes } from "./urls.gen.js";
267
+
268
+ export const SearchPage: Handler<"search", routes> = (ctx) => { ... };
269
+ ```
270
+
271
+ Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
272
+ Values are automatically coerced from query string (e.g., `"2"` becomes `2` for numbers).
273
+ Routes without a `search` schema keep the standard `URLSearchParams` behavior.
274
+
275
+ ### RouteSearchParams and RouteParams utility types
276
+
277
+ Extract typed params by route name for use in component props, return types, or anywhere:
278
+
279
+ ```typescript
280
+ import type { RouteSearchParams, RouteParams } from "@rangojs/router";
281
+
282
+ // RouteSearchParams<"name"> resolves the search schema to a typed object
283
+ type SP = RouteSearchParams<"search">;
284
+ // { q: string | undefined; page?: number; sort?: string }
285
+
286
+ // RouteParams<"name"> resolves URL params from the route pattern
287
+ type P = RouteParams<"blogPost">;
288
+ // { slug: string }
289
+
290
+ // Use in component props
291
+ interface SearchResultsProps {
292
+ params: RouteSearchParams<"search">;
293
+ }
294
+ ```
295
+
296
+ Both default to the global route map (`RegisteredRoutes` or `GeneratedRouteMap`).
297
+ Pass an explicit route map as the second type argument when needed:
298
+
299
+ ```typescript
300
+ import type { routes } from "./urls.gen.js";
301
+
302
+ type SP = RouteSearchParams<"search", routes>;
303
+ type P = RouteParams<"blogPost", routes>;
304
+ ```
305
+
306
+ ### Generated route types
307
+
308
+ In the generated `router.named-routes.gen.ts`, routes with search schemas
309
+ use `{ path, search }` objects:
310
+
311
+ ```typescript
312
+ // router.named-routes.gen.ts (auto-generated)
313
+ export const NamedRoutes = {
314
+ "search.index": {
315
+ path: "/search",
316
+ search: { q: "string", page: "number?", sort: "string?" },
317
+ },
318
+ "home.index": "/", // No search schema -> plain string
319
+ } as const;
320
+ ```
321
+
166
322
  ## Loader Type Safety
167
323
 
168
324
  Loaders have typed return values:
169
325
 
170
326
  ```typescript
171
327
  // loaders/product.ts
172
- export const ProductLoader = createLoader("product", async (ctx) => {
328
+ export const ProductLoader = createLoader(async (ctx) => {
173
329
  return {
174
330
  id: ctx.params.slug,
175
331
  name: "Widget",
@@ -178,7 +334,7 @@ export const ProductLoader = createLoader("product", async (ctx) => {
178
334
  });
179
335
 
180
336
  // In server component - type is inferred
181
- import { useLoader } from "@rangojs/router";
337
+ import { useLoader } from "@rangojs/router/client";
182
338
 
183
339
  async function ProductPage() {
184
340
  const product = await useLoader(ProductLoader);
@@ -188,39 +344,110 @@ async function ProductPage() {
188
344
 
189
345
  // In client component - same type
190
346
  "use client";
191
- import { useLoaderData } from "@rangojs/router/client";
347
+ import { useLoader } from "@rangojs/router/client";
192
348
 
193
349
  function ProductPrice() {
194
- const { product } = useLoaderData(ProductLoader);
195
- // product: { id: string; name: string; price: number }
350
+ const { data } = useLoader(ProductLoader);
351
+ // data: { id: string; name: string; price: number }
352
+ const product = data;
196
353
  return <span>${product.price}</span>;
197
354
  }
198
355
  ```
199
356
 
200
- ## Handle Type Safety
357
+ ## Typed Context Variables
201
358
 
202
- Handles have typed data:
359
+ `createVar<T>()` creates a typed token for `ctx.set()`/`ctx.get()`, making
360
+ handler-to-layout data contracts explicit and compile-time verified:
203
361
 
204
362
  ```typescript
205
- // handles/breadcrumbs.ts
206
- import { createHandle } from "@rangojs/router";
363
+ import { createVar } from "@rangojs/router";
207
364
 
208
- export const Breadcrumbs = createHandle<{ label: string; href: string }>();
365
+ // Define a typed token (shared between producer and consumer)
366
+ interface PaginationData {
367
+ current: number;
368
+ total: number;
369
+ perPage: number;
370
+ }
371
+ export const Pagination = createVar<PaginationData>();
209
372
 
210
- // In route definition - use handle() DSL
211
- import { urls } from "@rangojs/router";
373
+ // Non-cacheable var reading inside cache() or "use cache" throws at runtime
374
+ const Session = createVar<SessionData>({ cache: false });
375
+ ```
212
376
 
213
- export const urlpatterns = urls(({ path, handle }) => [
214
- path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
215
- handle(Breadcrumbs, { label: "Products", href: "/shop/products" }),
216
- ]),
217
- ]);
377
+ `createVar` accepts an optional options object. The `cache` option (default
378
+ `true`) controls whether the var's values can be read inside cache scopes.
379
+ Write-level escalation is also supported: `ctx.set(Var, value, { cache: false })`
380
+ marks a specific write as non-cacheable even if the var itself is cacheable.
381
+ "Least cacheable wins" — if either says `cache: false`, the value throws on
382
+ read inside `cache()` or `"use cache"`.
383
+
384
+ ### Producer (handler or middleware)
385
+
386
+ ```typescript
387
+ import { Pagination } from "../vars/pagination.js";
388
+
389
+ const ArticleList: Handler<"articles.list"> = async (ctx) => {
390
+ ctx.set(Pagination, { // type-checked
391
+ current: 1,
392
+ total: 10,
393
+ perPage: 5,
394
+ });
395
+ return <Articles />;
396
+ };
397
+ ```
398
+
399
+ ### Consumer (layout, parallel, or any context with get)
400
+
401
+ ```typescript
402
+ import { Pagination } from "../vars/pagination.js";
403
+
404
+ export function PaginationLayout(ctx: any) {
405
+ const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
406
+ if (!pagination) return <Outlet />;
407
+ return <nav>Page {pagination.current} of {pagination.total}</nav>;
408
+ }
409
+ ```
410
+
411
+ ### Why not just use RSCRouter.Vars?
412
+
413
+ `RSCRouter.Vars` (via module augmentation) provides app-global typing for
414
+ `ctx.get("key")` / `ctx.set("key", value)`. It works for middleware state
415
+ shared app-wide. `createVar<T>()` is for route-local or feature-scoped
416
+ context -- the producer and consumer import the same token, creating a
417
+ scoped contract without polluting global types.
418
+
419
+ Both approaches coexist: `ctx.get("user")` (global via Vars) and
420
+ `ctx.get(Pagination)` (scoped via createVar) work side by side.
421
+
422
+ ## Handle Type Safety
423
+
424
+ Handles have typed data:
218
425
 
219
- // In client - typed array
426
+ ```typescript
427
+ // Built-in Breadcrumbs handle — import from "@rangojs/router"
428
+ import { Breadcrumbs } from "@rangojs/router";
429
+ // Type: Handle<BreadcrumbItem, BreadcrumbItem[]>
430
+ // BreadcrumbItem: { label: string; href: string; content?: ReactNode | Promise<ReactNode> }
431
+
432
+ // In route handler — push is fully typed
433
+ path("/shop/product/:slug", (ctx) => {
434
+ const breadcrumb = ctx.use(Breadcrumbs);
435
+ breadcrumb({ label: "Products", href: "/shop/products" });
436
+ return <ProductPage />;
437
+ }, { name: "product" });
438
+
439
+ // In client — typed array
440
+ import { useHandle, Breadcrumbs } from "@rangojs/router/client";
220
441
  function BreadcrumbNav() {
221
442
  const crumbs = useHandle(Breadcrumbs);
222
- // crumbs: Array<{ label: string; href: string }>
443
+ // crumbs: BreadcrumbItem[]
223
444
  }
445
+
446
+ // Custom handles also work the same way
447
+ import { createHandle } from "@rangojs/router";
448
+ export const PageTitle = createHandle<string, string>(
449
+ (segments) => segments.flat().at(-1) ?? "Default Title"
450
+ );
224
451
  ```
225
452
 
226
453
  ## Ref Prop Type Safety (Loaders & Handles)
@@ -234,24 +461,22 @@ export const ProductLoader = createLoader(async (ctx) => {
234
461
  return { product: await fetchProduct(ctx.params.slug) };
235
462
  });
236
463
 
237
- // handles.ts
238
- export const Breadcrumbs = createHandle<{ label: string; href: string }>();
464
+ // Built-in Breadcrumbs — or any custom handle created with createHandle()
239
465
 
240
466
  // Client component — typeof infers all generics
241
- "use client";
242
- import { useLoader, useHandle } from "@rangojs/router/client";
467
+ ("use client");
468
+ import { useLoader, useHandle, type Breadcrumbs } from "@rangojs/router/client";
243
469
  import type { ProductLoader } from "../loaders";
244
- import type { Breadcrumbs } from "../handles";
245
470
 
246
471
  function MyComponent({
247
472
  loader,
248
473
  handle,
249
474
  }: {
250
- loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
251
- handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
475
+ loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
476
+ handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
252
477
  }) {
253
- const { data } = useLoader(loader); // data is typed
254
- const crumbs = useHandle(handle); // crumbs is typed array
478
+ const { data } = useLoader(loader); // data is typed
479
+ const crumbs = useHandle(handle); // crumbs is typed array
255
480
  // ...
256
481
  }
257
482
  ```
@@ -266,6 +491,7 @@ full functionality from module-level registries.
266
491
  // location-states.ts
267
492
  import { createLocationState } from "@rangojs/router";
268
493
 
494
+ // All export patterns work: export const, const + export { X }, export { X as Y }
269
495
  export const ProductPreview = createLocationState<{
270
496
  name: string;
271
497
  price: number;
@@ -312,8 +538,8 @@ global type declarations (like `RSCRouter.Env`).
312
538
  "skipLibCheck": true,
313
539
  "isolatedModules": true,
314
540
  "esModuleInterop": true,
315
- "resolveJsonModule": true
316
- }
541
+ "resolveJsonModule": true,
542
+ },
317
543
  }
318
544
  ```
319
545
 
@@ -322,7 +548,7 @@ global type declarations (like `RSCRouter.Env`).
322
548
  {
323
549
  "extends": "../../tsconfig.base.json",
324
550
  "include": ["src"],
325
- "files": ["src/router.tsx"]
551
+ "files": ["src/router.tsx"],
326
552
  }
327
553
  ```
328
554
 
@@ -331,19 +557,27 @@ global type declarations (like `RSCRouter.Env`).
331
557
  {
332
558
  "extends": "../../tsconfig.base.json",
333
559
  "include": ["src"],
334
- "files": ["src/router.tsx"]
560
+ "files": ["src/router.tsx"],
335
561
  }
336
562
  ```
337
563
 
338
- The `files` array ensures `router.tsx` (which contains `declare global { namespace RSCRouter { ... } }`)
339
- is always included in the compilation even if nothing directly imports it. Each app gets its own
340
- typed environment without interfering with other apps.
564
+ The `files` array ensures `router.tsx` (which contains `declare global { namespace RSCRouter { interface Env; interface Vars } }`)
565
+ is always included in the compilation even if nothing directly imports it. Route types come from the
566
+ auto-generated `*.named-routes.gen.ts` file (via `rango generate`), not from manual declaration.
567
+ Each app gets its own typed environment without interfering with other apps.
341
568
 
342
569
  ## Complete Type-Safe Setup
343
570
 
344
571
  ```typescript
345
572
  // 1. env.ts - Environment types
346
- export type AppEnv = RouterEnv<AppBindings, AppVariables>;
573
+ export interface AppBindings {
574
+ DB: D1Database;
575
+ KV: KVNamespace;
576
+ }
577
+
578
+ export interface AppVariables {
579
+ user?: { id: string; email: string; role: string };
580
+ }
347
581
 
348
582
  // 2. urls.tsx - Route definitions with names
349
583
  import { urls } from "@rangojs/router";
@@ -359,35 +593,40 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
359
593
  ]),
360
594
  ]);
361
595
 
362
- // 3. router.tsx - Registration
363
- const router = createRouter<AppEnv>({
596
+ // 3. router.tsx - Create router and export reverse
597
+ const router = createRouter<AppBindings>({
364
598
  document: Document,
365
- urls: urlpatterns,
366
- });
599
+ }).routes(urlpatterns);
367
600
 
601
+ // Register bindings and variables globally for implicit typing
368
602
  declare global {
369
603
  namespace RSCRouter {
370
- interface Env extends AppEnv {}
604
+ interface Env extends AppBindings {}
605
+ interface Vars extends AppVariables {}
371
606
  }
372
607
  }
373
608
 
609
+ export const reverse = router.reverse;
374
610
  export default router;
375
611
 
376
- // 4. loaders/*.ts - Type-safe loaders
377
- export const ProductLoader = createLoader("product", async (ctx) => {
612
+ // 4. Run `npx rango generate src/router.tsx` to generate
613
+ // router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
614
+ // No manual RegisteredRoutes declaration needed.
615
+
616
+ // 5. loaders/*.ts - Type-safe loaders
617
+ export const ProductLoader = createLoader(async (ctx) => {
378
618
  // ctx.params: { slug: string }
379
- // ctx.env.Variables.user: User | undefined
380
- // ctx.env.Bindings.DB: D1Database
619
+ // ctx.get("user"): User | undefined (from RSCRouter.Vars)
620
+ // ctx.env.DB: D1Database (plain bindings from RSCRouter.Env)
381
621
  return { product: await fetchProduct(ctx.params.slug) };
382
622
  });
383
623
 
384
- // 5. Server: ctx.href for named routes
624
+ // 6. Server: ctx.reverse for named routes
385
625
  path("/product/:slug", (ctx) => {
386
- const href = scopedHref<typeof urlpatterns>(ctx.href);
387
- return <Link to={href("shop")}>Back to Shop</Link>;
626
+ return <Link to={ctx.reverse("shop")}>Back to Shop</Link>;
388
627
  }, { name: "product" })
389
628
 
390
- // 6. Client: useHref for mounted paths, href for absolute
629
+ // 7. Client: useHref for mounted paths, href for absolute
391
630
  "use client";
392
631
  import { useHref, href, Link } from "@rangojs/router/client";
393
632
  <Link to={href("/shop/product/widget")}>Widget</Link>