@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
@@ -9,7 +9,7 @@ argument-hint: [pattern]
9
9
  ## Basic Route
10
10
 
11
11
  ```typescript
12
- import { urls } from "@rangojs/router/server";
12
+ import { urls } from "@rangojs/router";
13
13
 
14
14
  export const urlpatterns = urls(({ path }) => [
15
15
  path("/", HomePage, { name: "home" }),
@@ -74,8 +74,45 @@ path("/product/:slug", async (ctx) => {
74
74
 
75
75
  ```typescript
76
76
  path("/product/:slug", ProductPage, {
77
- name: "product", // Route name for href() and navigation
78
- })
77
+ name: "product", // Route name for href() and navigation
78
+ });
79
+ ```
80
+
81
+ ### Typed Search Params
82
+
83
+ Add a `search` schema to get typed `ctx.search`:
84
+
85
+ ```typescript
86
+ path("/search", SearchPage, {
87
+ name: "search",
88
+ search: { q: "string", page: "number?", sort: "string?" },
89
+ });
90
+ ```
91
+
92
+ Use `Handler<"name">` for typed search params (resolves from the generated route map automatically):
93
+
94
+ ```typescript
95
+ import type { Handler } from "@rangojs/router";
96
+
97
+ export const SearchPage: Handler<"search"> = (ctx) => {
98
+ // ctx.search is typed: { q: string; page?: number; sort?: string }
99
+ const { q, page, sort } = ctx.search;
100
+ // ctx.searchParams is always URLSearchParams
101
+ return <SearchResults q={q} page={page} sort={sort} />;
102
+ };
103
+ ```
104
+
105
+ Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
106
+ Missing params are `undefined` regardless of required/optional. The required/optional
107
+ distinction is a consumer-facing contract (for `href()` and `reverse()` autocomplete).
108
+
109
+ Use `RouteSearchParams<"name">` and `RouteParams<"name">` to extract types for props:
110
+
111
+ ```typescript
112
+ import type { RouteSearchParams, RouteParams } from "@rangojs/router";
113
+
114
+ type SP = RouteSearchParams<"search">; // { q: string; page?: number; sort?: string }
115
+ type P = RouteParams<"blogPost">; // { slug: string }
79
116
  ```
80
117
 
81
118
  ## Route Children
@@ -90,17 +127,193 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
90
127
  ])
91
128
  ```
92
129
 
