@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.30

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 (297) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +883 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4655 -747
  5. package/package.json +78 -50
  6. package/skills/cache-guide/SKILL.md +262 -0
  7. package/skills/caching/SKILL.md +54 -25
  8. package/skills/composability/SKILL.md +172 -0
  9. package/skills/debug-manifest/SKILL.md +12 -8
  10. package/skills/document-cache/SKILL.md +23 -21
  11. package/skills/fonts/SKILL.md +167 -0
  12. package/skills/hooks/SKILL.md +390 -63
  13. package/skills/host-router/SKILL.md +218 -0
  14. package/skills/intercept/SKILL.md +133 -10
  15. package/skills/layout/SKILL.md +102 -5
  16. package/skills/links/SKILL.md +239 -0
  17. package/skills/loader/SKILL.md +366 -29
  18. package/skills/middleware/SKILL.md +173 -36
  19. package/skills/mime-routes/SKILL.md +128 -0
  20. package/skills/parallel/SKILL.md +80 -3
  21. package/skills/prerender/SKILL.md +643 -0
  22. package/skills/rango/SKILL.md +86 -16
  23. package/skills/response-routes/SKILL.md +411 -0
  24. package/skills/route/SKILL.md +227 -14
  25. package/skills/router-setup/SKILL.md +225 -32
  26. package/skills/tailwind/SKILL.md +129 -0
  27. package/skills/theme/SKILL.md +12 -11
  28. package/skills/typesafety/SKILL.md +401 -75
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +10 -4
  31. package/src/bin/rango.ts +321 -0
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +87 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +20 -4
  38. package/src/browser/logging.ts +55 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +201 -553
  41. package/src/browser/navigation-client.ts +124 -71
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +295 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +267 -317
  46. package/src/browser/prefetch/cache.ts +146 -0
  47. package/src/browser/prefetch/fetch.ts +135 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +42 -0
  50. package/src/browser/prefetch/queue.ts +88 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +173 -73
  53. package/src/browser/react/NavigationProvider.tsx +138 -27
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +37 -0
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +49 -65
  65. package/src/browser/react/use-href.tsx +20 -188
  66. package/src/browser/react/use-link-status.ts +6 -5
  67. package/src/browser/react/use-mount.ts +31 -0
  68. package/src/browser/react/use-navigation.ts +27 -78
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +111 -26
  76. package/src/browser/scroll-restoration.ts +92 -16
  77. package/src/browser/segment-reconciler.ts +216 -0
  78. package/src/browser/segment-structure-assert.ts +83 -0
  79. package/src/browser/server-action-bridge.ts +504 -584
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +92 -57
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +438 -0
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +35 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +469 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +338 -0
  100. package/src/cache/cache-scope.ts +120 -303
  101. package/src/cache/cf/cf-cache-store.ts +119 -7
  102. package/src/cache/cf/index.ts +8 -2
  103. package/src/cache/document-cache.ts +101 -72
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +0 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +10 -15
  114. package/src/client.tsx +114 -135
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +17 -7
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +34 -19
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +165 -0
  124. package/src/host/errors.ts +97 -0
  125. package/src/host/index.ts +53 -0
  126. package/src/host/pattern-matcher.ts +214 -0
  127. package/src/host/router.ts +352 -0
  128. package/src/host/testing.ts +79 -0
  129. package/src/host/types.ts +146 -0
  130. package/src/host/utils.ts +25 -0
  131. package/src/href-client.ts +135 -49
  132. package/src/index.rsc.ts +182 -17
  133. package/src/index.ts +238 -24
  134. package/src/internal-debug.ts +11 -0
  135. package/src/loader.rsc.ts +27 -142
  136. package/src/loader.ts +27 -10
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +37 -0
  140. package/src/prerender/store.ts +185 -0
  141. package/src/prerender.ts +463 -0
  142. package/src/reverse.ts +330 -0
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +9 -11
  145. package/src/route-definition/dsl-helpers.ts +934 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1388
  151. package/src/route-map-builder.ts +241 -112
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +70 -9
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +158 -0
  158. package/src/router/handler-context.ts +371 -81
  159. package/src/router/intercept-resolution.ts +395 -0
  160. package/src/router/lazy-includes.ts +234 -0
  161. package/src/router/loader-resolution.ts +215 -122
  162. package/src/router/logging.ts +248 -0
  163. package/src/router/manifest.ts +155 -32
  164. package/src/router/match-api.ts +620 -0
  165. package/src/router/match-context.ts +5 -3
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +80 -93
  168. package/src/router/match-middleware/cache-lookup.ts +382 -9
  169. package/src/router/match-middleware/cache-store.ts +51 -22
  170. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  171. package/src/router/match-middleware/segment-resolution.ts +24 -6
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +34 -29
  174. package/src/router/metrics.ts +235 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +324 -367
  178. package/src/router/pattern-matching.ts +321 -30
  179. package/src/router/prerender-match.ts +400 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +137 -38
  182. package/src/router/router-context.ts +36 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +570 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +198 -0
  189. package/src/router/segment-resolution/revalidation.ts +1241 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -0
  192. package/src/router/segment-wrappers.ts +289 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +239 -0
  197. package/src/router/types.ts +77 -3
  198. package/src/router.ts +688 -3656
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +786 -760
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +5 -25
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +235 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +40 -14
  215. package/src/search-params.ts +230 -0
  216. package/src/segment-system.tsx +57 -61
  217. package/src/server/context.ts +202 -51
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +37 -0
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +422 -70
  223. package/src/server.ts +36 -120
  224. package/src/ssr/index.tsx +157 -26
  225. package/src/static-handler.ts +114 -0
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +687 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +102 -0
  241. package/src/types/segments.ts +148 -0
  242. package/src/types.ts +1 -1577
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -726
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +110 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -782
  261. package/src/vite/plugin-types.ts +131 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  266. package/src/vite/plugins/expose-id-utils.ts +287 -0
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +254 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +510 -0
  280. package/src/vite/router-discovery.ts +785 -0
  281. package/src/vite/utils/ast-handler-extract.ts +517 -0
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -3
  289. package/src/browser/lru-cache.ts +0 -69
  290. package/src/browser/request-controller.ts +0 -164
  291. package/src/cache/memory-store.ts +0 -253
  292. package/src/href-context.ts +0 -33
  293. package/src/href.ts +0 -255
  294. package/src/vite/expose-handle-id.ts +0 -209
  295. package/src/vite/expose-loader-id.ts +0 -357
  296. package/src/vite/expose-location-state-id.ts +0 -177
  297. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/README.md CHANGED
