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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -33,6 +33,26 @@ urls(({ path }) => [
33
33
  ]);
34
34
  ```
35
35
 
36
+ ### Optional URL params at runtime
37
+
38
+ Absent optional params are **omitted from `ctx.params`** — `ctx.params.<name>`
39
+ reads as `undefined`, matching the `RouteParams<"name">` type
40
+ (`{ query?: string }`). Use `??` to default and `=== undefined` to check
41
+ absence:
42
+
43
+ ```typescript
44
+ path("/search/:query?", (ctx) => {
45
+ const query = ctx.params.query ?? ""; // works — undefined coalesces
46
+ if (ctx.params.query === undefined) return <EmptySearch />;
47
+ return <Results query={ctx.params.query} />;
48
+ }, { name: "search" });
49
+ ```
50
+
51
+ For the common pattern of an optional locale prefix
52
+ (`include("/:locale?", routes)`) and the wider react-intl integration —
53
+ locale detection, fallback chains, URL generation with absent locale —
54
+ see `/i18n`.
55
+
36
56
  ## Route Handler Patterns
37
57
 
38
58
  ### Component Function
@@ -74,8 +94,45 @@ path("/product/:slug", async (ctx) => {
74
94
 
75
95
  ```typescript
76
96
  path("/product/:slug", ProductPage, {
77
- name: "product", // Route name for href() and navigation
78
- })
97
+ name: "product", // Route name for href() and navigation
98
+ });
99
+ ```
100
+
101
+ ### Typed Search Params
102
+
103
+ Add a `search` schema to get typed `ctx.search`:
104
+
105
+ ```typescript
106
+ path("/search", SearchPage, {
107
+ name: "search",
108
+ search: { q: "string", page: "number?", sort: "string?" },
109
+ });
110
+ ```
111
+
112
+ Use `Handler<"name">` for typed search params (resolves from the generated route map automatically):
113
+
114
+ ```typescript
115
+ import type { Handler } from "@rangojs/router";
116
+
117
+ export const SearchPage: Handler<"search"> = (ctx) => {
118
+ // ctx.search is typed: { q: string; page?: number; sort?: string }
119
+ const { q, page, sort } = ctx.search;
120
+ // ctx.searchParams is always URLSearchParams
121
+ return <SearchResults q={q} page={page} sort={sort} />;
122
+ };
123
+ ```
124
+
125
+ Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
126
+ Missing params are `undefined` regardless of required/optional. The required/optional
127
+ distinction is a consumer-facing contract (for `href()` and `reverse()` autocomplete).
128
+
129
+ Use `RouteSearchParams<"name">` and `RouteParams<"name">` to extract types for props:
130
+
131
+ ```typescript
132
+ import type { RouteSearchParams, RouteParams } from "@rangojs/router";
133
+
134
+ type SP = RouteSearchParams<"search">; // { q: string; page?: number; sort?: string }
135
+ type P = RouteParams<"blogPost">; // { slug: string }
79
136
  ```
80
137
 
81
138
  ## Route Children
@@ -90,17 +147,224 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
90
147
  ])
91
148
  ```
92
149
 
