@rangojs/router 0.16.0 → 0.17.0

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 (245) hide show
  1. package/README.md +254 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  4. package/dist/types/browser/partial-update.d.ts +11 -0
  5. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  6. package/dist/types/browser/rsc-router.d.ts +1 -1
  7. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  8. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  9. package/dist/types/cache/cache-scope.d.ts +34 -5
  10. package/dist/types/cache/handle-capture.d.ts +41 -3
  11. package/dist/types/cache/handle-snapshot.d.ts +53 -13
  12. package/dist/types/cache/segment-codec.d.ts +10 -3
  13. package/dist/types/cache/taint.d.ts +1 -1
  14. package/dist/types/cache/types.d.ts +15 -3
  15. package/dist/types/client.d.ts +4 -4
  16. package/dist/types/client.rsc.d.ts +4 -5
  17. package/dist/types/components/DefaultDocument.d.ts +5 -2
  18. package/dist/types/context-var.d.ts +6 -5
  19. package/dist/types/escape-script.d.ts +1 -1
  20. package/dist/types/handles/MetaTags.d.ts +5 -4
  21. package/dist/types/handles/Scripts.d.ts +8 -7
  22. package/dist/types/handles/meta.d.ts +2 -2
  23. package/dist/types/handles/script.d.ts +17 -16
  24. package/dist/types/href-client.d.ts +1 -1
  25. package/dist/types/html.d.ts +12 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/index.rsc.d.ts +3 -2
  28. package/dist/types/loader-store.d.ts +7 -4
  29. package/dist/types/reverse.d.ts +36 -4
  30. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  31. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  32. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  33. package/dist/types/route-types.d.ts +1 -1
  34. package/dist/types/router/debug-manifest.d.ts +3 -2
  35. package/dist/types/router/error-handling.d.ts +20 -0
  36. package/dist/types/router/handler-context.d.ts +6 -0
  37. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  38. package/dist/types/router/middleware-types.d.ts +3 -2
  39. package/dist/types/router/router-context.d.ts +3 -1
  40. package/dist/types/router/router-interfaces.d.ts +24 -2
  41. package/dist/types/router/router-options.d.ts +8 -5
  42. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  43. package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
  44. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  45. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
  46. package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
  47. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  48. package/dist/types/router/segment-wrappers.d.ts +1 -1
  49. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  50. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  51. package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
  52. package/dist/types/rsc/shell-capture.d.ts +5 -4
  53. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  54. package/dist/types/rsc/types.d.ts +10 -0
  55. package/dist/types/server/context.d.ts +33 -3
  56. package/dist/types/server/handle-store.d.ts +26 -2
  57. package/dist/types/server/request-context.d.ts +24 -21
  58. package/dist/types/ssr/index.d.ts +15 -0
  59. package/dist/types/static-handler.d.ts +4 -1
  60. package/dist/types/testing/generated-routes.d.ts +2 -2
  61. package/dist/types/testing/internal/context.d.ts +1 -1
  62. package/dist/types/testing/render-route.d.ts +32 -6
  63. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  64. package/dist/types/testing/vitest.d.ts +35 -1
  65. package/dist/types/theme/ThemeScript.d.ts +3 -3
  66. package/dist/types/types/cache-types.d.ts +14 -10
  67. package/dist/types/types/handler-context.d.ts +14 -0
  68. package/dist/types/types/loader-types.d.ts +8 -6
  69. package/dist/types/urls/response-types.d.ts +3 -12
  70. package/dist/types/use-loader.d.ts +12 -3
  71. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  72. package/dist/types/vite/rango.d.ts +1 -1
  73. package/dist/vite/index.js +1381 -1346
  74. package/package.json +2 -2
  75. package/skills/api-client/SKILL.md +22 -10
  76. package/skills/breadcrumbs/SKILL.md +65 -22
  77. package/skills/bundle-analysis/SKILL.md +22 -22
  78. package/skills/cache-guide/SKILL.md +207 -97
  79. package/skills/caching/SKILL.md +295 -93
  80. package/skills/catalog.json +16 -16
  81. package/skills/client-urls/SKILL.md +53 -31
  82. package/skills/cloudflare/SKILL.md +72 -20
  83. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  84. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  85. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  86. package/skills/comparison/SKILL.md +5 -0
  87. package/skills/comparison/references/framework-comparison.md +39 -33
  88. package/skills/composability/SKILL.md +38 -29
  89. package/skills/css/SKILL.md +10 -13
  90. package/skills/debug-manifest/SKILL.md +74 -31
  91. package/skills/defer-hydration/SKILL.md +2 -1
  92. package/skills/deployment-caching/SKILL.md +6 -1
  93. package/skills/document-cache/SKILL.md +92 -28
  94. package/skills/fonts/SKILL.md +16 -8
  95. package/skills/handler-use/SKILL.md +53 -17
  96. package/skills/hooks/SKILL.md +28 -20
  97. package/skills/hooks/data.md +23 -3
  98. package/skills/hooks/handle-and-actions.md +31 -16
  99. package/skills/hooks/navigation.md +39 -10
  100. package/skills/hooks/outlets.md +13 -6
  101. package/skills/hooks/state.md +37 -15
  102. package/skills/hooks/urls.md +7 -5
  103. package/skills/host-router/SKILL.md +64 -12
  104. package/skills/i18n/SKILL.md +60 -34
  105. package/skills/intercept/SKILL.md +161 -26
  106. package/skills/layout/SKILL.md +106 -50
  107. package/skills/links/SKILL.md +51 -19
  108. package/skills/loader/SKILL.md +242 -106
  109. package/skills/middleware/SKILL.md +119 -23
  110. package/skills/migrate-nextjs/SKILL.md +100 -45
  111. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  112. package/skills/migrate-react-router/SKILL.md +49 -38
  113. package/skills/migrate-react-router/component-migration.md +25 -6
  114. package/skills/migrate-react-router/data-and-actions.md +18 -5
  115. package/skills/migrate-react-router/route-mapping.md +12 -6
  116. package/skills/mime-routes/SKILL.md +12 -7
  117. package/skills/observability/SKILL.md +28 -14
  118. package/skills/parallel/SKILL.md +75 -39
  119. package/skills/ppr/SKILL.md +345 -150
  120. package/skills/prerender/SKILL.md +189 -100
  121. package/skills/rango/SKILL.md +65 -34
  122. package/skills/react-compiler/SKILL.md +6 -5
  123. package/skills/response-routes/SKILL.md +133 -25
  124. package/skills/route/SKILL.md +165 -60
  125. package/skills/router-setup/SKILL.md +418 -240
  126. package/skills/scripts/SKILL.md +31 -20
  127. package/skills/server-actions/SKILL.md +220 -59
  128. package/skills/shell-manifest/SKILL.md +12 -7
  129. package/skills/streams-and-websockets/SKILL.md +32 -14
  130. package/skills/tailwind/SKILL.md +37 -33
  131. package/skills/testing/SKILL.md +68 -32
  132. package/skills/testing/cache-prerender.md +55 -29
  133. package/skills/testing/client-components.md +59 -6
  134. package/skills/testing/e2e-parity.md +12 -2
  135. package/skills/testing/flight.md +19 -11
  136. package/skills/testing/handles.md +8 -5
  137. package/skills/testing/loader.md +8 -5
  138. package/skills/testing/middleware.md +8 -3
  139. package/skills/testing/predicates.md +120 -0
  140. package/skills/testing/render-handler.md +70 -22
  141. package/skills/testing/response-routes.md +11 -4
  142. package/skills/testing/reverse-and-types.md +20 -14
  143. package/skills/testing/server-actions.md +12 -3
  144. package/skills/testing/server-tree.md +9 -0
  145. package/skills/testing/setup.md +9 -5
  146. package/skills/theme/SKILL.md +106 -29
  147. package/skills/typesafety/SKILL.md +20 -9
  148. package/skills/typesafety/env-and-bindings.md +126 -47
  149. package/skills/typesafety/generated-files-and-cli.md +26 -14
  150. package/skills/typesafety/params-and-search.md +44 -19
  151. package/skills/typesafety/route-types.md +50 -14
  152. package/skills/use-cache/SKILL.md +200 -82
  153. package/skills/vercel/SKILL.md +21 -11
  154. package/skills/view-transitions/SKILL.md +41 -20
  155. package/src/__augment-tests__/augmented.check.ts +26 -1
  156. package/src/browser/index.ts +2 -2
  157. package/src/browser/navigation-bridge.ts +3 -4
  158. package/src/browser/navigation-transaction.ts +22 -2
  159. package/src/browser/partial-update.ts +35 -25
  160. package/src/browser/react/ScrollRestoration.tsx +6 -6
  161. package/src/browser/rsc-router.tsx +1 -1
  162. package/src/browser/scroll-restoration.ts +53 -6
  163. package/src/cache/cache-exec-scope.ts +37 -4
  164. package/src/cache/cache-key-utils.ts +1 -6
  165. package/src/cache/cache-runtime.ts +230 -61
  166. package/src/cache/cache-scope.ts +76 -12
  167. package/src/cache/document-cache.ts +15 -0
  168. package/src/cache/handle-capture.ts +90 -10
  169. package/src/cache/handle-snapshot.ts +132 -28
  170. package/src/cache/segment-codec.ts +26 -7
  171. package/src/cache/tag-invalidation.ts +4 -3
  172. package/src/cache/taint.ts +1 -1
  173. package/src/cache/types.ts +16 -3
  174. package/src/client.rsc.tsx +10 -9
  175. package/src/client.tsx +5 -4
  176. package/src/components/DefaultDocument.tsx +7 -2
  177. package/src/context-var.ts +6 -5
  178. package/src/escape-script.ts +1 -1
  179. package/src/handles/MetaTags.tsx +13 -11
  180. package/src/handles/Scripts.tsx +11 -10
  181. package/src/handles/meta.ts +2 -2
  182. package/src/handles/script.ts +17 -16
  183. package/src/href-client.ts +1 -1
  184. package/src/html.ts +12 -0
  185. package/src/index.rsc.ts +5 -0
  186. package/src/index.ts +1 -0
  187. package/src/loader-store.ts +7 -4
  188. package/src/prerender/build-shell-capture.ts +8 -11
  189. package/src/reverse.ts +44 -4
  190. package/src/route-definition/dsl-helpers.ts +117 -30
  191. package/src/route-definition/helpers-types.ts +33 -9
  192. package/src/route-definition/resolve-handler-use.ts +121 -6
  193. package/src/route-types.ts +0 -3
  194. package/src/router/debug-manifest.ts +10 -2
  195. package/src/router/error-handling.ts +51 -15
  196. package/src/router/handler-context.ts +34 -25
  197. package/src/router/intercept-resolution.ts +125 -39
  198. package/src/router/loader-resolution.ts +56 -11
  199. package/src/router/match-api.ts +12 -17
  200. package/src/router/match-middleware/background-revalidation.ts +161 -96
  201. package/src/router/match-middleware/cache-lookup.ts +93 -10
  202. package/src/router/match-middleware/cache-store.ts +17 -90
  203. package/src/router/middleware-types.ts +3 -5
  204. package/src/router/middleware.ts +52 -9
  205. package/src/router/prerender-match.ts +53 -19
  206. package/src/router/route-snapshot.ts +11 -1
  207. package/src/router/router-context.ts +2 -0
  208. package/src/router/router-interfaces.ts +25 -2
  209. package/src/router/router-options.ts +8 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -12
  211. package/src/router/segment-resolution/loader-cache.ts +205 -45
  212. package/src/router/segment-resolution/loader-mask.ts +12 -22
  213. package/src/router/segment-resolution/loader-snapshot.ts +6 -6
  214. package/src/router/segment-resolution/mask-nested.ts +7 -7
  215. package/src/router/segment-resolution/revalidation.ts +34 -6
  216. package/src/router/segment-wrappers.ts +4 -0
  217. package/src/rsc/render-pipeline.ts +3 -0
  218. package/src/rsc/response-route-handler.ts +11 -3
  219. package/src/rsc/rsc-rendering.ts +8 -45
  220. package/src/rsc/shell-capture-constants.ts +13 -0
  221. package/src/rsc/shell-capture.ts +111 -90
  222. package/src/rsc/ssr-setup.ts +14 -8
  223. package/src/rsc/types.ts +11 -0
  224. package/src/server/context.ts +88 -11
  225. package/src/server/cookie-store.ts +5 -4
  226. package/src/server/handle-store.ts +232 -7
  227. package/src/server/request-context.ts +35 -30
  228. package/src/ssr/index.tsx +40 -7
  229. package/src/static-handler.ts +4 -1
  230. package/src/testing/flight-runtime.d.ts +21 -0
  231. package/src/testing/generated-routes.ts +2 -2
  232. package/src/testing/internal/context.ts +1 -1
  233. package/src/testing/internal/flight-client-globals.ts +7 -5
  234. package/src/testing/render-route.tsx +85 -24
  235. package/src/testing/run-loader.ts +5 -1
  236. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  237. package/src/testing/vitest.ts +62 -1
  238. package/src/theme/ThemeScript.tsx +3 -3
  239. package/src/types/cache-types.ts +14 -10
  240. package/src/types/handler-context.ts +14 -0
  241. package/src/types/loader-types.ts +8 -6
  242. package/src/urls/response-types.ts +3 -23
  243. package/src/use-loader.tsx +12 -3
  244. package/src/vite/plugins/use-cache-transform.ts +118 -74
  245. package/src/vite/rango.ts +7 -3