@@ -1,8 +1,31 @@
1
1
  # @rangojs/router
2
2
 
3
- > **Warning:** This package is experimental and under active development. APIs may change without notice.
3
+ Named-route RSC router with structural composability and type-safe partial rendering for Vite.
4
4
 
5
- Type-safe RSC router with partial rendering support for Vite.
5
+ > **Experimental:** This package is under active development. APIs may change between releases. Install with `@experimental` tag.
6
+
7
+ ## Features
8
+
9
+ - **Named routes** — `reverse("blogPost", { slug })` for type-safe URL generation (Django-style)
10
+ - **Structural composability** — Attach routes, loaders, middleware, handles, caching, prerendering, and static generation without hiding the route tree
11
+ - **Composable URL patterns** — Django-style `urls()` DSL with `path`, `layout`, `include`
12
+ - **Data loaders** — `createLoader()` with automatic streaming and Suspense integration
13
+ - **Live data layer** — Pre-render or cache the UI shell while loaders stay live by default at request time
14
+ - **Layouts & nesting** — Nested layouts with `<Outlet />` and parallel routes
15
+ - **Segment-level caching** — `cache()` DSL with TTL/SWR and pluggable cache stores
16
+ - **Middleware** — Route-level middleware with cookie and header access
17
+ - **Pre-rendering** — `Prerender()` and `Static()` handlers for build-time rendering
18
+ - **Theme support** — Light/dark mode with FOUC prevention and system detection
19
+ - **Host routing** — Multi-app routing by domain/subdomain via `@rangojs/router/host`
20
+ - **Response routes** — `path.json()`, `path.text()`, `path.xml()` for API endpoints
21
+ - **Trailing slash control** — Per-route canonical URLs with `"never"`, `"always"`, or `"ignore"`
22
+ - **CLI codegen** — `rango generate` for route type generation
23
+
24
+ ## Design Docs
25
+
26
+ - [Execution model](./docs/internal/execution-model.md)
27
+ - [Semantic change checklist](./docs/internal/semantic-change-checklist.md)
28
+ - [Stability roadmap](./docs/internal/stability-roadmap.md)
6
29
 
7
30
  ## Installation
8
31
 
@@ -10,9 +33,865 @@ Type-safe RSC router with partial rendering support for Vite.
10
33
  npm install @rangojs/router@experimental