150
+ ## Handler Data Ownership
151
+
152
+ When a route has children (orphan layouts, parallels), the handler executes
153
+ first. Use `ctx.set(key, value)` to share data with children, who read it
154
+ via `ctx.get(key)`. Caching wraps all segments together, so either all run
155
+ or none do.
156
+
157
+ ### Typed context variables with createVar
158
+
159
+ Use `createVar<T>()` to create a typed token for `ctx.set()`/`ctx.get()`.
160
+ The token is imported by both the handler (producer) and layout (consumer),
161
+ making the data contract explicit and compile-time verified:
162
+
163
+ ```typescript
164
+ import { createVar } from "@rangojs/router";
165
+ import { Outlet, ParallelOutlet } from "@rangojs/router/client";
166
+
167
+ // Typed token -- shared between handler and layout
168
+ interface DashboardData {
169
+ title: string;
170
+ stats: { views: number };
171
+ }
172
+ const Dashboard = createVar<DashboardData>();
173
+
174
+ path("/dashboard/:id", async (ctx) => {
175
+ const data = await fetchDashboard(ctx.params.id);
176
+ ctx.set(Dashboard, data); // type-checked
177
+ return <DashboardPage data={data} />;
178
+ }, { name: "dashboard" }, () => [
179
+ layout((ctx) => {
180
+ const data = ctx.get(Dashboard); // typed as DashboardData | undefined
181
+ return (
182
+ <div>
183
+ <h1>{data?.title}</h1>
184
+ <Outlet />
185
+ <ParallelOutlet name="@sidebar" />
186
+ </div>
187
+ );
188
+ }),
189
+ parallel({
190
+ "@sidebar": (ctx) => {
191
+ const data = ctx.get(Dashboard);
192
+ return <Sidebar stats={data?.stats} />;
193
+ },
194
+ }),
195
+ ])
196
+ ```
197
+
198
+ String keys still work (`ctx.set("key", value)` / `ctx.get("key")`), but
199
+ `createVar<T>()` is preferred for type safety.
200
+
201
+ Only route handlers and middleware can call `ctx.set()`. Layouts, parallels,
202
+ and intercepts can only read via `ctx.get()`.
203
+
204
+ #### Non-cacheable context variables
205
+
206
+ Mark a var as non-cacheable when it holds inherently request-specific data
207
+ (sessions, auth tokens, per-request IDs). There are two ways:
208
+
209
+ ```typescript
210
+ // Var-level: every value written to this var is non-cacheable
211
+ const Session = createVar<SessionData>({ cache: false });
212
+
213
+ // Write-level: escalate a normally-cacheable var for this specific write
214
+ const Theme = createVar<string>();
215
+ ctx.set(Theme, userTheme, { cache: false });
216
+ ```
217
+
218
+ "Least cacheable wins" — if either the var definition or the write site says
219
+ `cache: false`, the value is non-cacheable.
220
+
221
+ Reading a non-cacheable var inside `cache()` or `"use cache"` throws at
222
+ runtime. This prevents request-specific data from leaking into cached output:
223
+
224
+ ```typescript
225
+ // This throws — Session is non-cacheable
226
+ async function CachedWidget(ctx) {
227
+ "use cache";
228
+ const session = ctx.get(Session); // Error: non-cacheable var read inside cache scope
229
+ return <Widget />;
230
+ }
231
+ ```
232
+
233
+ Cacheable vars (the default) can be read freely inside cache scopes.
234
+
235
+ ### Revalidation Contracts for Handler Data
236
+
237
+ Handler-first guarantees apply within a single full render pass. For partial
238
+ action revalidation, define named revalidation contracts and reuse them on both
239
+ the producer route and the consumer child segments.
240
+
241
+ ```typescript
242
+ // revalidation-contracts.ts
243
+ export const revalidateCheckoutData = ({ actionId }) =>
244
+ actionId?.includes("src/actions/checkout.ts#") ?? false;
245
+
246
+ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
247
+ revalidate(revalidateCheckoutData), // producer (route handler) reruns
248
+ layout(CheckoutLayout, () => [
249
+ revalidate(revalidateCheckoutData), // consumer reruns
250
+ parallel({ "@summary": CheckoutSummary }, () => [
251
+ revalidate(revalidateCheckoutData),
252
+ ]),
253
+ ]),
254
+ ]);
255
+ ```
256
+
257
+ If children depend on multiple upstream domains, compose multiple contracts on
258
+ the same segment (`revalidateAuthData`, `revalidateCheckoutData`, and so on).
259
+
260
+ For cleaner route trees, expose contract helpers and spread them:
261
+
262
+ ```typescript
263
+ import { revalidate } from "@rangojs/router";
264
+
265
+ export const revalidateCheckout = () => [revalidate(revalidateCheckoutData)];
266
+
267
+ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
268
+ revalidateCheckout(),
269
+ layout(CheckoutLayout, () => [revalidateCheckout()]),
270
+ ]);
271
+ ```
272
+
273
+ For scope/revalidation guarantees and non-guarantees, see:
274
+ [docs/execution-model.md](../../docs/internal/execution-model.md)
275
+
276
+ ## Redirects
277
+
278
+ ### Basic redirect
279
+
280
+ ```typescript
281
+ import { redirect } from "@rangojs/router";
282
+
283
+ path("/old-page", () => redirect("/new-page"), { name: "oldPage" });
284
+ ```
285
+
286
+ ### Redirect with custom status
287
+
288
+ ```typescript
289
+ path("/moved", () => redirect("/new-location", 301), { name: "moved" });
290
+ ```
291
+
292
+ ### Redirect with location state
293
+
294
+ Carry typed state through redirects (e.g. flash messages):
295
+
296
+ ```typescript
297
+ import { redirect, createLocationState } from "@rangojs/router";
298
+
299
+ export const FlashMessage = createLocationState<{ text: string }>({
300
+ flash: true,
301
+ });
302
+
303
+ path(
304
+ "/save",
305
+ (ctx) => {
306
+ // ... save logic
307
+ return redirect("/dashboard", {
308
+ state: [FlashMessage({ text: "Item saved!" })],
309
+ });
310
+ },
311
+ { name: "save" },
312
+ );
313
+
314
+ // With custom status + state
315
+ path(
316
+ "/action",
317
+ (ctx) => {
318
+ return redirect("/target", {
319
+ status: 303,
320
+ state: [FlashMessage({ text: "Action complete" })],
321
+ });
322
+ },
323
+ { name: "action" },
324
+ );
325
+ ```
326
+
327
+ Read the state on the target page with `useLocationState(FlashMessage)`. The
328
+ `{ flash: true }` option makes it auto-clear. Without `{ flash: true }`,
329
+ state persists on back/forward. See `/hooks` for details.
330
+
331
+ ### ctx.setLocationState()
332
+
333
+ Attach location state to any server response (not just redirects):
334
+
335
+ ```typescript
336
+ path("/dashboard", (ctx) => {
337
+ ctx.setLocationState(ServerInfo({ data: "welcome" }));
338
+ return <Dashboard />;
339
+ }, { name: "dashboard" })
340
+ ```
341
+
342
+ State flows to the browser via the RSC payload and is merged into
343
+ `history.pushState()`. Only works for SPA (partial) navigations.
344
+
93
345
  ## Handler Context
