@rangojs/router 0.0.0-experimental.e9c0b2f2 → 0.0.0-experimental.ea9f40f2

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 (222) hide show
  1. package/AGENTS.md +6 -10
  2. package/README.md +289 -938
  3. package/dist/bin/rango.js +271 -46
  4. package/dist/vite/index.js +673 -193
  5. package/package.json +10 -8
  6. package/skills/api-client/SKILL.md +1 -1
  7. package/skills/breadcrumbs/SKILL.md +31 -14
  8. package/skills/cache-guide/SKILL.md +5 -2
  9. package/skills/caching/SKILL.md +59 -4
  10. package/skills/catalog.json +271 -0
  11. package/skills/comparison/SKILL.md +50 -0
  12. package/skills/comparison/agents/openai.yaml +4 -0
  13. package/skills/comparison/references/framework-comparison.md +837 -0
  14. package/skills/composability/SKILL.md +83 -2
  15. package/skills/debug-manifest/SKILL.md +1 -1
  16. package/skills/defer-hydration/SKILL.md +235 -0
  17. package/skills/document-cache/SKILL.md +9 -1
  18. package/skills/fonts/SKILL.md +1 -1
  19. package/skills/handler-use/SKILL.md +8 -8
  20. package/skills/hooks/SKILL.md +54 -892
  21. package/skills/hooks/data.md +273 -0
  22. package/skills/hooks/handle-and-actions.md +103 -0
  23. package/skills/hooks/navigation.md +110 -0
  24. package/skills/hooks/outlets.md +41 -0
  25. package/skills/hooks/state.md +228 -0
  26. package/skills/hooks/urls.md +135 -0
  27. package/skills/host-router/SKILL.md +4 -4
  28. package/skills/i18n/SKILL.md +1 -1
  29. package/skills/intercept/SKILL.md +46 -14
  30. package/skills/layout/SKILL.md +27 -10
  31. package/skills/links/SKILL.md +1 -1
  32. package/skills/loader/SKILL.md +23 -1
  33. package/skills/middleware/SKILL.md +7 -3
  34. package/skills/migrate-nextjs/SKILL.md +167 -6
  35. package/skills/migrate-react-router/SKILL.md +59 -677
  36. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  37. package/skills/migrate-react-router/component-migration.md +196 -0
  38. package/skills/migrate-react-router/data-and-actions.md +225 -0
  39. package/skills/migrate-react-router/route-mapping.md +271 -0
  40. package/skills/mime-routes/SKILL.md +1 -1
  41. package/skills/observability/SKILL.md +9 -1
  42. package/skills/parallel/SKILL.md +23 -4
  43. package/skills/ppr/SKILL.md +622 -0
  44. package/skills/prerender/SKILL.md +28 -18
  45. package/skills/rango/SKILL.md +84 -25
  46. package/skills/response-routes/SKILL.md +15 -1
  47. package/skills/route/SKILL.md +71 -4
  48. package/skills/router-setup/SKILL.md +14 -3
  49. package/skills/scripts/SKILL.md +1 -1
  50. package/skills/server-actions/SKILL.md +3 -2
  51. package/skills/shell-manifest/SKILL.md +185 -0
  52. package/skills/streams-and-websockets/SKILL.md +1 -1
  53. package/skills/tailwind/SKILL.md +1 -1
  54. package/skills/testing/SKILL.md +2 -1
  55. package/skills/testing/handles.md +4 -2
  56. package/skills/testing/render-handler.md +15 -14
  57. package/skills/testing/reverse-and-types.md +8 -7
  58. package/skills/theme/SKILL.md +1 -1
  59. package/skills/typesafety/SKILL.md +45 -919
  60. package/skills/typesafety/env-and-bindings.md +254 -0
  61. package/skills/typesafety/generated-files-and-cli.md +335 -0
  62. package/skills/typesafety/params-and-search.md +153 -0
  63. package/skills/typesafety/route-types.md +209 -0
  64. package/skills/use-cache/SKILL.md +30 -3
  65. package/skills/vercel/SKILL.md +1 -1
  66. package/skills/view-transitions/SKILL.md +44 -1
  67. package/src/browser/event-controller.ts +62 -10
  68. package/src/browser/logging.ts +28 -0
  69. package/src/browser/merge-segment-loaders.ts +6 -4
  70. package/src/browser/navigation-bridge.ts +65 -16
  71. package/src/browser/navigation-client.ts +32 -2
  72. package/src/browser/navigation-store.ts +128 -14
  73. package/src/browser/network-error-handler.ts +34 -7
  74. package/src/browser/partial-update.ts +76 -17
  75. package/src/browser/prefetch/cache.ts +51 -11
  76. package/src/browser/prefetch/fetch.ts +59 -21
  77. package/src/browser/prefetch/queue.ts +19 -4
  78. package/src/browser/react/Link.tsx +13 -3
  79. package/src/browser/react/NavigationProvider.tsx +108 -4
  80. package/src/browser/response-adapter.ts +38 -9
  81. package/src/browser/rsc-router.tsx +54 -4
  82. package/src/browser/scroll-restoration.ts +7 -5
  83. package/src/browser/segment-reconciler.ts +31 -21
  84. package/src/browser/server-action-bridge.ts +22 -10
  85. package/src/browser/types.ts +54 -1
  86. package/src/build/generate-manifest.ts +155 -131
  87. package/src/build/index.ts +3 -1
  88. package/src/build/route-trie.ts +35 -7
  89. package/src/build/route-types/include-resolution.ts +347 -47
  90. package/src/build/runtime-discovery.ts +4 -1
  91. package/src/cache/cache-key-utils.ts +29 -0
  92. package/src/cache/cache-runtime.ts +262 -71
  93. package/src/cache/cache-scope.ts +2 -17
  94. package/src/cache/cache-tag.ts +60 -14
  95. package/src/cache/cf/cf-cache-store.ts +243 -20
  96. package/src/cache/document-cache.ts +54 -21
  97. package/src/cache/index.ts +1 -0
  98. package/src/cache/memory-segment-store.ts +110 -3
  99. package/src/cache/profile-registry.ts +15 -0
  100. package/src/cache/read-through-swr.ts +15 -1
  101. package/src/cache/segment-codec.ts +4 -4
  102. package/src/cache/shell-snapshot.ts +417 -0
  103. package/src/cache/types.ts +158 -0
  104. package/src/cache/vercel/vercel-cache-store.ts +401 -124
  105. package/src/client.rsc.tsx +0 -3
  106. package/src/client.tsx +0 -3
  107. package/src/cloudflare/tracing.ts +7 -8
  108. package/src/defer.ts +11 -22
  109. package/src/handle.ts +37 -15
  110. package/src/handles/MetaTags.tsx +16 -82
  111. package/src/handles/breadcrumbs.ts +12 -14
  112. package/src/handles/deferred-resolution.ts +127 -0
  113. package/src/handles/is-thenable.ts +7 -8
  114. package/src/handles/meta.ts +7 -44
  115. package/src/host/errors.ts +15 -0
  116. package/src/host/index.ts +1 -0
  117. package/src/index.rsc.ts +8 -2
  118. package/src/index.ts +19 -13
  119. package/src/internal-debug.ts +11 -8
  120. package/src/prerender.ts +17 -4
  121. package/src/redirect-origin.ts +14 -0
  122. package/src/render-error-thrower.tsx +20 -0
  123. package/src/route-content-wrapper.tsx +12 -5
  124. package/src/route-definition/dsl-helpers.ts +21 -32
  125. package/src/route-definition/helper-factories.ts +0 -2
  126. package/src/route-definition/helpers-types.ts +43 -43
  127. package/src/route-definition/index.ts +1 -2
  128. package/src/route-definition/resolve-handler-use.ts +0 -1
  129. package/src/route-definition/use-item-types.ts +3 -6
  130. package/src/route-map-builder.ts +41 -4
  131. package/src/route-types.ts +0 -5
  132. package/src/router/find-match.ts +86 -8
  133. package/src/router/instrument.ts +9 -4
  134. package/src/router/lazy-includes.ts +72 -12
  135. package/src/router/loader-resolution.ts +14 -2
  136. package/src/router/manifest.ts +56 -11
  137. package/src/router/match-api.ts +76 -32
  138. package/src/router/match-handlers.ts +181 -135
  139. package/src/router/match-middleware/background-revalidation.ts +40 -23
  140. package/src/router/match-middleware/cache-store.ts +39 -24
  141. package/src/router/match-result.ts +35 -15
  142. package/src/router/middleware.ts +64 -38
  143. package/src/router/navigation-snapshot.ts +7 -5
  144. package/src/router/parse-pattern.ts +115 -0
  145. package/src/router/pattern-matching.ts +53 -64
  146. package/src/router/prefetch-limits.ts +37 -0
  147. package/src/router/prerender-match.ts +11 -5
  148. package/src/router/preview-match.ts +3 -1
  149. package/src/router/request-classification.ts +23 -8
  150. package/src/router/route-snapshot.ts +14 -2
  151. package/src/router/router-context.ts +3 -1
  152. package/src/router/router-interfaces.ts +32 -1
  153. package/src/router/router-options.ts +30 -0
  154. package/src/router/segment-resolution/fresh.ts +39 -3
  155. package/src/router/segment-resolution/loader-cache.ts +93 -2
  156. package/src/router/segment-resolution/loader-mask.ts +60 -0
  157. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  158. package/src/router/segment-resolution/mask-nested.ts +83 -0
  159. package/src/router/segment-resolution/revalidation.ts +3 -0
  160. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  161. package/src/router/substitute-pattern-params.ts +54 -35
  162. package/src/router/telemetry-otel.ts +6 -8
  163. package/src/router/telemetry.ts +9 -1
  164. package/src/router/tracing.ts +14 -5
  165. package/src/router/trie-matching.ts +19 -11
  166. package/src/router/url-params.ts +13 -0
  167. package/src/router.ts +47 -16
  168. package/src/rsc/full-payload.ts +70 -0
  169. package/src/rsc/handler.ts +60 -33
  170. package/src/rsc/manifest-init.ts +1 -1
  171. package/src/rsc/nonce.ts +10 -1
  172. package/src/rsc/progressive-enhancement.ts +61 -4
  173. package/src/rsc/redirect-guard.ts +2 -1
  174. package/src/rsc/rsc-rendering.ts +429 -37
  175. package/src/rsc/server-action.ts +25 -2
  176. package/src/rsc/shell-capture.ts +1190 -0
  177. package/src/rsc/shell-serve.ts +181 -0
  178. package/src/rsc/transition-gate.ts +89 -0
  179. package/src/rsc/types.ts +30 -0
  180. package/src/segment-loader-promise.ts +18 -0
  181. package/src/segment-system.tsx +149 -14
  182. package/src/server/context.ts +67 -9
  183. package/src/server/cookie-store.ts +73 -1
  184. package/src/server/loader-registry.ts +13 -1
  185. package/src/server/request-context.ts +169 -10
  186. package/src/ssr/index.tsx +462 -178
  187. package/src/ssr/inject-rsc-eager.ts +167 -0
  188. package/src/ssr/ssr-root.tsx +228 -0
  189. package/src/testing/collect-handle.ts +14 -8
  190. package/src/testing/dispatch.ts +152 -40
  191. package/src/testing/generated-routes.ts +27 -11
  192. package/src/testing/index.ts +6 -0
  193. package/src/testing/render-handler.ts +14 -0
  194. package/src/testing/render-route.tsx +13 -10
  195. package/src/testing/run-transition-when.ts +164 -0
  196. package/src/theme/ThemeProvider.tsx +36 -26
  197. package/src/types/handler-context.ts +1 -1
  198. package/src/types/index.ts +2 -0
  199. package/src/types/route-config.ts +19 -7
  200. package/src/types/segments.ts +100 -0
  201. package/src/urls/include-helper.ts +10 -8
  202. package/src/urls/include-provider.ts +71 -0
  203. package/src/urls/index.ts +1 -0
  204. package/src/urls/path-helper-types.ts +44 -12
  205. package/src/urls/path-helper.ts +5 -0
  206. package/src/urls/pattern-types.ts +36 -0
  207. package/src/urls/type-extraction.ts +43 -18
  208. package/src/urls/urls-function.ts +0 -1
  209. package/src/vercel/tracing.ts +7 -7
  210. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  211. package/src/vite/discovery/discover-routers.ts +1 -1
  212. package/src/vite/discovery/discovery-errors.ts +61 -0
  213. package/src/vite/index.ts +7 -0
  214. package/src/vite/inject-client-debug.ts +88 -0
  215. package/src/vite/plugins/vercel-output.ts +114 -25
  216. package/src/vite/plugins/version-injector.ts +22 -7
  217. package/src/vite/plugins/virtual-entries.ts +80 -22
  218. package/src/vite/rango.ts +29 -19
  219. package/src/vite/router-discovery.ts +171 -43
  220. package/src/vite/utils/prerender-utils.ts +17 -4
  221. package/src/vite/utils/shared-utils.ts +47 -0
  222. package/src/network-error-thrower.tsx +0 -18