11
34
  ```
12
35
 
13
- ## Status
36
+ Peer dependencies:
37
+
38
+ ```bash
39
+ npm install react @vitejs/plugin-rsc
40
+ ```
41
+
42
+ For Cloudflare Workers:
43
+
44
+ ```bash
45
+ npm install @cloudflare/vite-plugin
46
+ ```
47
+
48
+ ## Import Paths
49
+
50
+ Use these import paths consistently:
51
+
52
+ - `@rangojs/router` — server/RSC router APIs, route DSL, `createRouter`, `urls`, `redirect`, `Prerender`, `Static`, shared types
53
+ - `@rangojs/router/client` — hooks and components such as `Link`, `Outlet`, `href`, `useNavigation`, `useLoader`, `useAction`, `useLocationState`
54
+ - `@rangojs/router/cache` — public cache APIs such as `CFCacheStore`, `MemorySegmentCacheStore`, `createDocumentCacheMiddleware`
55
+ - `@rangojs/router/host`, `@rangojs/router/theme`, `@rangojs/router/vite` — specialized public subpaths
56
+ - `@rangojs/router/rsc`, `@rangojs/router/ssr` — advanced server-only integration subpaths for custom request/HTML pipelines
57
+
58
+ Use only subpaths that are explicitly exported from the package. Avoid deep imports such as `@rangojs/router/cache/cf`.
59
+
60
+ `@rangojs/router` is conditionally resolved. Server-only root APIs such as
61
+ `createRouter()`, `urls()`, `redirect()`, `Prerender()`, and `cookies()` rely on
62
+ the `react-server` export condition and are meant to run in router definitions,
63
+ handlers, and other RSC/server modules. Outside that environment the root entry
64
+ falls back to stub implementations that throw guidance errors.
65
+
66
+ If you hit a root-entrypoint stub error:
67
+
68
+ - hooks and components like `Link`, `Outlet`, `useLoader`, `useNavigation`, and `MetaTags` belong in `@rangojs/router/client`
69
+ - cache APIs like `CFCacheStore` and `createDocumentCacheMiddleware` belong in `@rangojs/router/cache`
70
+ - host-router APIs belong in `@rangojs/router/host`
71
+
72
+ ## Quick Start
73
+
74
+ ### Vite Config
75
+
76
+ ```ts
77
+ // vite.config.ts
78
+ import react from "@vitejs/plugin-react";
79
+ import { defineConfig } from "vite";
80
+ import { rango } from "@rangojs/router/vite";
81
+
82
+ export default defineConfig({
83
+ plugins: [react(), rango({ preset: "cloudflare" })],
84
+ });
85
+ ```
86
+
87
+ ### Router
88
+
89
+ This file is a server/RSC module and should import router construction APIs from
90
+ `@rangojs/router`.
91
+
92
+ ```tsx
93
+ // src/router.tsx
94
+ import { createRouter, urls } from "@rangojs/router";
95
+ import { Document } from "./document";
96
+
97
+ const blogPatterns = urls(({ path }) => [
98
+ path("/", BlogIndexPage, { name: "index" }),
99
+ path("/:slug", BlogPostPage, { name: "post" }),
100
+ ]);
101
+
102
+ const urlpatterns = urls(({ path, include }) => [
103
+ path("/", HomePage, { name: "home" }),
104
+ include("/blog", blogPatterns, { name: "blog" }),
105
+ ]);
106
+
107
+ export const router = createRouter({ document: Document }).routes(urlpatterns);
108
+
109
+ // Export typed reverse function for URL generation by route name
110
+ export const reverse = router.reverse;
111
+
112
+ // reverse("blog.post", { slug: "hello-world" }) -> "/blog/hello-world"
113
+ ```
114
+
115
+ ### Document
116
+
117
+ ```tsx
118
+ // src/document.tsx
119
+ "use client";
120
+
121
+ import type { ReactNode } from "react";
122
+ import { MetaTags } from "@rangojs/router/client";
123
+
124
+ export function Document({ children }: { children: ReactNode }) {
125
+ return (
126
+ <html lang="en">
127
+ <head>
128
+ <MetaTags />
129
+ </head>
130
+ <body>{children}</body>
131
+ </html>
132
+ );
133
+ }
134
+ ```
135
+
136
+ ## Defining Routes
137
+
138
+ Rango is a named-route router first.
139
+
140
+ Paths define where a route lives. Names define how the app refers to it.
141
+
142
+ It is also structurally composable.
143
+
144
+ As an app grows, routes can pull in external handlers, loaders, middleware, handles, cache policy, intercepts, prerendering, and static generation while keeping the route tree visible at the composition site.
145
+
146
+ ### Named Routes
147
+
148
+ ```tsx
149
+ import { urls } from "@rangojs/router";
150
+
151
+ const urlpatterns = urls(({ path }) => [
152
+ path("/", HomePage, { name: "home" }),
153
+ path("/product/:slug", ProductPage, { name: "product" }),
154
+ path("/search/:query?", SearchPage, { name: "search" }),
155
+ path("/files/*", FilesPage, { name: "files" }),
156
+ ]);
157
+ ```
158
+
159
+ Use `reverse()` as the default way to link to routes:
160
+
161
+ ```tsx
162
+ router.reverse("product", { slug: "widget" }); // "/product/widget"
163
+ router.reverse("search", undefined, { q: "rsc" }); // "/search?q=rsc"
164
+ ```
165
+
166
+ ### Composable URL Modules
167
+
168
+ Local route names compose cleanly with `include(..., { name })`:
169
+
170
+ ```tsx
171
+ import { urls } from "@rangojs/router";
172
+
173
+ export const blogPatterns = urls(({ path }) => [
174
+ path("/", BlogIndexPage, { name: "index" }),
175
+ path("/:slug", BlogPostPage, { name: "post" }),
176
+ ]);
177
+
178
+ export const urlpatterns = urls(({ path, include }) => [
179
+ path("/", HomePage, { name: "home" }),
180
+ include("/blog", blogPatterns, { name: "blog" }),
181
+ ]);
182
+
183
+ router.reverse("blog.index"); // "/blog"
184
+ router.reverse("blog.post", { slug: "hello-world" }); // "/blog/hello-world"
185
+ ```
186
+
187
+ This is the core composition model:
188
+
189
+ - Paths stay local to the module that defines them
190
+ - Names become stable references across the app
191
+ - `include()` scales those names without forcing raw path-string coupling
192
+
193
+ ### Structural Composability
194
+
195
+ Rango avoids the usual tradeoff between modularity and visibility.
196
+
197
+ You can extract route behavior into separate files or packages and still keep one readable route definition that shows the structure of the app.
198
+
199
+ ```tsx
200
+ import { urls } from "@rangojs/router";
201
+ import { ProductPage } from "./routes/product";
202
+ import { ProductLoader } from "./loaders/product";
203
+ import { productMiddleware } from "./middleware/product";
204
+ import { productRevalidate } from "./revalidation/product";
205
+
206
+ const shopPatterns = urls(({ path, loader, middleware, revalidate, cache }) => [
207
+ path("/product/:slug", ProductPage, { name: "product" }, () => [
208
+ middleware(productMiddleware),
209
+ loader(ProductLoader),
210
+ revalidate(productRevalidate),
211
+ cache({ ttl: 300 }),
212
+ ]),
213
+ ]);
214
+ ```
215
+
216
+ The route tree stays explicit even when behavior is modular.
217
+
218
+ This applies to:
219
+
220
+ - external route modules mounted with `include()`
221
+ - imported loaders, middleware, and handles attached at the route site
222
+ - prerendering and static generation attached without turning the route tree opaque
223
+
224
+ ### Loaders As the Live Data Layer
225
+
226
+ Rango separates app structure from app data.
227
+
228
+ Routes, layouts, and pre-rendered segments can be static or cached, while
229
+ loaders stay live by default and re-resolve at request time.
230
+
231
+ This means you can pre-render or cache the shell of a page without freezing its
232
+ data.
233
+
234
+ - `cache()` caches route structure and rendered UI segments
235
+ - `Prerender()` skips loaders at build time
236
+ - `loader()` provides fresh request-time data
237
+ - individual loaders can opt into caching explicitly when needed
238
+
239
+ ```tsx
240
+ import { urls, Prerender } from "@rangojs/router";
241
+ import { ArticleLoader } from "./loaders/article";
242
+
243
+ const docsPatterns = urls(({ path, loader }) => [
244
+ path("/docs/:slug", Prerender(DocsArticle), { name: "docs.article" }, () => [
245
+ loader(ArticleLoader), // fresh by default
246
+ ]),
247
+ ]);
248
+ ```
249
+
250
+ Pre-render the page, keep the data live.
251
+
252
+ ### Typed Handlers
253
+
254
+ Route handlers receive a typed context with params, search params, and `reverse()`:
255
+
256
+ ```tsx
257
+ import type { Handler } from "@rangojs/router";
258
+
259
+ export const ProductPage: Handler<"product"> = (ctx) => {
260
+ const { slug } = ctx.params; // typed from pattern
261
+ const homeUrl = ctx.reverse("home"); // type-safe URL by route name
262
+ return <h1>Product: {slug}</h1>;
263
+ };
264
+ ```
265
+
266
+ ### Choosing a Handler Style
267
+
268
+ All handler typing styles are supported, but they solve different problems:
269
+
270
+ - `Handler<"product">` — default for named app routes
271
+ - `Handler<".post", ScopedRouteMap<"blog">>` — best for reusable included modules
272
+ - `Handler<"/blog/:slug">` — good for unnamed or local-only extracted handlers
273
+ - `Handler<{ slug: string }>` — escape hatch for advanced or decoupled cases
274
+
275
+ Example of a scoped local name inside a mounted module:
276
+
277
+ ```tsx
278
+ import type { Handler, ScopedRouteMap } from "@rangojs/router";
279
+
280
+ type BlogRoutes = ScopedRouteMap<"blog">;
281
+
282
+ export const BlogPostPage: Handler<".post", BlogRoutes> = (ctx) => {
283
+ return <a href={ctx.reverse(".index")}>Back to blog</a>;
284
+ };
285
+ ```
286
+
287
+ See [`../../docs/named-routes.md`](../../docs/named-routes.md) for the recommended mental model.
288
+
289
+ ### Search Params
290
+
291
+ Define a search schema on the route for type-safe search parameters:
292
+
293
+ ```tsx
294
+ const urlpatterns = urls(({ path }) => [
295
+ path("/search", SearchPage, {
296
+ name: "search",
297
+ search: { q: "string", page: "number?", sort: "string?" },
298
+ }),
299
+ ]);
300
+
301
+ // Handler receives typed search params via ctx.search
302
+ const SearchPage: Handler<"search"> = (ctx) => {
303
+ const { q, page, sort } = ctx.search;
304
+ // q: string, page: number | undefined, sort: string | undefined
305
+ };
306
+ ```
307
+
308
+ ### Trailing Slash Handling
309
+
310
+ Trailing slash behavior is a current `path()` feature.
311
+
312
+ Set it per route with `trailingSlash`:
313
+
314
+ ```tsx
315
+ const urlpatterns = urls(({ path }) => [
316
+ path("/about", AboutPage, {
317
+ name: "about",
318
+ trailingSlash: "never",
319
+ }),
320
+ path("/docs/", DocsPage, {
321
+ name: "docs",
322
+ trailingSlash: "always",
323
+ }),
324
+ path("/webhook", WebhookHandler, {
325
+ name: "webhook",
326
+ trailingSlash: "ignore",
327
+ }),
328
+ ]);
329
+ ```
330
+
331
+ Modes:
332
+
333
+ - `"never"` — canonical URL has no trailing slash, redirects `/about/` to `/about`
334
+ - `"always"` — canonical URL has a trailing slash, redirects `/docs` to `/docs/`
335
+ - `"ignore"` — matches both forms without redirect
336
+
337
+ Default behavior when `trailingSlash` is omitted:
338
+
339
+ - There is no separate global default mode
340
+ - If the pattern is defined without a trailing slash, the canonical URL is the no-slash form
341
+ - If the pattern is defined with a trailing slash, the canonical URL is the slash form
342
+ - The router redirects to the canonical form based on the pattern you defined
343
+
344
+ The recommended public API is the per-route `path(..., { trailingSlash })` option. Use `"ignore"` sparingly, especially on content pages, because `/x` and `/x/` are distinct URLs.
345
+
346
+ ### Response Routes
347
+
348
+ Define API endpoints that bypass the RSC pipeline:
349
+
350
+ ```tsx
351
+ const urlpatterns = urls(({ path }) => [
352
+ path.json("/api/health", () => ({ status: "ok" }), { name: "health" }),
353
+ path.text("/robots.txt", () => "User-agent: *\nAllow: /", { name: "robots" }),
354
+ path.xml("/feed.xml", () => "<rss>...</rss>", { name: "feed" }),
355
+ ]);
356
+ ```
357
+
358
+ Response types available: `path.json()`, `path.text()`, `path.html()`, `path.xml()`, `path.image()`, `path.stream()`, `path.any()`.
359
+
360
+ ## Layouts & Nesting
361
+
362
+ ### Layouts with Outlet
363
+
364
+ ```tsx
365
+ import { urls } from "@rangojs/router";
366
+
367
+ const urlpatterns = urls(({ path, layout }) => [
368
+ layout(<MainLayout />, () => [
369
+ path("/", HomePage, { name: "home" }),
370
+ path("/about", AboutPage, { name: "about" }),
371
+ ]),
372
+ ]);
373
+ ```
374
+
375
+ ```tsx
376
+ "use client";
377
+ import { Outlet } from "@rangojs/router/client";
378
+
379
+ function MainLayout() {
380
+ return (
381
+ <div>
382
+ <nav>...</nav>
383
+ <Outlet />
384
+ </div>
385
+ );
386
+ }
387
+ ```
388
+
389
+ ### Loading Skeletons
390
+
391
+ ```tsx
392
+ const urlpatterns = urls(({ path, loading }) => [
393
+ path("/product/:slug", ProductPage, { name: "product" }, () => [
394
+ loading(<ProductSkeleton />),
395
+ ]),
396
+ ]);
397
+ ```
398
+
399
+ ### Parallel Routes
400
+
401
+ ```tsx
402
+ const urlpatterns = urls(({ path, layout, parallel, loader, loading }) => [
403
+ layout(BlogLayout, () => [
404
+ parallel({ "@sidebar": BlogSidebarHandler }, () => [
405
+ loader(BlogSidebarLoader),
406
+ loading(<SidebarSkeleton />),
407
+ ]),
408
+ path("/blog", BlogIndexPage, { name: "blog" }),
409
+ path("/blog/:slug", BlogPostPage, { name: "blogPost" }),
410
+ ]),
411
+ ]);
412
+ ```
413
+
414
+ ## Data Loaders
415
+
416
+ ### Creating a Loader
417
+
418
+ ```tsx
419
+ import { createLoader } from "@rangojs/router";
420
+
421
+ export const BlogSidebarLoader = createLoader(async (ctx) => {
422
+ const posts = await db.getRecentPosts();
423
+ return { posts, loadedAt: new Date().toISOString() };
424
+ });
425
+ ```
426
+
427
+ ### Using in Server Components (Handlers)
428
+
429
+ ```tsx
430
+ import type { HandlerContext } from "@rangojs/router";
431
+ import { BlogSidebarLoader } from "./loaders/blog";
432
+
433
+ async function BlogSidebarHandler(ctx: HandlerContext) {
434
+ const { posts } = await ctx.use(BlogSidebarLoader);
435
+ return (
436
+ <ul>
437
+ {posts.map((p) => (
438
+ <li key={p.slug}>{p.title}</li>
439
+ ))}
440
+ </ul>
441
+ );
442
+ }
443
+ ```
444
+
445
+ ### Using in Client Components
446
+
447
+ ```tsx
448
+ "use client";
449
+ import { useLoader } from "@rangojs/router/client";
450
+ import { BlogSidebarLoader } from "./loaders/blog";
451
+
452
+ function BlogSidebar() {
453
+ const { data } = useLoader(BlogSidebarLoader);
454
+ return (
455
+ <ul>
456
+ {data.posts.map((p) => (
457
+ <li key={p.slug}>{p.title}</li>
458
+ ))}
459
+ </ul>
460
+ );
461
+ }
462
+ ```
463
+
464
+ ### Attaching Loaders to Routes
465
+
466
+ ```tsx
467
+ const urlpatterns = urls(({ path, loader }) => [
468
+ path("/blog", BlogIndexPage, { name: "blog" }, () => [
469
+ loader(BlogSidebarLoader),
470
+ ]),
471
+ ]);
472
+ ```
473
+
474
+ ## Navigation & Links
475
+
476
+ ### Named Routes with `reverse()` (Server Components)
477
+
478
+ In server components, use `reverse()` to generate URLs by route name:
479
+
480
+ ```tsx
481
+ import { Link } from "@rangojs/router/client";
482
+ import { reverse } from "./router";
483
+
484
+ function BlogIndex() {
485
+ return (
486
+ <nav>
487
+ <Link to={reverse("home")}>Home</Link>
488
+ <Link to={reverse("blogPost", { slug: "my-post" })}>My Post</Link>
489
+ <Link to={reverse("about")}>About</Link>
490
+ </nav>
491
+ );
492
+ }
493
+ ```
494
+
495
+ `reverse()` is type-safe — route names and required params are checked at compile time. Included routes use dotted names: `reverse("api.health")`.
496
+
497
+ Handlers also have `ctx.reverse()` directly on the context:
498
+
499
+ ```tsx
500
+ const BlogPostPage: Handler<"blogPost"> = (ctx) => {
501
+ const backUrl = ctx.reverse("blog");
502
+ return <Link to={backUrl}>Back to blog</Link>;
503
+ };
504
+ ```
505
+
506
+ ### `href()` for Path Validation (Client Components)
507
+
508
+ In client components, use `href()` for compile-time path validation:
509
+
510
+ ```tsx
511
+ "use client";
512
+ import { Link, href } from "@rangojs/router/client";
513
+
514
+ function Nav() {
515
+ return (
516
+ <nav>
517
+ <Link to={href("/")}>Home</Link>
518
+ <Link to={href("/blog")} prefetch="adaptive">
519
+ Blog
520
+ </Link>
521
+ <Link to={href("/about")}>About</Link>
522
+ </nav>
523
+ );
524
+ }
525
+ ```
526
+
527
+ `href()` validates that the path matches a registered route pattern at compile time (e.g. `/blog/my-post` matches `/blog/:slug`).
528
+
529
+ ### Navigation Hooks
530
+
531
+ ```tsx
532
+ "use client";
533
+ import { useNavigation, useRouter } from "@rangojs/router/client";
534
+
535
+ function SearchForm() {
536
+ const router = useRouter();
537
+ const nav = useNavigation();
538
+
539
+ function handleSubmit(query: string) {
540
+ router.push(`/search?q=${encodeURIComponent(query)}`);
541
+ }
542
+
543
+ return <form onSubmit={...}>{nav.state !== "idle" && <Spinner />}</form>;
544
+ }
545
+ ```
546
+
547
+ ### Scroll Restoration
548
+
549
+ ```tsx
550
+ "use client";
551
+ import { ScrollRestoration } from "@rangojs/router/client";
552
+
553
+ function Document({ children }) {
554
+ return (
555
+ <html>
556
+ <body>
557
+ {children}
558
+ <ScrollRestoration />
559
+ </body>
560
+ </html>
561
+ );
562
+ }
563
+ ```
564
+
565
+ ## Includes (Composable Modules)
566
+
567
+ Split URL patterns into composable modules with `include()`:
568
+
569
+ ```tsx
570
+ // src/api/urls.tsx
571
+ import { urls } from "@rangojs/router";
572
+
573
+ export const apiPatterns = urls(({ path }) => [
574
+ path.json("/health", () => ({ status: "ok" }), { name: "health" }),
575
+ path.json("/products", getProducts, { name: "products" }),
576
+ ]);
577
+
578
+ // src/urls.tsx
579
+ import { urls } from "@rangojs/router";
580
+ import { apiPatterns } from "./api/urls";
581
+
582
+ export const urlpatterns = urls(({ path, include }) => [
583
+ path("/", HomePage, { name: "home" }),
584
+ include("/api", apiPatterns, { name: "api" }),
585
+ // Mounts apiPatterns under /api: /api/health, /api/products
586
+ ]);
587
+ ```
588
+
589
+ Included route names are prefixed with the include name: `reverse("api.health")`, `reverse("api.products")`.
590
+
591
+ ### Include name scoping
592
+
593
+ The `name` option controls how child route names appear globally:
594
+
595
+ | Form | Child names | Generated types | Reverse resolution |
596
+ | ---------------------------------- | ------------------- | ---------------------- | -------------------------------------------------------------------- |
597
+ | `include("/x", p, { name: "ns" })` | `ns.child` | Exported as `ns.child` | `reverse("ns.child")` globally, `reverse(".child")` inside |
598
+ | `include("/x", p, { name: "" })` | `child` (flattened) | Exported as-is | `reverse("child")` globally, `reverse(".child")` inside (root-scope) |
599
+ | `include("/x", p)` | Private scope | Not exported | `reverse(".child")` inside only |
600
+
601
+ Without a `name`, included routes are local to the mounted module. They still match requests and render normally, but their names are hidden from the generated route map and cannot be reversed globally. Use `{ name: "" }` to merge children into the parent namespace without adding a prefix.
602
+
603
+ **`{ name: "" }` is flattening, not isolation.** Flattened routes behave as if defined inline at the include site — dot-local reverse (`.name`) can reach any sibling route at root scope, including routes from other `{ name: "" }` mounts. If you need module-level isolation, omit the `name` option or use a namespace.
604
+
605
+ ## Middleware
606
+
607
+ ```tsx
608
+ const urlpatterns = urls(({ path, middleware }) => [
609
+ middleware(
610
+ async (ctx, next) => {
611
+ const start = Date.now();
612
+ const response = await next();
613
+ console.log(
614
+ `${ctx.request.method} ${ctx.url.pathname} ${Date.now() - start}ms`,
615
+ );
616
+ return response;
617
+ },
618
+ () => [path("/dashboard", DashboardPage, { name: "dashboard" })],
619
+ ),
620
+ ]);
621
+ ```
622
+
623
+ ## Caching
624
+
625
+ ### Route-Level Caching
626
+
627
+ ```tsx
628
+ const urlpatterns = urls(({ path, cache }) => [
629
+ cache({ ttl: 60, swr: 300 }, () => [
630
+ path("/blog", BlogIndexPage, { name: "blog" }),
631
+ path("/blog/:slug", BlogPostPage, { name: "blogPost" }),
632
+ ]),
633
+ ]);
634
+ ```
635
+
636
+ ### Cache Store Configuration
637
+
638
+ ```tsx
639
+ import { createRouter } from "@rangojs/router";
640
+ import {
641
+ CFCacheStore,
642
+ createDocumentCacheMiddleware,
643
+ } from "@rangojs/router/cache";
644
+
645
+ export const router = createRouter({
646
+ document: Document,
647
+ cache: (env) => ({
648
+ store: new CFCacheStore({
649
+ defaults: { ttl: 60, swr: 300 },
650
+ ctx: env.ctx,
651
+ }),
652
+ }),
653
+ })
654
+ .use(createDocumentCacheMiddleware())
655
+ .routes(urlpatterns);
656
+ ```
657
+
658
+ Available cache stores:
659
+
660
+ - `CFCacheStore` — Cloudflare edge cache (production)
661
+ - `MemorySegmentCacheStore` — In-memory cache (development/testing)
662
+
663
+ ## Pre-rendering
664
+
665
+ Pre-rendering generates route segments at build time. The worker handles all requests — there are no static files served from assets.
666
+
667
+ ### Static Segments
668
+
669
+ Use `Static()` for segments rendered once at build time (no params). Works on `path()`, `layout()`, and `parallel()`:
670
+
671
+ ```tsx
672
+ import { Static } from "@rangojs/router";
673
+
674
+ export const AboutPage = Static(async () => {
675
+ return <article>...</article>;
676
+ });
677
+
678
+ export const DocsNav = Static(async () => {
679
+ const items = await readDocsNavItems();
680
+ return (
681
+ <nav>
682
+ {items.map((i) => (
683
+ <a key={i.slug} href={i.slug}>
684
+ {i.title}
685
+ </a>
686
+ ))}
687
+ </nav>
688
+ );
689
+ });
690
+ ```
691
+
692
+ ### Dynamic Routes with Prerender
693
+
694
+ Use `Prerender()` for route-scoped pre-rendering. With params, provide `getParams` first, handler second:
695
+
696
+ ```tsx
697
+ import { Prerender } from "@rangojs/router";
698
+
699
+ export const BlogPost = Prerender(
700
+ async () => {
701
+ const slugs = await getAllBlogSlugs();
702
+ return slugs.map((slug) => ({ slug }));
703
+ },
704
+ async (ctx) => {
705
+ const post = await getPost(ctx.params.slug);
706
+ return <article>{post.content}</article>;
707
+ },
708
+ );
709
+ ```
710
+
711
+ ### Passthrough for Unknown Params
712
+
713
+ ```tsx
714
+ import { Prerender } from "@rangojs/router";
715
+
716
+ export const ProductPage = Prerender(
717
+ async () => {
718
+ const featured = await db.getFeaturedProducts();
719
+ return featured.map((p) => ({ id: p.id }));
720
+ },
721
+ async (ctx) => {
722
+ const product = await db.getProduct(ctx.params.id);
723
+ return <Product data={product} />;
724
+ },
725
+ { passthrough: true },
726
+ );
727
+ ```
728
+
729
+ With `passthrough: true`, known params are served from the build-time cache and unknown params fall through to live rendering.
730
+
731
+ Handlers can also skip individual param sets with `ctx.passthrough()`, deferring them to the live handler at runtime:
732
+
733
+ ```tsx
734
+ export const ProductPage = Prerender(
735
+ async () => {
736
+ const all = await db.getAllProducts();
737
+ return all.map((p) => ({ id: p.id }));
738
+ },
739
+ async (ctx) => {
740
+ const product = await db.getProduct(ctx.params.id);
741
+ if (!product.published) return ctx.passthrough();
742
+ return <Product data={product} />;
743
+ },
744
+ { passthrough: true },
745
+ );
746
+ ```
747
+
748
+ ## Theme
749
+
750
+ ### Router Configuration
751
+
752
+ ```tsx
753
+ export const router = createRouter({
754
+ document: Document,
755
+ theme: {
756
+ defaultTheme: "light",
757
+ themes: ["light", "dark", "system"],
758
+ attribute: "class",
759
+ enableSystem: true,
760
+ },
761
+ }).routes(urlpatterns);
762
+ ```
763
+
764
+ ### Theme Toggle
765
+
766
+ ```tsx
767
+ "use client";
768
+ import { useTheme } from "@rangojs/router/theme";
769
+
770
+ function ThemeToggle() {
771
+ const { theme, setTheme, themes } = useTheme();
772
+ return (
773
+ <select value={theme} onChange={(e) => setTheme(e.target.value)}>
774
+ {themes.map((t) => (
775
+ <option key={t}>{t}</option>
776
+ ))}
777
+ </select>
778
+ );
779
+ }
780
+ ```
781
+
782
+ ## Host Routing
783
+
784
+ Route requests to different apps based on domain/subdomain patterns using `@rangojs/router/host`:
785
+
786
+ ```tsx
787
+ // worker.rsc.tsx
788
+ import { createHostRouter } from "@rangojs/router/host";
789
+
790
+ const hostRouter = createHostRouter();
791
+
792
+ hostRouter.host(["*.localhost"]).map(() => import("./apps/admin/handler.js"));
793
+ hostRouter.host(["localhost"]).map(() => import("./apps/site/handler.js"));
794
+ hostRouter.fallback().map(() => import("./apps/site/handler.js"));
795
+
796
+ export default {
797
+ async fetch(request, env, ctx) {
798
+ return hostRouter.match(request, { env, ctx });
799
+ },
800
+ };
801
+ ```
802
+
803
+ Each sub-app has its own `createRouter()` and `urls()`. The host router lazily imports the matched app's handler. Patterns are matched in registration order — register more specific patterns (subdomains) before catch-alls.
804
+
805
+ ## Meta Tags
806
+
807
+ Accumulate meta tags across route segments using the built-in `Meta` handle:
808
+
809
+ ```tsx
810
+ import { Meta } from "@rangojs/router";
811
+ import type { HandlerContext } from "@rangojs/router";
812
+
813
+ export function BlogPostPage(ctx: HandlerContext) {
814
+ const meta = ctx.use(Meta);
815
+ meta({ title: "My Blog Post" });
816
+ meta({ name: "description", content: "A great blog post" });
817
+ meta({ property: "og:title", content: "My Blog Post" });
818
+
819
+ return <article>...</article>;
820
+ }
821
+ ```
822
+
823
+ Render collected tags in the document with `<MetaTags />` from `@rangojs/router/client`.
824
+
825
+ ## CLI: `rango generate`
826
+
827
+ Route types are generated automatically by the Vite plugin. The CLI is a manual fallback for generating types outside the dev server (e.g. in CI or for IDE support before first `pnpm dev`):
828
+
829
+ ```bash
830
+ npx rango generate src/router.tsx
831
+ npx rango generate src/ # recursive scan
832
+ npx rango generate src/urls.tsx src/api/ # mix files and directories
833
+ ```
834
+
835
+ Auto-detects file type:
836
+
837
+ - Files with `createRouter` → `*.named-routes.gen.ts` with global route map
838
+ - Files with `urls()` → `*.gen.ts` with per-module route names, params, and search types
839
+
840
+ ## Type Safety
841
+
842
+ The Vite plugin automatically generates a `router.named-routes.gen.ts` file that globally registers route names, patterns, and search schemas via `RSCRouter.GeneratedRouteMap`. This powers server-side named-route typing such as `Handler<"name">`, `ctx.reverse()`, `getRequestContext().reverse()`, and `RouteParams<"name">` without any manual route registration. The gen file is updated on dev server startup, HMR, and production builds.
843
+
844
+ Use the generated map by default. Augment `RSCRouter.RegisteredRoutes` only when you need the richer `typeof router.routeMap` shape globally, especially for response-aware and path-based utilities.
845
+
846
+ ```typescript
847
+ // router.tsx
848
+ const router = createRouter<AppBindings>({}).routes(urlpatterns);
849
+
850
+ declare global {
851
+ namespace RSCRouter {
852
+ interface Env extends AppEnv {}
853
+ interface Vars extends AppVars {}
854
+ interface RegisteredRoutes extends typeof router.routeMap {}
855
+ }
856
+ }
857
+ ```
858
+
859
+ Quick rule of thumb:
860
+
861
+ - `GeneratedRouteMap` (auto-generated) — use for server-side named-route typing: `Handler<"name">`, `ctx.reverse()`, `Prerender<"name">`
862
+ - `typeof router.routeMap` — use when you need route entries with response metadata
863
+ - `RegisteredRoutes` (manual augmentation) — use to expose `typeof router.routeMap` globally for `href()`, `PathResponse`, `ValidPaths`, and other path/response-aware utilities
864
+
865
+ For extracted reusable loaders or middleware, prefer global dotted names on
866
+ `ctx.reverse()` by default. If you want type-safe local names for a specific
867
+ module, use `scopedReverse<typeof localPatterns>(ctx.reverse)` or
868
+ `scopedReverse<routes>(ctx.reverse)` with a generated local route type.
869
+
870
+ ## Subpath Exports
871
+
872
+ | Export | Description |
873
+ | ------------------------ | -------------------------------------------------------------------------------------------------------- |
874
+ | `@rangojs/router` | Server/RSC core and shared types: `createRouter`, `urls`, `createLoader`, `Handler`, `Prerender`, `Meta` |
875
+ | `@rangojs/router/client` | Client: `Link`, `Outlet`, `href`, `useNavigation`, `useLoader`, `MetaTags` |
876
+ | `@rangojs/router/cache` | Cache: `CFCacheStore`, `MemorySegmentCacheStore`, `createDocumentCacheMiddleware` |
877
+ | `@rangojs/router/theme` | Theme: `useTheme`, `ThemeProvider`, `ThemeScript` |
878
+ | `@rangojs/router/host` | Host routing: `createHostRouter`, `defineHosts` |
879
+ | `@rangojs/router/vite` | Vite plugin: `rango()` |
880
+ | `@rangojs/router/rsc` | Advanced server pipeline APIs: `createRSCHandler`, request-context access |
881
+ | `@rangojs/router/ssr` | Advanced SSR bridge APIs: `createSSRHandler` |
882
+ | `@rangojs/router/server` | Internal build/runtime utilities for advanced integrations |
883
+ | `@rangojs/router/build` | Build utilities |
884
+
885
+ The root entrypoint is not a generic client/runtime barrel. If you need hooks
886
+ or components, import from `@rangojs/router/client`; if you need cache or host
887
+ APIs, use their dedicated subpaths.
888
+
889
+ ## Examples
890
+
891
+ See the `examples/` directory for full working applications:
14
892
 
15
- This package is in early experimental stages. It is not recommended for production use.
893
+ - [`cloudflare-basic`](../../examples/cloudflare-basic) Cloudflare Workers with caching, loaders, theme, and pre-rendering
894
+ - [`cloudflare-multi-router`](../../examples/cloudflare-multi-router) — Multi-app host routing
16
895
 
17
896
  ## License
18
897