@@ -1,17 +1,27 @@
1
1
  ---
2
2
  name: typesafety
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.
3
+ description: Set up type-safe route names, params, search params, response payloads, and environment types in @rangojs/router. Use when route or search params aren't typed, ctx.env or ctx.get() is untyped, TypeScript can't infer a loader's return type, or wiring RegisteredRoutes / the generated route map.
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
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.
9
+ This skill covers how route names, params, search params, response payloads,
10
+ environment bindings, and context variables get their types. Use it when
11
+ setting up a new app's type surfaces or when one of them resolves to
12
+ `any`/`unknown`/`never` unexpectedly.
13
+
14
+ What you lose without the setup:
15
+
16
+ - `ctx.reverse()` accepts any name and `href()` any `/${string}` path, so a
17
+ typo 404s at runtime instead of failing to compile.
18
+ - `Handler<"name">` is rejected (there is no generated map to look the name
19
+ up in), so `ctx.params`/`ctx.search` can't be inferred from the route.
20
+ - `ctx.env` is `unknown` until `Rango.Env` is registered (every binding access
21
+ is a compile error), and string-key `ctx.get("key")` returns `any` until
22
+ `Rango.Vars` is registered (a typo'd key is silently accepted).
23
+ - `Rango.PathResponse` resolves to `never` until `Rango.RegisteredRoutes` is
24
+ registered.
15
25
 
16
26
  Each topic's full setup, code, and caveats live in a companion file linked
17
27
  below. Read the one for your case.
@@ -48,5 +58,6 @@ below. Read the one for your case.
48
58
  scoped context tokens, handle typing, passing loaders/handles as typed
49
59
  props, and location state typing.
50
60
 
51
- See `/links` for the full URL generation guide (per-module `*.gen.ts`,
52
- `useReverse`).
61
+ Related skills: `/links` (URL generation: `ctx.reverse`, `href`,
62
+ `useReverse`, per-module `*.gen.ts`), `/response-routes` (response payload
63
+ typing), `/api-client` (typed fetch client over `RouteResponse`).
@@ -27,6 +27,7 @@ export interface AppVariables {
27
27
 
28
28
  ```typescript
29
29
  // router.tsx
30
+ import { createRouter } from "@rangojs/router";
30
31
  import type { AppBindings, AppVariables } from "./env";
31
32
 
32
33
  const router = createRouter<AppBindings>({
@@ -40,7 +41,9 @@ declare global {
40
41
  interface Vars extends AppVariables {}
41
42
  }
42
43
  }
44
+ ```
43
45
 
46
+ ```typescript
44
47
  // middleware - typed via ctx.set / ctx.get
45
48
  import type { Middleware } from "@rangojs/router";
46
49
 
@@ -52,38 +55,35 @@ export const authMiddleware: Middleware = async (ctx, next) => {
52
55
  });
53
56
  await next();
54
57
  };
55
-
56
- // loaders - typed context
57
- export const UserLoader = createLoader(async (ctx) => {
58
- const db = ctx.env.DB; // D1Database (plain bindings)
59
- const userId = ctx.get("user")?.id; // from Rango.Vars
60
- return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
61
- });
62
58
  ```
63
59
 
64
60
  ## Global Environment Registration
65
61
 
66
- Register environment types globally for implicit typing:
62
+ The `declare global` block above is what types the contexts. `createRouter<TEnv>()`
63
+ types the router instance, but `urls()` modules, `Handler`, middleware, and
64
+ response routes read `Rango.Env` / `Rango.Vars` — they cannot see the router's
65
+ generic. Put the block in a file that is part of every TypeScript program that
66
+ typechecks handlers (usually `router.tsx`, kept in via tsconfig `files`).
67
67
 
68
- ```typescript
69
- // router.tsx
70
- declare global {
71
- namespace Rango {
72
- interface Env extends AppBindings {}
73
- interface Vars extends AppVariables {}
74
- }
75
- }
76
- ```
68
+ What each context sees:
77
69
 
78
- Now handlers have typed context without explicit imports:
70
+ | Context | `ctx.env` | `ctx.get("key")` |
71
+ | ------------------------------------- | ----------------------------------------- | -------------------------------------- |
72
+ | Handlers, middleware, response routes | `Rango.Env` (`unknown` if not registered) | `Rango.Vars` (`any` if not registered) |
73
+ | Loaders (`createLoader`) | `any` (not typed from `Rango.Env`) | `Rango.Vars` (`any` if not registered) |
74
+
75
+ An unregistered `Rango.Env` is deliberately `unknown`, so `ctx.env.DB` is a
76
+ compile error rather than a silent `any`. An unregistered `Rango.Vars` falls
77
+ back to `any` so string-key vars work with zero config.
79
78
 
80
79
  ```typescript
81
80
  // In loaders
81
+ import { createLoader } from "@rangojs/router";
82
+
82
83
  export const DashboardLoader = createLoader(async (ctx) => {
83
- // ctx.env.DB is typed from global Rango.Env
84
- // ctx.get("user") is typed from global Rango.Vars
85
- const user = ctx.get("user");
86
- return { user };
84
+ const user = ctx.get("user"); // typed from global Rango.Vars
85
+ const db = ctx.env.DB as D1Database; // ctx.env is `any` in loaders
86
+ return { user, count: await countOrders(db, user?.id) };
87
87
  });
88
88
  ```
89
89
 
@@ -104,7 +104,10 @@ interface PaginationData {
104
104
  export const Pagination = createVar<PaginationData>();
105
105
 
106
106
  // Non-cacheable var — reading inside cache() or "use cache" throws at runtime
107
- const Session = createVar<SessionData>({ cache: false });
107
+ interface SessionData {
108
+ userId: string;
109
+ }
110
+ export const Session = createVar<SessionData>({ cache: false });
108
111
  ```
109
112
 
110
113
  `createVar` accepts an optional options object. The `cache` option (default
@@ -114,9 +117,16 @@ marks a specific write as non-cacheable even if the var itself is cacheable.
114
117
  "Least cacheable wins" — if either says `cache: false`, the value throws on
115
118
  read inside `cache()` or `"use cache"`.
116
119
 
117
- ### Producer (handler or middleware)
120
+ ### Producer (handler, layout, or middleware)
121
+
122
+ Write with `ctx.set()` from middleware, route handlers, or layout handlers;
123
+ read with `ctx.get()` in the segments they wrap. A route handler runs before
124
+ the layouts and parallels declared in its own use callback (handler-first), so
125
+ those see the value; an outer layout wrapping the route renders first and
126
+ does not. See `/route` for the full ordering.
118
127
 
119
128
  ```typescript
129
+ import type { Handler } from "@rangojs/router";
120
130
  import { Pagination } from "../vars/pagination.js";
121
131
 
122
132
  const ArticleList: Handler<"articles.list"> = async (ctx) => {
@@ -132,15 +142,30 @@ const ArticleList: Handler<"articles.list"> = async (ctx) => {
132
142
  ### Consumer (layout, parallel, or any context with get)
133
143
 
134
144
  ```typescript
145
+ import type { Handler } from "@rangojs/router";
146
+ import { Outlet } from "@rangojs/router/client";
135
147
  import { Pagination } from "../vars/pagination.js";
136
148
 
137
- export function PaginationLayout(ctx: any) {
138
- const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
139
- if (!pagination) return <Outlet />;
140
- return <nav>Page {pagination.current} of {pagination.total}</nav>;
141
- }
149
+ // Mounted inside the producer's use callback:
150
+ // path("/articles", ArticleList, { name: "articles.list" }, () => [layout(PaginationLayout)])
151
+ export const PaginationLayout: Handler = (ctx) => {
152
+ const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
153
+ return (
154
+ <>
155
+ <Outlet />
156
+ {pagination && (
157
+ <nav>
158
+ Page {pagination.current} of {pagination.total}
159
+ </nav>
160
+ )}
161
+ </>
162
+ );
163
+ };
142
164
  ```
143
165
 
166
+ Type the layout (`Handler`) rather than annotating `ctx: any`: with `any`,
167
+ `ctx.get(Pagination)` is `any` too and the token contract is lost.
168
+
144
169
  ### Why not just use Rango.Vars?
145
170
 
146
171
  `Rango.Vars` (via global namespace augmentation) provides app-global typing for
@@ -154,7 +179,9 @@ Both approaches coexist: `ctx.get("user")` (global via Vars) and
154
179
 
155
180
  ## Handle Type Safety
156
181
 
157
- Handles have typed data:
182
+ A handle is typed by two parameters: `Handle<TData, TAccumulated>`. `TData` is
183
+ what each segment pushes; `TAccumulated` is what readers get after the collect
184
+ function runs (default `TData[][]`, one array per segment).
158
185
 
159
186
  ```typescript
160
187
  // Built-in Breadcrumbs handle — import from "@rangojs/router"
@@ -168,18 +195,33 @@ path("/shop/product/:slug", (ctx) => {
168
195
  breadcrumb({ label: "Products", href: "/shop/products" });
169
196
  return <ProductPage />;
170
197
  }, { name: "product" });
198
+ ```
171
199
 
200
+ ```tsx
172
201
  // In client — typed array
202
+ "use client";
173
203
  import { useHandle, Breadcrumbs } from "@rangojs/router/client";
204
+
174
205
  function BreadcrumbNav() {
175
- const crumbs = useHandle(Breadcrumbs);
176
- // crumbs: BreadcrumbItem[]
206
+ const crumbs = useHandle(Breadcrumbs); // crumbs: BreadcrumbItem[]
207
+ return (
208
+ <nav>
209
+ {crumbs.map((c) => (
210
+ <a key={c.href} href={c.href}>
211
+ {c.label}
212
+ </a>
213
+ ))}
214
+ </nav>
215
+ );
177
216
  }
217
+ ```
178
218
 
179
- // Custom handles also work the same way
219
+ ```typescript
220
+ // Custom handle: give TAccumulated whenever collect changes the shape
180
221
  import { createHandle } from "@rangojs/router";
222
+
181
223
  export const PageTitle = createHandle<string, string>(
182
- (segments) => segments.flat().at(-1) ?? "Default Title"
224
+ (segments) => segments.flat().at(-1) ?? "Default Title",
183
225
  );
184
226
  ```
185
227
 
@@ -190,11 +232,16 @@ Use `typeof` to get the full typed definition without manually specifying generi
190
232
 
191
233
  ```typescript
192
234
  // loaders.ts
235
+ import { createLoader } from "@rangojs/router";
236
+
193
237
  export const ProductLoader = createLoader(async (ctx) => {
194
238
  return { product: await fetchProduct(ctx.params.slug) };
195
239
  });
196
240
 
197
- // Built-in Breadcrumbs — or any custom handle created with createHandle()
241
+ // Server: pass the definitions as props
242
+ path("/product/:slug", () => (
243
+ <MyComponent loader={ProductLoader} handle={Breadcrumbs} />
244
+ ), { name: "product" }, () => [loader(ProductLoader)]);
198
245
  ```
199
246
 
200
247
  ```tsx
@@ -208,20 +255,27 @@ function MyComponent({
208
255
  handle,
209
256
  }: {
210
257
  loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
211
- handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
258
+ handle: typeof Breadcrumbs; // Handle<BreadcrumbItem, BreadcrumbItem[]>
212
259
  }) {
213
260
  const { data } = useLoader(loader); // data is typed
214
- const crumbs = useHandle(handle); // crumbs is typed array
261
+ const crumbs = useHandle(handle); // crumbs: BreadcrumbItem[]
215
262
  // ...
216
263
  }
217
264
  ```
218
265
 
219
- RSC Flight serialization calls `toJSON()` on both loaders and handles,
220
- sending only `{ __brand, $$id }` to the client. The hooks recover the
221
- full functionality from module-level registries.
266
+ Loader and handle definitions are plain `{ __brand, $$id }` objects — the
267
+ loader function and the handle's collect function live in module-level
268
+ registries keyed by `$$id` — so they cross the RSC boundary as-is and the
269
+ hooks look the rest up by id. `useLoader()` still needs the loader registered
270
+ on the route with `loader()` (or use `useFetchLoader()` for on-demand fetching).
222
271
 
223
272
  ## Location State Type Safety
224
273
 
274
+ `createLocationState<T>()` returns a typed definition; the Vite plugin injects
275
+ its key, so it must be exported from a module. Values are stored in
276
+ `history.state`, so `T` must be structured-cloneable: functions, symbols,
277
+ class constructors, React elements, and `unknown` are compile errors.
278
+
225
279
  ```typescript
226
280
  // location-states.ts
227
281
  import { createLocationState } from "@rangojs/router";
@@ -233,13 +287,28 @@ export const ProductPreview = createLocationState<{
233
287
  image: string;
234
288
  }>();
235
289
 
290
+ // Flash state: cleared after the first read
291
+ export const FlashMessage = createLocationState<{ text: string }>({
292
+ flash: true,
293
+ });
294
+ ```
295
+
296
+ ```tsx
297
+ "use client";
298
+ import { Link, href, useLocationState } from "@rangojs/router/client";
299
+ import { ProductPreview } from "./location-states";
300
+
236
301
  // Passing state through Link
237
- <Link
238
- to={href("product", { slug: "widget" })}
239
- state={[ProductPreview({ name: "Widget", price: 99, image: "/img.jpg" })]}
240
- >
241
- View Product
242
- </Link>
302
+ function ProductCard() {
303
+ return (
304
+ <Link
305
+ to={href("/product/widget")}
306
+ state={[ProductPreview({ name: "Widget", price: 99, image: "/img.jpg" })]}
307
+ >
308
+ View Product
309
+ </Link>
310
+ );
311
+ }
243
312
 
244
313
  // Reading state in component
245
314
  function ProductHeader() {
@@ -247,8 +316,18 @@ function ProductHeader() {
247
316
  // preview: { name: string; price: number; image: string } | undefined
248
317
 
249
318
  if (preview) {
250
- return <h1>{preview.name} - ${preview.price}</h1>;
319
+ return (
320
+ <h1>
321
+ {preview.name} - ${preview.price}
322
+ </h1>
323
+ );
251
324
  }
252
325
  return <h1>Loading...</h1>;
253
326
  }
254
327
  ```
328
+
329
+ Other entry points: pass a getter (`ProductPreview(() => ({ ... }))`) to compute
330
+ the value at click time; `ctx.setLocationState(entry)` attaches state from a
331
+ handler; `redirect(url, { state })` carries it through a redirect; and
332
+ `ProductPreview.read()` / `.write(value)` / `.delete()` access the current
333
+ history entry outside React (client-only, non-reactive).
@@ -5,23 +5,23 @@
5
5
  ```typescript
6
6
  // router.tsx
7
7
  import { createRouter } from "@rangojs/router";
8
+ import { Document } from "./document";
8
9
  import { urlpatterns } from "./urls";
10
+ import type { AppBindings } from "./env";
9
11
 
10
- const router = createRouter<AppBindings>({
12
+ export const router = createRouter<AppBindings>({
11
13
  document: Document,
12
14
  }).routes(urlpatterns);
13
15
 
14
16
  // Server-side named-route reverse (type-safe via routeMap)
15
17
  export const reverse = router.reverse;
16
-
17
- export default router;
18
18
  ```
19
19
 
20
20
  ### Which global type should I use?
21
21
 
22
22
  Use the generated route map by default. Manual `RegisteredRoutes` augmentation
23
23
  is only needed when you want the richer `typeof router.routeMap` shape
24
- available globally.
24
+ available globally — in practice, for `Rango.PathResponse`.
25
25
 
26
26
  - `GeneratedRouteMap` — auto-registered by `router.named-routes.gen.ts`
27
27
  Use for `Handler<"name">` (type annotation), `Prerender<"name">(...)` (function
@@ -95,19 +95,27 @@ instance in the same TypeScript program:
95
95
  }
96
96
  ```
97
97
 
98
- Then generate the route types from the router file:
98
+ The Vite plugin writes `src/router.named-routes.gen.ts` (next to the router
99
+ file) automatically: before the dev server starts, again when route files
100
+ change, and during build. To produce it without starting Vite (a fresh clone,
101
+ a CI typecheck step), run the CLI against the router file:
99
102
 
100
103
  ```bash
101
104
  npx rango generate src/router.tsx
102
105
  ```
103
106
 
104
- This creates `src/router.named-routes.gen.ts`, which augments
105
- `Rango.GeneratedRouteMap`. Keep that generated file committed with the router
107
+ The file augments `Rango.GeneratedRouteMap`. Keep it committed with the router
106
108
  source. The `files` entry keeps `router.tsx` in the program even when nothing
107
109
  imports it directly, so `Rango.Env`, `Rango.Vars`, and optional
108
110
  `Rango.RegisteredRoutes` augmentation are visible to handlers, loaders, actions,
109
111
  and client helpers.
110
112
 
113
+ CLI flags: the default mode fails (and writes nothing) when an `include()` cannot
114
+ be resolved statically (factory functions, dynamic expressions); `--static`
115
+ accepts partial output with warnings; `--runtime` uses Vite-based discovery for
116
+ full coverage (needs `vite` and `@vitejs/plugin-rsc`, optional
117
+ `--config <path>`). Passing a directory scans it for router and `urls()` files.
118
+
111
119
  ### Named Routes, `$$routeNames`, And `router.routeMap`
112
120
 
113
121
  There are two runtime/type surfaces with similar names:
@@ -299,7 +307,9 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
299
307
  ]);
300
308
 
301
309
  // 3. router.tsx - Create router and export reverse
302
- const router = createRouter<AppBindings>({
310
+ import { createRouter } from "@rangojs/router";
311
+
312
+ export const router = createRouter<AppBindings>({
303
313
  document: Document,
304
314
  }).routes(urlpatterns);
305
315
 
@@ -312,10 +322,10 @@ declare global {
312
322
  }
313
323
 
314
324
  export const reverse = router.reverse;
315
- export default router;
316
325
 
317
- // 4. Run `npx rango generate src/router.tsx` to generate
318
- // router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
326
+ // 4. router.named-routes.gen.ts is written by the Vite plugin (dev + build);
327
+ // run `npx rango generate src/router.tsx` to produce it without Vite.
328
+ // It auto-registers GeneratedRouteMap globally.
319
329
  // No manual RegisteredRoutes declaration is needed for named-route handlers,
320
330
  // ctx.reverse, prerender, href(), or Rango.Path. Add `RegisteredRoutes
321
331
  // extends AppRoutes` (an alias of typeof router.routeMap) when global
@@ -323,10 +333,12 @@ export default router;
323
333
  // router.routeMap metadata.
324
334
 
325
335
  // 5. loaders/*.ts - Type-safe loaders
336
+ import { createLoader } from "@rangojs/router";
337
+
326
338
  export const ProductLoader = createLoader(async (ctx) => {
327
- // ctx.params: { slug: string }
328
- // ctx.get("user"): User | undefined (from Rango.Vars)
329
- // ctx.env.DB: D1Database (plain bindings from Rango.Env)
339
+ // ctx.params.slug: string | undefined (a loader is not bound to one route)
340
+ // ctx.get("user"): AppVariables["user"] (from Rango.Vars)
341
+ // ctx.env: any (createLoader does not apply Rango.Env; cast if you need types)
330
342
  return { product: await fetchProduct(ctx.params.slug) };
331
343
  });
332
344
 
@@ -22,12 +22,17 @@ global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
22
22
  import type { Handler } from "@rangojs/router";
23
23
 
24
24
  export const SearchPage: Handler<"search"> = (ctx) => {
25
- // ctx.search is typed: { q: string; page?: number; sort?: string }
26
- const { q, page, sort } = ctx.search;
25
+ // ctx.search is typed: { q: string | undefined; page?: number; sort?: string }
26
+ const { q = "", page = 1, sort } = ctx.search;
27
27
  return <SearchResults q={q} page={page} sort={sort} />;
28
28
  };
29
29
  ```
30
30
 
31
+ Required keys (no `?`) still resolve to `T | undefined` inside the handler:
32
+ the schema tells callers (`ctx.reverse`, `useReverse`) what to send, but the
33
+ server cannot trust the client to send it, so the handler must handle a
34
+ missing value.
35
+
31
36
  This avoids circular references because `Handler` defaults to `GeneratedRouteMap`
32
37
  (from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
33
38
 
@@ -48,8 +53,12 @@ Note the difference: `Handler<"search">` (no dot) resolves against the global
48
53
  `routes` for param/search inference and only uses it for local `ctx.reverse(".x")`.
49
54
 
50
55
  Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
51
- Values are automatically coerced from query string (e.g., `"2"` becomes `2` for numbers).
52
- Routes without a `search` schema keep the standard `URLSearchParams` behavior.
56
+ Values are automatically coerced from the query string: numbers accept finite
57
+ decimal values (`"2"` becomes `2`; `"0x10"`, `"Infinity"` or an empty value are
58
+ treated as missing), and a present boolean is `true` for `"true"`/`"1"` and
59
+ `false` otherwise. Absent keys are omitted, required or not. Routes without a
60
+ `search` schema get `ctx.search` as an empty object; read the raw query from
61
+ `ctx.searchParams` (a `URLSearchParams`).
53
62
 
54
63
  ### RouteSearchParams and RouteParams utility types
55
64
 
@@ -104,12 +113,15 @@ export const NamedRoutes = {
104
113
  } as const;
105
114
  ```
106
115
 
107
- You never open a `.gen.ts` by hand. Treat the generated types as call-site
108
- honesty checks, not modules to read:
116
+ You never edit a `.gen.ts` by hand. Treat the generated types as call-site
117
+ checks, not modules to read:
109
118
 
110
- - **Do not import `router.named-routes.gen.ts` directly**, and don't reach for
111
- `Rango.GeneratedRouteMap`. It is the whole-app manifest, auto-wired
112
- globally — `Handler<"name">` and `ctx.reverse("name")` already see it.
119
+ - **You rarely need to import `router.named-routes.gen.ts`**, and you don't
120
+ need to reference `Rango.GeneratedRouteMap`. It is the whole-app manifest,
121
+ auto-wired globally — `Handler<"name">` and `ctx.reverse("name")` already
122
+ see it. Importing its `NamedRoutes` value is an escape hatch (global names
123
+ in `useReverse`, an absolute-path map for `/api-client`); on the client it
124
+ ships every route name and pattern in the app.
113
125
  - **Per-module `*.gen.ts` imports are fine** — they are the opt-in local-route
114
126
  pattern for `useReverse(routes)` and explicit local handler typing
115
127
  (`Handler<".name", routes>`). See `/links`.
@@ -119,35 +131,48 @@ smell — fix the call site (or regenerate), never edit the generated file.
119
131
 
120
132
  ## Loader Type Safety
121
133
 
122
- Loaders have typed return values:
134
+ A loader's return type is inferred from its function and flows to every
135
+ consumer. `createLoader` is not bound to one route, so `ctx.params` is
136
+ `Record<string, string | undefined>`:
123
137
 
124
138
  ```typescript
125
139
  // loaders/product.ts
140
+ import { createLoader } from "@rangojs/router";
141
+
126
142
  export const ProductLoader = createLoader(async (ctx) => {
127
143
  return {
128
- id: ctx.params.slug,
144
+ id: ctx.params.slug ?? "",
129
145
  name: "Widget",
130
146
  price: 99,
131
147
  };
132
148
  });
149
+ ```
133
150
 
134
- // In server component - type is inferred
135
- import { useLoader } from "@rangojs/router/client";
151
+ Server handler (escape hatch — reads the data in the handler itself):
136
152
 
137
- async function ProductPage() {
138
- const product = await useLoader(ProductLoader);
153
+ ```typescript
154
+ path("/product/:slug", async (ctx) => {
155
+ const product = await ctx.use(ProductLoader);
139
156
  // product: { id: string; name: string; price: number }
140
157
  return <h1>{product.name}</h1>;
141
- }
158
+ }, { name: "product" }, () => [loader(ProductLoader)]);
159
+ ```
160
+
161
+ Client component (the default consumption site):
142
162
 
143
- // In client component - same type
163
+ ```typescript
144
164
  "use client";
145
165
  import { useLoader } from "@rangojs/router/client";
166
+ import { ProductLoader } from "../loaders/product";
146
167
 
147
168
  function ProductPrice() {
148
169
  const { data } = useLoader(ProductLoader);
149
170
  // data: { id: string; name: string; price: number }
150
- const product = data;
151
- return <span>${product.price}</span>;
171
+ return <span>${data.price}</span>;
152
172
  }
153
173
  ```
174
+
175
+ `useLoader()` is a client hook — never call it in a server component.
176
+ Client-side data is typed through `Rango.FlightSerialize<T>` (the RSC Flight
177
+ boundary), so values Flight preserves (such as `Date`) keep their type. See
178
+ `/loader` for when to use `ctx.use()` versus `useLoader()`.