130
+ ## Handler Data Ownership
131
+
132
+ When a route has children (orphan layouts, parallels), the handler executes
133
+ first. Use `ctx.set(key, value)` to share data with children, who read it
134
+ via `ctx.get(key)`. Caching wraps all segments together, so either all run
135
+ or none do.
136
+
137
+ ### Typed context variables with createVar
138
+
139
+ Use `createVar<T>()` to create a typed token for `ctx.set()`/`ctx.get()`.
140
+ The token is imported by both the handler (producer) and layout (consumer),
141
+ making the data contract explicit and compile-time verified:
142
+
143
+ ```typescript
144
+ import { createVar } from "@rangojs/router";
145
+ import { Outlet, ParallelOutlet } from "@rangojs/router/client";
146
+
147
+ // Typed token -- shared between handler and layout
148
+ interface DashboardData {
149
+ title: string;
150
+ stats: { views: number };
151
+ }
152
+ const Dashboard = createVar<DashboardData>();
153
+
154
+ path("/dashboard/:id", async (ctx) => {
155
+ const data = await fetchDashboard(ctx.params.id);
156
+ ctx.set(Dashboard, data); // type-checked
157
+ return <DashboardPage data={data} />;
158
+ }, { name: "dashboard" }, () => [
159
+ layout((ctx) => {
160
+ const data = ctx.get(Dashboard); // typed as DashboardData | undefined
161
+ return (
162
+ <div>
163
+ <h1>{data?.title}</h1>
164
+ <Outlet />
165
+ <ParallelOutlet name="@sidebar" />
166
+ </div>
167
+ );
168
+ }),
169
+ parallel({
170
+ "@sidebar": (ctx) => {
171
+ const data = ctx.get(Dashboard);
172
+ return <Sidebar stats={data?.stats} />;
173
+ },
174
+ }),
175
+ ])
176
+ ```
177
+
178
+ String keys still work (`ctx.set("key", value)` / `ctx.get("key")`), but
179
+ `createVar<T>()` is preferred for type safety.
180
+
181
+ Only route handlers and middleware can call `ctx.set()`. Layouts, parallels,
182
+ and intercepts can only read via `ctx.get()`.
183
+
184
+ ### Revalidation Contracts for Handler Data
185
+
186
+ Handler-first guarantees apply within a single full render pass. For partial
187
+ action revalidation, define named revalidation contracts and reuse them on both
188
+ the producer route and the consumer child segments.
189
+
190
+ ```typescript
191
+ // revalidation-contracts.ts
192
+ export const revalidateCheckoutData = ({ actionId }) =>
193
+ actionId?.includes("src/actions/checkout.ts#") ?? false;
194
+
195
+ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
196
+ revalidate(revalidateCheckoutData), // producer (route handler) reruns
197
+ layout(CheckoutLayout, () => [
198
+ revalidate(revalidateCheckoutData), // consumer reruns
199
+ parallel({ "@summary": CheckoutSummary }, () => [
200
+ revalidate(revalidateCheckoutData),
201
+ ]),
202
+ ]),
203
+ ]);
204
+ ```
205
+
206
+ If children depend on multiple upstream domains, compose multiple contracts on
207
+ the same segment (`revalidateAuthData`, `revalidateCheckoutData`, and so on).
208
+
209
+ For cleaner route trees, expose contract helpers and spread them:
210
+
211
+ ```typescript
212
+ import { revalidate } from "@rangojs/router";
213
+
214
+ export const revalidateCheckout = () => [revalidate(revalidateCheckoutData)];
215
+
216
+ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
217
+ revalidateCheckout(),
218
+ layout(CheckoutLayout, () => [revalidateCheckout()]),
219
+ ]);
220
+ ```
221
+
222
+ For scope/revalidation guarantees and non-guarantees, see:
223
+ [docs/execution-model.md](../../docs/internal/execution-model.md)
224
+
225
+ ## Redirects
226
+
227
+ ### Basic redirect
228
+
229
+ ```typescript
230
+ import { redirect } from "@rangojs/router";
231
+
232
+ path("/old-page", () => redirect("/new-page"), { name: "oldPage" });
233
+ ```
234
+
235
+ ### Redirect with custom status
236
+
237
+ ```typescript
238
+ path("/moved", () => redirect("/new-location", 301), { name: "moved" });
239
+ ```
240
+
241
+ ### Redirect with location state
242
+
243
+ Carry typed state through redirects (e.g. flash messages):
244
+
245
+ ```typescript
246
+ import { redirect, createLocationState } from "@rangojs/router";
247
+
248
+ export const FlashMessage = createLocationState<{ text: string }>({
249
+ flash: true,
250
+ });
251
+
252
+ path(
253
+ "/save",
254
+ (ctx) => {
255
+ // ... save logic
256
+ return redirect("/dashboard", {
257
+ state: [FlashMessage({ text: "Item saved!" })],
258
+ });
259
+ },
260
+ { name: "save" },
261
+ );
262
+
263
+ // With custom status + state
264
+ path(
265
+ "/action",
266
+ (ctx) => {
267
+ return redirect("/target", {
268
+ status: 303,
269
+ state: [FlashMessage({ text: "Action complete" })],
270
+ });
271
+ },
272
+ { name: "action" },
273
+ );
274
+ ```
275
+
276
+ Read the state on the target page with `useLocationState(FlashMessage)`. The
277
+ `{ flash: true }` option makes it auto-clear. Without `{ flash: true }`,
278
+ state persists on back/forward. See `/hooks` for details.
279
+
280
+ ### ctx.setLocationState()
281
+
282
+ Attach location state to any server response (not just redirects):
283
+
284
+ ```typescript
285
+ path("/dashboard", (ctx) => {
286
+ ctx.setLocationState(ServerInfo({ data: "welcome" }));
287
+ return <Dashboard />;
288
+ }, { name: "dashboard" })
289
+ ```
290
+
291
+ State flows to the browser via the RSC payload and is merged into
292
+ `history.pushState()`. Only works for SPA (partial) navigations.
293
+
93
294
  ## Handler Context