@@ -1,926 +1,52 @@
1
1
  ---
2
2
  name: typesafety
3
- description: Set up type-safe routes, params, and environment types in @rangojs/router
3
+ description: Set up type-safe routes, params, and environment types in @rangojs/router. Use when route or search params aren't typed, TypeScript can't infer a loader's return type, or wiring up typed environment bindings.
4
4
  argument-hint: [setup]
5
5
  ---
6
6
 
7
7
  # Type Safety Setup
8
8
 
9
- @rangojs/router provides end-to-end type safety for routes, parameters, and environment.
10
-
11
- ## Router Setup
12
-
13
- ```typescript
14
- // router.tsx
15
- import { createRouter } from "@rangojs/router";
16
- import { urlpatterns } from "./urls";
17
-
18
- const router = createRouter<AppBindings>({
19
- document: Document,
20
- }).routes(urlpatterns);
21
-
22
- // Server-side named-route reverse (type-safe via routeMap)
23
- export const reverse = router.reverse;
24
-
25
- export default router;
26
- ```
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">` (type annotation), `Prerender<"name">(...)` (function
36
- call with type arg for param inference), server `ctx.reverse()`, and
37
- named-route param/search inference.
38
- - `typeof router.routeMap` the real merged route map from your router
39
- instance, including response-route metadata such as `{ path, response }`.
40
- - `RegisteredRoutes` manual global hook for exposing `typeof router.routeMap`
41
- to global utilities that need the exact router-builder map, especially
42
- `Rango.PathResponse`.
43
-
44
- ### Generated Route Type Surfaces
45
-
46
- There are three distinct typing surfaces. They are **not** interchangeable —
47
- pick the one that matches what you need to type:
48
-
49
- | Surface | Source | Scope | Gives | Does not give |
50
- | ------------------- | ---------------------------------------- | ------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
51
- | `GeneratedRouteMap` | `router.named-routes.gen.ts` (auto) | global | route names, path params, search schemas | response/MIME payloads |
52
- | `routes` | per-module `*.gen.ts` (`rango generate`) | local | local names, params, search | the global app map |
53
- | `RegisteredRoutes` | manual `extends typeof router.routeMap` | global | paths, params, **response payloads** | the `Handler`/`Prerender` default (those read `GeneratedRouteMap` to avoid a `router.tsx` cycle) |
54
-
55
- Key consequence: `href()` and the ambient `Rango.Path` type are typed from
56
- whichever map is present — they prefer `RegisteredRoutes` when you wire it, otherwise fall back to
57
- the auto-generated `GeneratedRouteMap`, so **`rango generate` alone gives you
58
- path-checked `href()`** with no manual augmentation. Response and MIME payload
59
- inference is the exception: it comes only from `typeof router.routeMap` (via
60
- `RegisteredRoutes`), because `GeneratedRouteMap` carries paths + search but no
61
- payloads — so `Rango.PathResponse` resolves to `never` until you wire
62
- `RegisteredRoutes`.
63
-
64
- Recommended setup:
65
-
66
- ```typescript
67
- // router.tsx
68
- import { createRouter } from "@rangojs/router";
69
- import { urlpatterns } from "./urls";
70
- import type { AppBindings, AppVars } from "./env";
71
-
72
- export const router = createRouter<AppBindings>({}).routes(urlpatterns);
73
-
74
- declare global {
75
- namespace Rango {
76
- interface Env extends AppBindings {}
77
- interface Vars extends AppVars {}
78
- interface RegisteredRoutes extends typeof router.routeMap {}
79
- }
80
- }
81
- ```
82
-
83
- ### Single-App Setup Checklist
84
-
85
- For one app, keep the ambient types, generated named-routes file, and router
86
- instance in the same TypeScript program:
87
-
88
- ```jsonc
89
- // tsconfig.json
90
- {
91
- "compilerOptions": {
92
- "strict": true,
93
- "moduleResolution": "bundler",
94
- "jsx": "react-jsx",
95
- "noEmit": true,
96
- },
97
- "include": ["src"],
98
- "files": ["src/router.tsx"],
99
- }
100
- ```
101
-
102
- Then generate the route types from the router file:
103
-
104
- ```bash
105
- npx rango generate src/router.tsx
106
- ```
107
-
108
- This creates `src/router.named-routes.gen.ts`, which augments
109
- `Rango.GeneratedRouteMap`. Keep that generated file committed with the router
110
- source. The `files` entry keeps `router.tsx` in the program even when nothing
111
- imports it directly, so `Rango.Env`, `Rango.Vars`, and optional
112
- `Rango.RegisteredRoutes` augmentation are visible to handlers, loaders, actions,
113
- and client helpers.
114
-
115
- ### Named Routes, `$$routeNames`, And `router.routeMap`
116
-
117
- There are two runtime/type surfaces with similar names:
118
-
119
- - `router.named-routes.gen.ts` exports `NamedRoutes` and augments
120
- `Rango.GeneratedRouteMap`. The Vite plugin imports that file internally and
121
- injects it as `$$routeNames` so `router.reverse` has the static route-name map.
122
- App code should not pass or import `$$routeNames` directly.
123
- - `router.routeMap` is the public router instance property for type extraction.
124
- Use `typeof router.routeMap` when augmenting `Rango.RegisteredRoutes` for
125
- global response payload helpers such as `Rango.PathResponse`.
126
-
127
- Do not document or use a public `router.routeNames` API unless one is
128
- intentionally added. Today, the public extraction surface is `router.routeMap`;
129
- the generated file and `$$routeNames` are build machinery.
130
-
131
- ## Route Definition with Type-Safe Names
132
-
133
- ```typescript
134
- // urls.tsx
135
- import { urls } from "@rangojs/router";
136
-
137
- export const urlpatterns = urls(({ path, layout }) => [
138
- path("/", HomePage, { name: "home" }),
139
- path("/products", ProductsPage, { name: "products" }),
140
- path("/product/:slug", ProductPage, { name: "product" }),
141
- path("/cart", CartPage, { name: "cart" }),
142
- path("/checkout/:step?", CheckoutPage, { name: "checkout" }),
143
- ]);
144
-
145
- // Route names are inferred from the { name } option
146
- ```
147
-
148
- ## Type-Safe href()
149
-
150
- ### Server: ctx.reverse with route names
151
-
152
- In route handlers, `ctx.reverse()` uses two namespaces:
153
-
154
- - **`.name`** — local route, resolved within the current `include()` scope
155
- - **`name`** — global route, from the named-routes definition
156
-
157
- ```typescript
158
- import type { Handler } from "@rangojs/router";
159
-
160
- export const ProductHandler: Handler<"shop.product"> = (ctx) => {
161
- ctx.reverse(".cart"); // Local: /shop/cart
162
- ctx.reverse(".product", { slug: "widget" }); // Local: /shop/product/widget
163
- ctx.reverse("blog.post", { slug: "1" }); // Global: /blog/1
164
- };
165
- ```
166
-
167
- For type-safe local names, generate a route types file with `npx rango generate urls/shop.tsx`
168
- and pass it as the second generic to `Handler` or `Prerender`:
169
-
170
- ```typescript
171
- import type { Handler } from "@rangojs/router";
172
- import type { routes } from "./shop.gen.js";
173
-
174
- export const ProductHandler: Handler<"shop.product", routes> = (ctx) => {
175
- ctx.reverse(".cart"); // Type-safe local name
176
- ctx.reverse(".product", { slug: "widget" }); // Type-safe local with params
177
- ctx.reverse("blog.post", { slug: "hi" }); // Type-safe global name
178
- };
179
- ```
180
-
181
- ### Client: href + useHref
182
-
183
- On the client, `href()` validates paths against registered route patterns at compile time:
184
-
185
- ```typescript
186
- "use client";
187
- import { href, useHref, Link } from "@rangojs/router/client";
188
-
189
- // href() validates absolute paths via PatternToPath types
190
- href("/about"); // Valid path
191
- href("/blog/hello"); // Matches /blog/:slug
192
-
193
- // useHref() auto-prefixes with include() mount
194
- function ShopNav() {
195
- const href = useHref();
196
- return <Link to={href("/cart")}>Cart</Link>; // "/shop/cart"
197
- }
198
- ```
199
-
200
- `href()` and the `Rango.Path` type read from `RegisteredRoutes` when you augment
201
- it, otherwise from the auto-generated `GeneratedRouteMap` — so `rango generate`
202
- alone type-checks `href()` paths with no manual augmentation. The augmentation
203
- below is only needed for **`Rango.PathResponse`** (response-payload inference), which
204
- `GeneratedRouteMap` cannot provide:
205
-
206
- ```typescript
207
- declare global {
208
- namespace Rango {
209
- interface RegisteredRoutes extends typeof router.routeMap {}
210
- }
211
- }
212
- ```
213
-
214
- For wrapper helpers, type the path parameter as `Rango.Path`. It is ambient (no
215
- import) and shares `href()`'s compile-time path checking, so a wrapper stays in
216
- sync with your routes automatically:
217
-
218
- ```typescript
219
- import { href } from "@rangojs/router/client";
220
-
221
- export const appHref = (path: Rango.Path): string => href(path);
222
- ```
223
-
224
- For response-route payloads, `Rango.PathResponse<T>` is the ambient lookup. It
225
- accepts a route _pattern_ **or** a concrete path, so it also serves as the return
226
- type of a typed `fetch` wrapper. It only resolves once `RegisteredRoutes` carries
227
- response metadata:
228
-
229
- ```typescript
230
- import { href } from "@rangojs/router/client";
231
-
232
- type Product = Rango.PathResponse<"/api/products/:id">; // by pattern
233
- type Same = Rango.PathResponse<"/api/products/42">; // by concrete path
234
-
235
- // Response inferred from the concrete path passed in:
236
- async function get<T extends Rango.Path>(
237
- path: T,
238
- ): Promise<Rango.PathResponse<T>> {
239
- return fetch(href(path)).then((r) => r.json());
240
- }
241
- const product = await get("/api/products/42"); // Product (bare value)
242
- ```
243
-
244
- Pattern keys (`/:id`) match exactly; a concrete path under a _nested_ dynamic
245
- route can match several patterns and union their responses.
246
-
247
- `Rango.PathResponse` describes the JSON **wire** shape, not the handler's raw
248
- return. A `path.json()` handler returning `{ createdAt: Date }` resolves here to
249
- `{ createdAt: string }` (bare value), matching what `r.json()` yields. This
250
- is applied via the ambient `Rango.JsonSerialize<T>` transform (`Date -> string`,
251
- honors `toJSON()`, drops functions/`undefined`, `bigint -> never`). A separate
252
- `Rango.FlightSerialize<T>` models the higher-fidelity RSC Flight boundary
253
- (loaders / RSC props, where `Date` is preserved) — do **not** use it for
254
- `path.json()`.
255
-
256
- ### Overriding serialization globally
257
-
258
- For your own types, the zero-config way to control the JSON wire shape is a
259
- `toJSON()` method — `Rango.JsonSerialize` honors it, and it matches the runtime
260
- exactly (`JSON.stringify` calls `toJSON()`):
261
-
262
- ```typescript
263
- class Money {
264
- constructor(private cents: number) {}
265
- toJSON(): number {
266
- return this.cents;
267
- }
268
- }
269
- // Rango.JsonSerialize<Money> is number; Rango.PathResponse reflects it.
270
- ```
271
-
272
- To override a transform for types you **don't** own (or for the Flight boundary,
273
- which has no `toJSON()`), augment its override slot. Because `Rango.JsonSerialize`
274
- / `Rango.FlightSerialize` are type _aliases_ (TS can't merge those), you provide a
275
- single member that is your **complete** transform, delegating to the built-in for
276
- the cases you don't change:
277
-
278
- ```typescript
279
- declare global {
280
- namespace Rango {
281
- interface JsonSerializeOverride<T> {
282
- app: T extends Decimal ? string : Rango.JsonSerializeBuiltin<T>;
283
- }
284
- interface FlightSerializeOverride<T> {
285
- app: T extends Money ? number : Rango.FlightSerializeBuiltin<T>;
286
- }
287
- }
288
- }
289
- // Rango.JsonSerialize<Decimal> -> string; Rango.FlightSerialize<Money> -> number;
290
- // everything else stays on the built-in, recursively (nested fields too).
291
- ```
292
-
293
- Rules: provide **exactly one** member (the slot is read as
294
- `Override<T>[keyof Override<T>]`, so multiple members union and conflict).
295
- Overrides win over `toJSON()` and apply at every nesting level. Caveat for JSON:
296
- the `path.json()` runtime is plain `JSON.stringify`, which only honors `toJSON()`,
297
- so a `JsonSerializeOverride` that disagrees with what the runtime emits will lie —
298
- prefer `toJSON()` for your own types and use the slot only for types you can't
299
- modify.
300
-
301
- See `/links` for full URL generation guide.
302
-
303
- ## Environment Type Setup
304
-
305
- Define your app's environment for type-safe bindings and variables:
306
-
307
- ```typescript
308
- // env.ts
309
-
310
- // Cloudflare bindings — passed as TEnv to createRouter<TEnv>()
311
- export interface AppBindings {
312
- DB: D1Database;
313
- KV: KVNamespace;
314
- CACHE: KVNamespace;
315
- AI: Ai;
316
- }
317
-
318
- // Variables set by middleware — declared via global namespace augmentation
319
- export interface AppVariables {
320
- user?: { id: string; email: string; role: string };
321
- requestId?: string;
322
- permissions?: string[];
323
- }
324
- ```
325
-
326
- ### Using Environment Types
327
-
328
- ```typescript
329
- // router.tsx
330
- import type { AppBindings, AppVariables } from "./env";
331
-
332
- const router = createRouter<AppBindings>({
333
- document: Document,
334
- }).routes(urlpatterns);
335
-
336
- // Register bindings and variables globally for implicit typing
337
- declare global {
338
- namespace Rango {
339
- interface Env extends AppBindings {}
340
- interface Vars extends AppVariables {}
341
- }
342
- }
343
-
344
- // middleware - typed via ctx.set / ctx.get
345
- import type { Middleware } from "@rangojs/router";
346
-
347
- export const authMiddleware: Middleware = async (ctx, next) => {
348
- ctx.set("user", {
349
- id: "123",
350
- email: "user@example.com",
351
- role: "admin",
352
- });
353
- await next();
354
- };
355
-
356
- // loaders - typed context
357
- export const UserLoader = createLoader(async (ctx) => {
358
- const db = ctx.env.DB; // D1Database (plain bindings)
359
- const userId = ctx.get("user")?.id; // from Rango.Vars
360
- return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
361
- });
362
- ```
363
-
364
- ## Global Environment Registration
365
-
366
- Register environment types globally for implicit typing:
367
-
368
- ```typescript
369
- // router.tsx
370
- declare global {
371
- namespace Rango {
372
- interface Env extends AppBindings {}
373
- interface Vars extends AppVariables {}
374
- }
375
- }
376
- ```
377
-
378
- Now handlers have typed context without explicit imports:
379
-
380
- ```typescript
381
- // In loaders
382
- export const DashboardLoader = createLoader(async (ctx) => {
383
- // ctx.env.DB is typed from global Rango.Env
384
- // ctx.get("user") is typed from global Rango.Vars
385
- const user = ctx.get("user");
386
- return { user };
387
- });
388
- ```
389
-
390
- ## Typed Search Params
391
-
392
- Add a `search` schema to `path()` options for type-safe query parameters:
393
-
394
- ```typescript
395
- // Route definition with search schema
396
- path("/search", SearchPage, {
397
- name: "search",
398
- search: { q: "string", page: "number?", sort: "string?" },
399
- });
400
- ```
401
-
402
- ### Handler with typed search params
403
-
404
- `Handler<"name">` automatically resolves route params and search params from the
405
- global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
406
-
407
- ```typescript
408
- // pages/search.tsx
409
- import type { Handler } from "@rangojs/router";
410
-
411
- export const SearchPage: Handler<"search"> = (ctx) => {
412
- // ctx.search is typed: { q: string; page?: number; sort?: string }
413
- const { q, page, sort } = ctx.search;
414
- return <SearchResults q={q} page={page} sort={sort} />;
415
- };
416
- ```
417
-
418
- This avoids circular references because `Handler` defaults to `GeneratedRouteMap`
419
- (from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
420
-
421
- You can also pass an explicit route map for per-module isolation (opt-in,
422
- after running `npx rango generate`). With a local map, the route name is
423
- **dot-prefixed** so params and search resolve from `routes`, not the global map:
424
-
425
- ```typescript
426
- import type { Handler } from "@rangojs/router";
427
- import type { routes } from "./urls.gen.js";
428
-
429
- export const SearchPage: Handler<".search", routes> = (ctx) => { ... };
430
- ```
431
-
432
- Note the difference: `Handler<"search">` (no dot) resolves against the global
433
- `GeneratedRouteMap`; `Handler<".search", routes>` resolves against the local
434
- `routes` map. Mixing them — `Handler<"search", routes>` — silently ignores
435
- `routes` for param/search inference and only uses it for local `ctx.reverse(".x")`.
436
-
437
- Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
438
- Values are automatically coerced from query string (e.g., `"2"` becomes `2` for numbers).
439
- Routes without a `search` schema keep the standard `URLSearchParams` behavior.
440
-
441
- ### RouteSearchParams and RouteParams utility types
442
-
443
- Extract typed params by route name for use in component props, return types, or anywhere:
444
-
445
- ```typescript
446
- import type { RouteSearchParams, RouteParams } from "@rangojs/router";
447
-
448
- // RouteSearchParams<"name"> resolves the search schema to a typed object
449
- type SP = RouteSearchParams<"search">;
450
- // { q: string | undefined; page?: number; sort?: string }
451
-
452
- // RouteParams<"name"> resolves URL params from the route pattern
453
- type P = RouteParams<"blogPost">;
454
- // { slug: string }
455
-
456
- // Optional URL params (`:slug?`) resolve to `string | undefined`
457
- // because absent segments are omitted from `ctx.params` at runtime.
458
- type C = RouteParams<"checkout">;
459
- // { step?: string }
460
- // → ctx.params.step is `string | undefined`; use `?? "default"` to coalesce.
461
-
462
- // Use in component props
463
- interface SearchResultsProps {
464
- params: RouteSearchParams<"search">;
465
- }
466
- ```
467
-
468
- Both default to the global route map (`RegisteredRoutes` or `GeneratedRouteMap`).
469
- Pass an explicit route map as the second type argument when needed:
470
-
471
- ```typescript
472
- import type { routes } from "./urls.gen.js";
473
-
474
- type SP = RouteSearchParams<"search", routes>;
475
- type P = RouteParams<"blogPost", routes>;
476
- ```
477
-
478
- ### Generated route types
479
-
480
- In the generated `router.named-routes.gen.ts`, routes with search schemas
481
- use `{ path, search }` objects:
482
-
483
- ```typescript
484
- // router.named-routes.gen.ts (auto-generated)
485
- export const NamedRoutes = {
486
- "search.index": {
487
- path: "/search",
488
- search: { q: "string", page: "number?", sort: "string?" },
489
- },
490
- "home.index": "/", // No search schema -> plain string
491
- } as const;
492
- ```
493
-
494
- You never open a `.gen.ts` by hand. Treat the generated types as call-site
495
- honesty checks, not modules to read:
496
-
497
- - **Do not import `router.named-routes.gen.ts` directly**, and don't reach for
498
- `Rango.GeneratedRouteMap`. It is the whole-app manifest, auto-wired
499
- globally — `Handler<"name">` and `ctx.reverse("name")` already see it.
500
- - **Per-module `*.gen.ts` imports are fine** — they are the opt-in local-route
501
- pattern for `useReverse(routes)` and explicit local handler typing
502
- (`Handler<".name", routes>`). See `/links`.
503
-
504
- If a type error points at a generated map instead of your call site, that's a
505
- smell — fix the call site (or regenerate), never edit the generated file.
506
-
507
- ## Loader Type Safety
508
-
509
- Loaders have typed return values:
510
-
511
- ```typescript
512
- // loaders/product.ts
513
- export const ProductLoader = createLoader(async (ctx) => {
514
- return {
515
- id: ctx.params.slug,
516
- name: "Widget",
517
- price: 99,
518
- };
519
- });
520
-
521
- // In server component - type is inferred
522
- import { useLoader } from "@rangojs/router/client";
523
-
524
- async function ProductPage() {
525
- const product = await useLoader(ProductLoader);
526
- // product: { id: string; name: string; price: number }
527
- return <h1>{product.name}</h1>;
528
- }
529
-
530
- // In client component - same type
531
- "use client";
532
- import { useLoader } from "@rangojs/router/client";
533
-
534
- function ProductPrice() {
535
- const { data } = useLoader(ProductLoader);
536
- // data: { id: string; name: string; price: number }
537
- const product = data;
538
- return <span>${product.price}</span>;
539
- }
540
- ```
541
-
542
- ## Typed Context Variables
543
-
544
- `createVar<T>()` creates a typed token for `ctx.set()`/`ctx.get()`, making
545
- handler-to-layout data contracts explicit and compile-time verified:
546
-
547
- ```typescript
548
- import { createVar } from "@rangojs/router";
549
-
550
- // Define a typed token (shared between producer and consumer)
551
- interface PaginationData {
552
- current: number;
553
- total: number;
554
- perPage: number;
555
- }
556
- export const Pagination = createVar<PaginationData>();
557
-
558
- // Non-cacheable var — reading inside cache() or "use cache" throws at runtime
559
- const Session = createVar<SessionData>({ cache: false });
560
- ```
561
-
562
- `createVar` accepts an optional options object. The `cache` option (default
563
- `true`) controls whether the var's values can be read inside cache scopes.
564
- Write-level escalation is also supported: `ctx.set(Var, value, { cache: false })`
565
- marks a specific write as non-cacheable even if the var itself is cacheable.
566
- "Least cacheable wins" — if either says `cache: false`, the value throws on
567
- read inside `cache()` or `"use cache"`.
568
-
569
- ### Producer (handler or middleware)
570
-
571
- ```typescript
572
- import { Pagination } from "../vars/pagination.js";
573
-
574
- const ArticleList: Handler<"articles.list"> = async (ctx) => {
575
- ctx.set(Pagination, { // type-checked
576
- current: 1,
577
- total: 10,
578
- perPage: 5,
579
- });
580
- return <Articles />;
581
- };
582
- ```
583
-
584
- ### Consumer (layout, parallel, or any context with get)
585
-
586
- ```typescript
587
- import { Pagination } from "../vars/pagination.js";
588
-
589
- export function PaginationLayout(ctx: any) {
590
- const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
591
- if (!pagination) return <Outlet />;
592
- return <nav>Page {pagination.current} of {pagination.total}</nav>;
593
- }
594
- ```
595
-
596
- ### Why not just use Rango.Vars?
597
-
598
- `Rango.Vars` (via global namespace augmentation) provides app-global typing for
599
- `ctx.get("key")` / `ctx.set("key", value)`. It works for middleware state
600
- shared app-wide. `createVar<T>()` is for route-local or feature-scoped
601
- context -- the producer and consumer import the same token, creating a
602
- scoped contract without polluting global types.
603
-
604
- Both approaches coexist: `ctx.get("user")` (global via Vars) and
605
- `ctx.get(Pagination)` (scoped via createVar) work side by side.
606
-
607
- ## Handle Type Safety
608
-
609
- Handles have typed data:
610
-
611
- ```typescript
612
- // Built-in Breadcrumbs handle — import from "@rangojs/router"
613
- import { Breadcrumbs } from "@rangojs/router";
614
- // Type: Handle<BreadcrumbItem, BreadcrumbItem[]>
615
- // BreadcrumbItem: { label: string; href: string; content?: ReactNode | Promise<ReactNode> }
616
-
617
- // In route handler — push is fully typed
618
- path("/shop/product/:slug", (ctx) => {
619
- const breadcrumb = ctx.use(Breadcrumbs);
620
- breadcrumb({ label: "Products", href: "/shop/products" });
621
- return <ProductPage />;
622
- }, { name: "product" });
623
-
624
- // In client — typed array
625
- import { useHandle, Breadcrumbs } from "@rangojs/router/client";
626
- function BreadcrumbNav() {
627
- const crumbs = useHandle(Breadcrumbs);
628
- // crumbs: BreadcrumbItem[]
629
- }
630
-
631
- // Custom handles also work the same way
632
- import { createHandle } from "@rangojs/router";
633
- export const PageTitle = createHandle<string, string>(
634
- (segments) => segments.flat().at(-1) ?? "Default Title"
635
- );
636
- ```
637
-
638
- ## Ref Prop Type Safety (Loaders & Handles)
639
-
640
- Loaders and handles can be passed as props from server to client components.
641
- Use `typeof` to get the full typed definition without manually specifying generics:
642
-
643
- ```typescript
644
- // loaders.ts
645
- export const ProductLoader = createLoader(async (ctx) => {
646
- return { product: await fetchProduct(ctx.params.slug) };
647
- });
648
-
649
- // Built-in Breadcrumbs — or any custom handle created with createHandle()
650
- ```
651
-
652
- ```tsx
653
- // Client component — typeof infers all generics
654
- "use client";
655
- import { useLoader, useHandle, type Breadcrumbs } from "@rangojs/router/client";
656
- import type { ProductLoader } from "../loaders";
657
-
658
- function MyComponent({
659
- loader,
660
- handle,
661
- }: {
662
- loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
663
- handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
664
- }) {
665
- const { data } = useLoader(loader); // data is typed
666
- const crumbs = useHandle(handle); // crumbs is typed array
667
- // ...
668
- }
669
- ```
670
-
671
- RSC Flight serialization calls `toJSON()` on both loaders and handles,
672
- sending only `{ __brand, $$id }` to the client. The hooks recover the
673
- full functionality from module-level registries.
674
-
675
- ## Stable identity: `path#export`
676
-
677
- Loaders, handles, cached functions (`functionId`), and server actions
678
- (`actionId`) all share one identity scheme: `{modulePath}#{exportName}`,
679
- injected at build by the `exposeInternalIds` and `exposeActionId` Vite plugins.
680
- This is also the identity React server actions carry across the Flight boundary,
681
- which is why a `revalidate()` predicate sees an action as a `path#export` string:
682
-
683
- ```typescript
684
- revalidate(
685
- ({ actionId }) => actionId === "src/actions/cart.ts#addToCart" || undefined,
686
- );
687
- ```
688
-
689
- `actionId` is the only stable reference React exposes across the Flight boundary,
690
- so it stays as the floor and escape hatch. The hand-written-string surface
691
- (`actionId?.includes("cart.ts#")`) is brittle: a renamed action or moved file
692
- silently stops matching with no compile error. Prefer **`ctx.isAction()`** in a
693
- revalidate predicate — it resolves the action's id from an imported reference, so
694
- a rename is a type error in one place instead of silent drift:
695
-
696
- ```ts
697
- import { addToCart, removeFromCart } from "./actions/cart";
698
- import * as CartActions from "./actions/cart";
699
-
700
- revalidate((ctx) => ctx.isAction(addToCart) || undefined); // one action
701
- revalidate((ctx) => ctx.isAction(addToCart, removeFromCart) || undefined); // several
702
- revalidate((ctx) => ctx.isAction(CartActions) || undefined); // any action in the module
703
- ```
704
-
705
- `ctx.isAction()` (only available on the revalidate predicate's context) returns a
706
- raw boolean — combine with `|| undefined` for the "revalidate on match, else
707
- defer" intent. It resolves the reference the same way the router derives
708
- `actionId` (`$id` in production, `$$id` in dev), so matching
709
- works in both modes. `actionId` stays available for advanced cases.
710
-
711
- ## Location State Type Safety
712
-
713
- ```typescript
714
- // location-states.ts
715
- import { createLocationState } from "@rangojs/router";
716
-
717
- // All export patterns work: export const, const + export { X }, export { X as Y }
718
- export const ProductPreview = createLocationState<{
719
- name: string;
720
- price: number;
721
- image: string;
722
- }>();
723
-
724
- // Passing state through Link
725
- <Link
726
- to={href("product", { slug: "widget" })}
727
- state={[ProductPreview({ name: "Widget", price: 99, image: "/img.jpg" })]}
728
- >
729
- View Product
730
- </Link>
731
-
732
- // Reading state in component
733
- function ProductHeader() {
734
- const preview = useLocationState(ProductPreview);
735
- // preview: { name: string; price: number; image: string } | undefined
736
-
737
- if (preview) {
738
- return <h1>{preview.name} - ${preview.price}</h1>;
739
- }
740
- return <h1>Loading...</h1>;
741
- }
742
- ```
743
-
744
- ## Multi-Project tsconfig Setup
745
-
746
- For monorepos or multi-app setups, each app should have its own TypeScript
747
- program. Do not typecheck two Rango apps with different `Rango.Env`,
748
- `Rango.Vars`, or `Rango.RegisteredRoutes` declarations in one tsconfig, because
749
- ambient global interfaces merge across the whole program.
750
-
751
- ### Multiple routers in one program
752
-
753
- `Rango.GeneratedRouteMap` is a **single global interface**. Each router's
754
- generated `router.named-routes.gen.ts` augments it, so two routers in the **same
755
- TS program** that define overlapping route names (e.g. both have a `home`) make
756
- the augmentations collide:
757
-
758
- ```text
759
- Interface 'GeneratedRouteMap' cannot simultaneously extend ...
760
- Named property 'home' ... are not identical.
761
- ```
762
-
763
- This is the multi-router / host-router case. Resolve it by:
764
-
765
- - **Separate TS programs** — give each router its own tsconfig (as below) so only
766
- one generated map is in scope per program. Recommended.
767
- - **Unique route-name prefixes** — name routes per router (`appA.home`,
768
- `appB.home`) so the merged global map has no duplicate keys.
769
-
770
- A single global generated map is a single-router convenience; global named-route
771
- typing across multiple routers in one program is not supported today (it would
772
- need per-router scoping in the generated map).
773
-
774
- Use a shared base tsconfig for common compiler options, then make every app
775
- tsconfig include its own source tree, its own `router.tsx`, and the generated
776
- `router.named-routes.gen.ts` that lives beside that router.
777
-
778
- ```jsonc
779
- // tsconfig.base.json (root)
780
- {
781
- "compilerOptions": {
782
- "target": "ES2022",
783
- "module": "ESNext",
784
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
785
- "jsx": "react-jsx",
786
- "moduleResolution": "bundler",
787
- "strict": true,
788
- "noEmit": true,
789
- "skipLibCheck": true,
790
- "isolatedModules": true,
791
- "esModuleInterop": true,
792
- "resolveJsonModule": true,
793
- },
794
- }
795
- ```
796
-
797
- ```jsonc
798
- // apps/shop/tsconfig.json
799
- {
800
- "extends": "../../tsconfig.base.json",
801
- "include": ["src"],
802
- "files": ["src/router.tsx"],
803
- }
804
- ```
805
-
806
- ```jsonc
807
- // apps/blog/tsconfig.json
808
- {
809
- "extends": "../../tsconfig.base.json",
810
- "include": ["src"],
811
- "files": ["src/router.tsx"],
812
- }
813
- ```
814
-
815
- Run generation per app:
816
-
817
- ```bash
818
- npx rango generate apps/shop/src/router.tsx
819
- npx rango generate apps/blog/src/router.tsx
820
- ```
821
-
822
- If an app has multiple tsconfigs (`tsconfig.app.json`, `tsconfig.test.json`,
823
- `tsconfig.worker.json`), every tsconfig that typechecks Rango handlers,
824
- components, loaders, actions, or client navigation must see the same app-local
825
- type surfaces:
826
-
827
- ```jsonc
828
- // apps/shop/tsconfig.test.json
829
- {
830
- "extends": "./tsconfig.json",
831
- "include": ["src", "tests"],
832
- "files": ["src/router.tsx"],
833
- }
834
- ```
835
-
836
- The `files` array ensures `router.tsx` is always included even if nothing
837
- directly imports it. The generated `router.named-routes.gen.ts` is normally
838
- covered by `include: ["src"]`; if a tsconfig uses a narrow `include`, add the
839
- generated file explicitly. Each app gets its own typed environment and named
840
- route map without interfering with other apps.
841
-
842
- For response and MIME payload lookup in each app, augment `RegisteredRoutes`
843
- inside that app's router file:
844
-
845
- ```typescript
846
- // apps/shop/src/router.tsx
847
- export const router = createRouter<ShopEnv>({ document: Document }).routes(
848
- urlpatterns,
849
- );
850
-
851
- declare global {
852
- namespace Rango {
853
- interface Env extends ShopEnv {}
854
- interface RegisteredRoutes extends typeof router.routeMap {}
855
- }
856
- }
857
- ```
858
-
859
- ## Complete Type-Safe Setup
860
-
861
- ```typescript
862
- // 1. env.ts - Environment types
863
- export interface AppBindings {
864
- DB: D1Database;
865
- KV: KVNamespace;
866
- }
867
-
868
- export interface AppVariables {
869
- user?: { id: string; email: string; role: string };
870
- }
871
-
872
- // 2. urls.tsx - Route definitions with names
873
- import { urls } from "@rangojs/router";
874
-
875
- export const urlpatterns = urls(({ path, layout, loader }) => [
876
- path("/", HomePage, { name: "home" }),
877
-
878
- layout(<ShopLayout />, () => [
879
- path("/shop", ShopIndex, { name: "shop" }),
880
- path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
881
- loader(ProductLoader),
882
- ]),
883
- ]),
884
- ]);
885
-
886
- // 3. router.tsx - Create router and export reverse
887
- const router = createRouter<AppBindings>({
888
- document: Document,
889
- }).routes(urlpatterns);
890
-
891
- // Register bindings and variables globally for implicit typing
892
- declare global {
893
- namespace Rango {
894
- interface Env extends AppBindings {}
895
- interface Vars extends AppVariables {}
896
- }
897
- }
898
-
899
- export const reverse = router.reverse;
900
- export default router;
901
-
902
- // 4. Run `npx rango generate src/router.tsx` to generate
903
- // router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
904
- // No manual RegisteredRoutes declaration is needed for named-route handlers,
905
- // ctx.reverse, prerender, href(), or Rango.Path. Add `RegisteredRoutes
906
- // extends typeof router.routeMap` when global response payload helpers such
907
- // as Rango.PathResponse need the richer router.routeMap metadata.
908
-
909
- // 5. loaders/*.ts - Type-safe loaders
910
- export const ProductLoader = createLoader(async (ctx) => {
911
- // ctx.params: { slug: string }
912
- // ctx.get("user"): User | undefined (from Rango.Vars)
913
- // ctx.env.DB: D1Database (plain bindings from Rango.Env)
914
- return { product: await fetchProduct(ctx.params.slug) };
915
- });
916
-
917
- // 6. Server: ctx.reverse for named routes
918
- path("/product/:slug", (ctx) => {
919
- return <Link to={ctx.reverse("shop")}>Back to Shop</Link>;
920
- }, { name: "product" })
921
-
922
- // 7. Client: useHref for mounted paths, href for absolute
923
- "use client";
924
- import { useHref, href, Link } from "@rangojs/router/client";
925
- <Link to={href("/shop/product/widget")}>Widget</Link>
926
- ```
9
+ @rangojs/router provides end-to-end type safety for routes, parameters, and
10
+ environment. Without it: `ctx.reverse()`/`href()` accept any string (typos
11
+ 404 at runtime, not compile time), `ctx.search`/`ctx.params` fall back to
12
+ loose `Record<string, string>`, and `ctx.env`/`ctx.get()` are untyped so a
13
+ missing binding surfaces as `undefined` in production instead of a build
14
+ error.
15
+
16
+ Each topic's full setup, code, and caveats live in a companion file linked
17
+ below. Read the one for your case.
18
+
19
+ ## Routing table
20
+
21
+ | I need... | Topic | File |
22
+ | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -------------------------------------------------------------- |
23
+ | Named routes, `.gen.ts` surfaces, `RegisteredRoutes` vs `GeneratedRouteMap`, tsconfig checklist | Router setup & generated route types | [`./generated-files-and-cli.md`](./generated-files-and-cli.md) |
24
+ | Type-safe `path()` names, `ctx.reverse()`, `href()`/`useHref()`, `Rango.PathResponse`, stable `path#export` identity | Route & href typing | [`./route-types.md`](./route-types.md) |
25
+ | Typed `search` schemas, `RouteSearchParams`/`RouteParams`, loader return types | Search params & loader typing | [`./params-and-search.md`](./params-and-search.md) |
26
+ | Typed `env`/bindings, `Rango.Vars`, `createVar()`, handle typing, loader/handle ref props, location state typing | Environment, context, and state typing | [`./env-and-bindings.md`](./env-and-bindings.md) |
27
+ | Multi-app / multi-router tsconfig setup, avoiding `GeneratedRouteMap` collisions | Multi-project setup & full walkthrough | [`./generated-files-and-cli.md`](./generated-files-and-cli.md) |
28
+ | Slow typecheck with many `include()` modules (instantiation blowup), wide `UrlPatterns<any>` annotations | Typecheck cost at route scale | [`./generated-files-and-cli.md`](./generated-files-and-cli.md) |
29
+
30
+ ## Companion files
31
+
32
+ - [`./generated-files-and-cli.md`](./generated-files-and-cli.md) — Router
33
+ setup, the three route-typing surfaces (`GeneratedRouteMap` /
34
+ per-module `routes` / `RegisteredRoutes`), the single-app setup checklist,
35
+ `$$routeNames` vs `router.routeMap`, multi-project tsconfig setup, and the
36
+ complete end-to-end setup walkthrough.
37
+ - [`./route-types.md`](./route-types.md) Type-safe route names, server
38
+ `ctx.reverse()`, client `href()`/`useHref()`, `Rango.Path`,
39
+ `Rango.PathResponse` (incl. overriding JSON/Flight serialization), and the
40
+ `path#export` stable identity scheme shared by loaders/handles/cached
41
+ functions/actions.
42
+ - [`./params-and-search.md`](./params-and-search.md) — Typed `search`
43
+ schemas on `path()`, `Handler<"name">` param/search inference,
44
+ `RouteSearchParams`/`RouteParams` utility types, and loader return-type
45
+ inference.
46
+ - [`./env-and-bindings.md`](./env-and-bindings.md) Environment bindings
47
+ (`TEnv`) and `Rango.Env`/`Rango.Vars` registration, `createVar<T>()`
48
+ scoped context tokens, handle typing, passing loaders/handles as typed
49
+ props, and location state typing.
50
+
51
+ See `/links` for the full URL generation guide (per-module `*.gen.ts`,
52
+ `useReverse`).