94
346
 
95
347
  Every handler receives a context object:
96
348
 
97
349
  ```typescript
98
- interface HandlerContext<TParams = Record<string, string>> {
99
- params: TParams; // URL parameters
100
- request: Request; // Original request
101
- url: URL; // Parsed URL
102
- env: TEnv; // Environment (bindings + variables)
103
- use<T>(handle: Handle<T>): T; // Access handles
350
+ interface HandlerContext<TParams = {}, TEnv = DefaultEnv, TSearch = {}> {
351
+ params: TParams; // URL parameters
352
+ request: Request; // Original request
353
+ searchParams: URLSearchParams; // Query params (always URLSearchParams)
354
+ search: {} | ResolveSearchSchema<TSearch>; // Typed search params (from search schema)
355
+ url: URL; // Parsed URL
356
+ env: TEnv; // Environment (bindings + variables)
357
+ set(key: string, value: any): void; // Set context variable (untyped string key)
358
+ set<T>(contextVar: ContextVar<T>, value: T): void; // Set typed context variable
359
+ get(key: string): any; // Read context variable (untyped string key)
360
+ get<T>(contextVar: ContextVar<T>): T | undefined; // Read typed context variable
361
+ use<T>(handle: Handle<T>): T; // Access handles
362
+ reverse(
363
+ name: string,
364
+ params?: Record<string, string>,
365
+ search?: Record<string, unknown>,
366
+ ): string; // URL generation
367
+ setLocationState(entries: LocationStateEntry[]): void; // Attach state to response
104
368
  }
105
369
  ```
106
370
 
@@ -111,11 +375,11 @@ path("/product/:slug", (ctx) => {
111
375
  // Access URL params
112
376
  const { slug } = ctx.params;
113
377
 
114
- // Access query params
115
- const tab = ctx.url.searchParams.get("tab");
378
+ // Access query params (untyped - use search schema for typed access)
379
+ const tab = ctx.searchParams.get("tab");
116
380
 
117
- // Access environment
118
- const db = ctx.env.Bindings.DB;
381
+ // Access platform bindings
382
+ const db = ctx.env.DB;
119
383
 
120
384
  // Access handles
121
385
  const breadcrumbs = ctx.use(Breadcrumbs);
@@ -139,11 +403,38 @@ urls(({ path, layout }) => [
139
403
  ])
140
404
  ```
141
405
 
406
+ ## View Transitions
407
+
408
+ A route can configure its own `transition()` — the wrap goes around the route's component itself (routes are leaves; they have no separate default outlet channel). If the route component renders a `<ParallelOutlet />` directly, that slot remains inside the route's VT subtree, so prefer mounting parallel slots in a layout when combining intercept modals with route-level transitions. See [skills/view-transitions](../view-transitions/SKILL.md) for examples and the wrap-location rules across layouts, routes, and slots.
409
+
410
+ ## Handler-attached `.use`
411
+
412
+ Page handlers can carry their own loader, middleware, error boundaries, parallels, and other defaults via a `.use` callback — so the page is self-contained and reusable across mount sites without re-wiring the same items.
413
+
414
+ ```typescript
415
+ const ProductPage: Handler<"/product/:slug"> = async (ctx) => {
416
+ const product = await ctx.use(ProductLoader);
417
+ return <ProductView product={product} />;
418
+ };
419
+ ProductPage.use = () => [
420
+ loader(ProductLoader),
421
+ loading(<ProductSkeleton />),
422
+ middleware(async (ctx, next) => {
423
+ await next();
424
+ ctx.header("Cache-Control", "private, max-age=60");
425
+ }),
426
+ ];
427
+
428
+ // Mount site has no per-page wiring — defaults travel with the handler.
429
+ path("/product/:slug", ProductPage, { name: "product" });
430
+ ```
431
+
432
+ Explicit `use()` at the mount site merges with `handler.use` (handler defaults first, explicit second). See [skills/handler-use](../handler-use/SKILL.md) for the merge order, allowed item types per mount site, and override semantics.
433
+
142
434
  ## Complete Example
143
435
 
144
436
  ```typescript
145
- import { urls } from "@rangojs/router";
146
- import { Breadcrumbs } from "./handles/breadcrumbs";
437
+ import { urls, Breadcrumbs } from "@rangojs/router";
147
438
 
148
439
  export const urlpatterns = urls(({ path, layout, loader, loading }) => [
149
440
  // Simple route