94
295
 
95
296
  Every handler receives a context object:
96
297
 
97
298
  ```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
299
+ interface HandlerContext<TParams = {}, TEnv = DefaultEnv, TSearch = {}> {
300
+ params: TParams; // URL parameters
301
+ request: Request; // Original request
302
+ searchParams: URLSearchParams; // Query params (always URLSearchParams)
303
+ search: {} | ResolveSearchSchema<TSearch>; // Typed search params (from search schema)
304
+ url: URL; // Parsed URL
305
+ env: TEnv; // Environment (bindings + variables)
306
+ set(key: string, value: any): void; // Set context variable (untyped string key)
307
+ set<T>(contextVar: ContextVar<T>, value: T): void; // Set typed context variable
308
+ get(key: string): any; // Read context variable (untyped string key)
309
+ get<T>(contextVar: ContextVar<T>): T | undefined; // Read typed context variable
310
+ use<T>(handle: Handle<T>): T; // Access handles
311
+ reverse(
312
+ name: string,
313
+ params?: Record<string, string>,
314
+ search?: Record<string, unknown>,
315
+ ): string; // URL generation
316
+ setLocationState(entries: LocationStateEntry[]): void; // Attach state to response
104
317
  }
105
318
  ```
106
319
 
@@ -111,11 +324,11 @@ path("/product/:slug", (ctx) => {
111
324
  // Access URL params
112
325
  const { slug } = ctx.params;
113
326
 
114
- // Access query params
115
- const tab = ctx.url.searchParams.get("tab");
327
+ // Access query params (untyped - use search schema for typed access)
328
+ const tab = ctx.searchParams.get("tab");
116
329
 
117
- // Access environment
118
- const db = ctx.env.Bindings.DB;
330
+ // Access platform bindings
331
+ const db = ctx.env.DB;
119
332
 
120
333
  // Access handles
121
334
  const breadcrumbs = ctx.use(Breadcrumbs);
@@ -142,7 +355,7 @@ urls(({ path, layout }) => [
142
355
  ## Complete Example
143
356
 
144
357
  ```typescript
145
- import { urls } from "@rangojs/router/server";
358
+ import { urls } from "@rangojs/router";
146
359
  import { Breadcrumbs } from "./handles/breadcrumbs";
147
360
 
148
361
  export const urlpatterns = urls(({ path, layout, loader, loading }) => [
@@ -1,20 +1,20 @@
1
1
  ---
2
2
  name: router-setup
3
- description: Create and configure the RSC router with createRSCRouter
3
+ description: Create and configure the RSC router with createRouter
4
4
  argument-hint: [option]
5
5
  ---
6
6
 
7
- # Router Setup with createRSCRouter
7
+ # Router Setup with createRouter
8
8
 
9
9
  ## Basic Router Creation
10
10
 
11
11
  ```typescript
12
12
  // src/router.tsx
13
- import { createRSCRouter } from "@rangojs/router/server";
13
+ import { createRouter } from "@rangojs/router";
14
14
  import { Document } from "./document";
15
15
  import { urlpatterns } from "./urls";
16
16
 
17
- const router = createRSCRouter({
17
+ const router = createRouter({
18
18
  document: Document,
19
19
  urls: urlpatterns,
20
20
  });
@@ -26,7 +26,7 @@ export default router;
26
26
 
27
27
  ```typescript
28
28
  // src/urls.tsx
29
- import { urls } from "@rangojs/router/server";
29
+ import { urls } from "@rangojs/router";
30
30
  import { HomePage } from "./pages/home";
31
31
  import { AboutPage } from "./pages/about";
32
32
  import { ProductPage } from "./pages/product";
@@ -50,20 +50,22 @@ export const urlpatterns = urls(({ path, layout, loader, loading }) => [
50
50
  The `urls()` function provides a callback with all available DSL functions:
51
51
 
52
52
  ```typescript
53
- urls(({
54
- path, // Define a route
55
- layout, // Wrap routes in a layout
56
- parallel, // Define parallel routes (slots)
57
- loader, // Add data loader
58
- loading, // Add loading skeleton
59
- cache, // Configure caching
60
- middleware, // Add middleware
61
- revalidate, // Control revalidation
62
- intercept, // Intercept routes for modals
63
- when, // Conditional rendering
64
- }) => [
65
- // Route definitions here
66
- ]);
53
+ urls(
54
+ ({
55
+ path, // Define a route
56
+ layout, // Wrap routes in a layout
57
+ parallel, // Define parallel routes (slots)
58
+ loader, // Add data loader
59
+ loading, // Add loading skeleton
60
+ cache, // Configure caching
61
+ middleware, // Add middleware
62
+ revalidate, // Control revalidation
63
+ intercept, // Intercept routes for modals
64
+ when, // Conditional rendering
65
+ }) => [
66
+ // Route definitions here
67
+ ],
68
+ );
67
69
  ```
68
70
 
69
71
  ## Router Options
@@ -76,7 +78,7 @@ interface RSCRouterOptions<TEnv> {
76
78
  // Document component wrapping entire app
77
79
  document?: ComponentType<{ children: ReactNode }>;
78
80
 
79
- // Enable performance metrics
81
+ // Enable per-request performance timeline (console waterfall + Server-Timing header)
80
82
  debugPerformance?: boolean;
81
83
 
82
84
  // Default error boundary
@@ -93,9 +95,57 @@ interface RSCRouterOptions<TEnv> {
93
95
 
94
96
  // Global cache configuration
95
97
  cache?: CacheConfig<TEnv>;
98
+
99
+ // Theme configuration
100
+ theme?: ThemeConfig | true;
101
+
102
+ // SSR options (streaming policy)
103
+ ssr?: SSROptions<TEnv>;
104
+
105
+ // Telemetry sink for structured lifecycle events
106
+ telemetry?: TelemetrySink;
107
+
108
+ // Connection warmup (default: true)
109
+ warmup?: boolean;
110
+
111
+ // Prefetch cache TTL in seconds (default: 300)
112
+ // Controls in-memory cache duration and Cache-Control max-age for prefetch responses.
113
+ // Set to false to disable prefetch caching.
114
+ prefetchCacheTTL?: number | false;
115
+
116
+ // CSP nonce provider (for router.fetch)
117
+ nonce?: (
118
+ request: Request,
119
+ env: TEnv,
120
+ ) => string | true | Promise<string | true>;
121
+
122
+ // RSC version string (for router.fetch)
123
+ version?: string;
96
124
  }
97
125
  ```
98
126
 
127
+ ## Using the Request Handler
128
+
129
+ The router provides a `fetch` method to handle RSC requests:
130
+
131
+ ```typescript
132
+ // src/router.tsx
133
+ import { createRouter } from "@rangojs/router";
134
+ import { Document } from "./document";
135
+ import { urlpatterns } from "./urls";
136
+
137
+ export const router = createRouter({
138
+ document: Document,
139
+ urls: urlpatterns,
140
+ nonce: () => true, // Auto-generate nonce for CSP
141
+ });
142
+
143
+ // src/worker.tsx (Cloudflare Workers)
144
+ import { router } from "./router";
145
+
146
+ export default { fetch: router.fetch };
147
+ ```
148
+
99
149
  ## Document Component
100
150
 
101
151
  ```typescript
@@ -121,12 +171,50 @@ export function Document({ children }: { children: ReactNode }) {
121
171
  ## Using with Cloudflare Workers
122
172
 
123
173
  ```typescript
174
+ // src/router.tsx
175
+ import { createRouter } from "@rangojs/router";
176
+ import { Document } from "./document";
177
+ import { urlpatterns } from "./urls";
178
+
179
+ export const router = createRouter<AppBindings>({
180
+ document: Document,
181
+ urls: urlpatterns,
182
+ });
183
+
184
+ // src/worker.tsx
185
+ import { router } from "./router";
186
+
187
+ export default {
188
+ async fetch(request: Request, env: Env, ctx: ExecutionContext) {
189
+ return router.fetch(request, { env, ctx });
190
+ },
191
+ };
192
+ ```
193
+
194
+ ### With Dynamic Cache Configuration
195
+
196
+ For per-request cache configuration (e.g., Cloudflare Workers with ExecutionContext):
197
+
198
+ ```typescript
199
+ // src/router.tsx
200
+ import { createRouter } from "@rangojs/router";
201
+ import { CFCacheStore } from "@rangojs/router/cache";
202
+
203
+ export const router = createRouter<AppBindings>({
204
+ document: Document,
205
+ urls: urlpatterns,
206
+ // Cache config receives (env, ctx) separately
207
+ cache: (_env, ctx) => ({
208
+ store: new CFCacheStore({ ctx: ctx!, defaults: { ttl: 60 } }),
209
+ }),
210
+ });
211
+
124
212
  // src/worker.tsx
125
- import router from "./router";
213
+ import { router } from "./router";
126
214
 
127
215
  export default {
128
216
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
129
- return router.fetch(request, env, ctx);
217
+ return router.fetch(request, { env, ctx });
130
218
  },
131
219
  };
132
220
  ```
@@ -135,7 +223,7 @@ export default {
135
223
 
136
224
  ```typescript
137
225
  // src/urls.tsx
138
- import { urls } from "@rangojs/router/server";
226
+ import { urls } from "@rangojs/router";
139
227
  import { Outlet } from "@rangojs/router/client";
140
228
 
141
229
  // Pages
@@ -176,11 +264,11 @@ export const urlpatterns = urls(({ path, layout, parallel, loader, loading, cach
176
264
 
177
265
  ```typescript
178
266
  // src/router.tsx
179
- import { createRSCRouter } from "@rangojs/router/server";
267
+ import { createRouter } from "@rangojs/router";
180
268
  import { Document } from "./document";
181
269
  import { urlpatterns } from "./urls";
182
270
 
183
- const router = createRSCRouter({
271
+ const router = createRouter({
184
272
  document: Document,
185
273
  urls: urlpatterns,
186
274
 
@@ -206,7 +294,7 @@ export default router;
206
294
 
207
295
  ```typescript
208
296
  // src/urls/shop.tsx
209
- import { urls } from "@rangojs/router/server";
297
+ import { urls } from "@rangojs/router";
210
298
 
211
299
  export const shopPatterns = urls(({ path, layout }) => [
212
300
  path("/", ShopIndex, { name: "index" }),
@@ -214,10 +302,10 @@ export const shopPatterns = urls(({ path, layout }) => [
214
302
  ]);
215
303
 
216
304
  // src/urls.tsx
217
- import { urls, include } from "@rangojs/router/server";
305
+ import { urls } from "@rangojs/router";
218
306
  import { shopPatterns } from "./urls/shop";
219
307
 
220
- export const urlpatterns = urls(({ path }) => [
308
+ export const urlpatterns = urls(({ path, include }) => [
221
309
  path("/", HomePage, { name: "home" }),
222
310
  include("/shop", shopPatterns, { name: "shop" }),
223
311
  ]);
@@ -226,21 +314,126 @@ export const urlpatterns = urls(({ path }) => [
226
314
  ## Environment Types
227
315
 
228
316
  ```typescript
229
- import type { RouterEnv } from "@rangojs/router/server";
230
-
317
+ // Bindings passed as TEnv to createRouter<TEnv>()
231
318
  interface AppBindings {
232
319
  DB: D1Database;
233
320
  KV: KVNamespace;
234
321
  }
235
322
 
323
+ // Variables declared via module augmentation
236
324
  interface AppVariables {
237
325
  user?: { id: string; name: string };
238
326
  }
239
327
 
240
- type AppEnv = RouterEnv<AppBindings, AppVariables>;
328
+ const router = createRouter<AppBindings>({
329
+ document: Document,
330
+ urls: urlpatterns,
331
+ });
332
+
333
+ // Register types globally for implicit typing
334
+ declare global {
335
+ namespace RSCRouter {
336
+ interface Env extends AppBindings {}
337
+ interface Vars extends AppVariables {}
338
+ }
339
+ }
340
+ ```
341
+
342
+ ## Connection Warmup
343
+
344
+ Enabled by default. Keeps TCP+TLS connections alive so navigations after idle periods
345
+ don't pay handshake costs.
346
+
347
+ After 60s of no user interaction, the connection is marked cold. When the user returns
348
+ (tab becomes visible or first mouse/touch), a `HEAD ?_rsc_warmup` request re-establishes
349
+ the TLS connection before the next navigation. The server responds with 204 No Content
350
+ before any middleware or routing runs.
351
+
352
+ ```typescript
353
+ // Enabled by default
354
+ const router = createRouter({
355
+ document: Document,
356
+ urls: urlpatterns,
357
+ });
358
+
359
+ // Disable warmup
360
+ const router = createRouter({
361
+ document: Document,
362
+ urls: urlpatterns,
363
+ warmup: false,
364
+ });
365
+ ```
366
+
367
+ The warmup request is relative to the current page path, so it works correctly
368
+ with subpath deployments (reverse proxy, base path).
369
+
370
+ ## Telemetry
371
+
372
+ The router emits structured lifecycle events through a pluggable telemetry sink.
373
+ Zero overhead when not configured.
374
+
375
+ ```typescript
376
+ // Console sink for development
377
+ import { createRouter, createConsoleSink } from "@rangojs/router";
241
378
 
242
- const router = createRSCRouter<AppEnv>({
379
+ const router = createRouter({
243
380
  document: Document,
244
381
  urls: urlpatterns,
382
+ telemetry: createConsoleSink(),
245
383
  });
246
384
  ```
385
+
386
+ ```typescript
387
+ // OpenTelemetry for production
388
+ import { createRouter, createOTelSink } from "@rangojs/router";
389
+ import { trace } from "@opentelemetry/api";
390
+
391
+ const router = createRouter({
392
+ document: Document,
393
+ urls: urlpatterns,
394
+ telemetry: createOTelSink(trace.getTracer("my-app")),
395
+ });
396
+ ```
397
+
398
+ ```typescript
399
+ // Custom sink
400
+ const router = createRouter({
401
+ telemetry: {
402
+ emit(event) {
403
+ // Send to any observability backend
404
+ myTracer.record(event);
405
+ },
406
+ },
407
+ });
408
+ ```
409
+
410
+ Events emitted: `request.start/end/error`, `loader.start/end/error`,
411
+ `handler.error`, `cache.decision`, `revalidation.decision`.
412
+
413
+ ## SSR Streaming Policy
414
+
415
+ Control whether HTML SSR responses stream progressively or wait for all content:
416
+
417
+ ```typescript
418
+ import { createRouter, type SSRStreamMode } from "@rangojs/router";
419
+
420
+ const router = createRouter({
421
+ ssr: {
422
+ resolveStreaming: ({ request }) => {
423
+ const ua = request.headers.get("user-agent") ?? "";
424
+ // Bots that can't process streamed HTML get a fully resolved page
425
+ if (/Googlebot|bingbot/i.test(ua)) return "allReady";
426
+ return "stream";
427
+ },
428
+ },
429
+ });
430
+ ```
431
+
432
+ `SSRStreamMode` is `"stream" | "allReady"`:
433
+
434
+ - `"stream"` (default) — flush HTML as React renders. Suspense fallbacks appear first, then resolved content streams in. Best for real users (fastest TTFB).
435
+ - `"allReady"` — await `stream.allReady` before flushing. The full page arrives in one shot. Use for bots that cannot execute JavaScript or process chunked HTML.
436
+
437
+ The resolver receives `{ request, env, url }` and may be sync or async. It only runs on HTML SSR paths — RSC partials, `__rsc` requests, and response routes are unaffected.
438
+
439
+ When `resolveStreaming` is not configured, the default is `"stream"`.