@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.204030a9

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 (293) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +242 -55
  3. package/dist/bin/rango.js +277 -99
  4. package/dist/vite/index.js +2929 -1132
  5. package/dist/vite/index.js.bak +5448 -0
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +68 -21
  8. package/skills/breadcrumbs/SKILL.md +252 -0
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +243 -21
  11. package/skills/caching/SKILL.md +159 -10
  12. package/skills/composability/SKILL.md +27 -2
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +262 -51
  16. package/skills/host-router/SKILL.md +243 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +46 -4
  19. package/skills/layout/SKILL.md +28 -7
  20. package/skills/links/SKILL.md +249 -17
  21. package/skills/loader/SKILL.md +291 -31
  22. package/skills/middleware/SKILL.md +49 -12
  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 +27 -0
  26. package/skills/observability/SKILL.md +137 -0
  27. package/skills/parallel/SKILL.md +197 -6
  28. package/skills/prerender/SKILL.md +125 -102
  29. package/skills/rango/SKILL.md +242 -23
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +66 -9
  32. package/skills/route/SKILL.md +91 -8
  33. package/skills/router-setup/SKILL.md +98 -8
  34. package/skills/server-actions/SKILL.md +751 -0
  35. package/skills/streams-and-websockets/SKILL.md +283 -0
  36. package/skills/testing/SKILL.md +511 -188
  37. package/skills/typesafety/SKILL.md +354 -50
  38. package/skills/use-cache/SKILL.md +34 -5
  39. package/skills/view-transitions/SKILL.md +294 -0
  40. package/src/__augment-tests__/augment.ts +81 -0
  41. package/src/__augment-tests__/augmented.check.ts +117 -0
  42. package/src/__internal.ts +92 -0
  43. package/src/browser/action-coordinator.ts +53 -36
  44. package/src/browser/app-shell.ts +52 -0
  45. package/src/browser/app-version.ts +14 -0
  46. package/src/browser/event-controller.ts +91 -70
  47. package/src/browser/history-state.ts +21 -0
  48. package/src/browser/index.ts +3 -3
  49. package/src/browser/link-interceptor.ts +4 -0
  50. package/src/browser/navigation-bridge.ts +183 -18
  51. package/src/browser/navigation-client.ts +187 -57
  52. package/src/browser/navigation-store.ts +75 -17
  53. package/src/browser/navigation-transaction.ts +21 -37
  54. package/src/browser/partial-update.ts +143 -40
  55. package/src/browser/prefetch/cache.ts +275 -28
  56. package/src/browser/prefetch/fetch.ts +191 -46
  57. package/src/browser/prefetch/policy.ts +6 -0
  58. package/src/browser/prefetch/queue.ts +123 -20
  59. package/src/browser/prefetch/resource-ready.ts +77 -0
  60. package/src/browser/rango-state.ts +53 -13
  61. package/src/browser/react/Link.tsx +98 -14
  62. package/src/browser/react/NavigationProvider.tsx +110 -33
  63. package/src/browser/react/context.ts +7 -2
  64. package/src/browser/react/filter-segment-order.ts +51 -7
  65. package/src/browser/react/index.ts +3 -0
  66. package/src/browser/react/location-state-shared.ts +175 -4
  67. package/src/browser/react/location-state.ts +39 -13
  68. package/src/browser/react/use-handle.ts +23 -64
  69. package/src/browser/react/use-navigation.ts +22 -2
  70. package/src/browser/react/use-params.ts +20 -8
  71. package/src/browser/react/use-reverse.ts +106 -0
  72. package/src/browser/react/use-router.ts +43 -10
  73. package/src/browser/react/use-segments.ts +11 -8
  74. package/src/browser/response-adapter.ts +25 -0
  75. package/src/browser/rsc-router.tsx +200 -75
  76. package/src/browser/scroll-restoration.ts +46 -39
  77. package/src/browser/segment-reconciler.ts +36 -9
  78. package/src/browser/segment-structure-assert.ts +2 -2
  79. package/src/browser/server-action-bridge.ts +31 -36
  80. package/src/browser/types.ts +81 -5
  81. package/src/build/collect-fallback-refs.ts +107 -0
  82. package/src/build/generate-manifest.ts +65 -40
  83. package/src/build/generate-route-types.ts +5 -0
  84. package/src/build/index.ts +2 -0
  85. package/src/build/route-trie.ts +69 -26
  86. package/src/build/route-types/codegen.ts +4 -4
  87. package/src/build/route-types/include-resolution.ts +9 -2
  88. package/src/build/route-types/per-module-writer.ts +7 -4
  89. package/src/build/route-types/router-processing.ts +278 -88
  90. package/src/build/route-types/scan-filter.ts +9 -2
  91. package/src/build/route-types/source-scan.ts +118 -0
  92. package/src/build/runtime-discovery.ts +9 -20
  93. package/src/cache/cache-runtime.ts +15 -11
  94. package/src/cache/cache-scope.ts +76 -49
  95. package/src/cache/cf/cf-cache-store.ts +501 -18
  96. package/src/cache/cf/index.ts +5 -1
  97. package/src/cache/document-cache.ts +17 -7
  98. package/src/cache/index.ts +1 -0
  99. package/src/cache/taint.ts +55 -0
  100. package/src/client.rsc.tsx +5 -1
  101. package/src/client.tsx +95 -284
  102. package/src/context-var.ts +72 -2
  103. package/src/debug.ts +2 -2
  104. package/src/decode-loader-results.ts +36 -0
  105. package/src/errors.ts +30 -1
  106. package/src/handle.ts +65 -12
  107. package/src/handles/breadcrumbs.ts +66 -0
  108. package/src/handles/index.ts +1 -0
  109. package/src/host/index.ts +2 -5
  110. package/src/host/router.ts +129 -57
  111. package/src/host/types.ts +31 -2
  112. package/src/host/utils.ts +1 -1
  113. package/src/href-client.ts +140 -20
  114. package/src/index.rsc.ts +15 -40
  115. package/src/index.ts +92 -76
  116. package/src/loader-store.ts +500 -0
  117. package/src/loader.rsc.ts +2 -5
  118. package/src/loader.ts +3 -10
  119. package/src/missing-id-error.ts +68 -0
  120. package/src/outlet-context.ts +1 -1
  121. package/src/prerender/store.ts +57 -15
  122. package/src/prerender.ts +141 -80
  123. package/src/response-utils.ts +37 -0
  124. package/src/reverse.ts +65 -15
  125. package/src/route-content-wrapper.tsx +6 -28
  126. package/src/route-definition/dsl-helpers.ts +435 -260
  127. package/src/route-definition/helper-factories.ts +29 -139
  128. package/src/route-definition/helpers-types.ts +110 -34
  129. package/src/route-definition/index.ts +3 -3
  130. package/src/route-definition/redirect.ts +11 -3
  131. package/src/route-definition/resolve-handler-use.ts +155 -0
  132. package/src/route-definition/use-item-types.ts +32 -0
  133. package/src/route-map-builder.ts +7 -1
  134. package/src/route-types.ts +37 -41
  135. package/src/router/basename.ts +14 -0
  136. package/src/router/content-negotiation.ts +113 -1
  137. package/src/router/error-handling.ts +1 -1
  138. package/src/router/find-match.ts +4 -2
  139. package/src/router/handler-context.ts +105 -39
  140. package/src/router/intercept-resolution.ts +15 -22
  141. package/src/router/lazy-includes.ts +12 -9
  142. package/src/router/loader-resolution.ts +175 -23
  143. package/src/router/logging.ts +5 -2
  144. package/src/router/manifest.ts +31 -16
  145. package/src/router/match-api.ts +129 -193
  146. package/src/router/match-handlers.ts +63 -20
  147. package/src/router/match-middleware/background-revalidation.ts +30 -2
  148. package/src/router/match-middleware/cache-lookup.ts +136 -106
  149. package/src/router/match-middleware/cache-store.ts +54 -10
  150. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  151. package/src/router/match-middleware/segment-resolution.ts +61 -5
  152. package/src/router/match-result.ts +124 -18
  153. package/src/router/metrics.ts +239 -14
  154. package/src/router/middleware-types.ts +61 -31
  155. package/src/router/middleware.ts +226 -124
  156. package/src/router/navigation-snapshot.ts +182 -0
  157. package/src/router/pattern-matching.ts +118 -19
  158. package/src/router/prerender-match.ts +114 -10
  159. package/src/router/preview-match.ts +32 -102
  160. package/src/router/request-classification.ts +286 -0
  161. package/src/router/revalidation.ts +85 -9
  162. package/src/router/route-snapshot.ts +245 -0
  163. package/src/router/router-context.ts +6 -1
  164. package/src/router/router-interfaces.ts +91 -29
  165. package/src/router/router-options.ts +89 -19
  166. package/src/router/router-registry.ts +2 -5
  167. package/src/router/segment-resolution/fresh.ts +240 -23
  168. package/src/router/segment-resolution/helpers.ts +30 -25
  169. package/src/router/segment-resolution/loader-cache.ts +1 -0
  170. package/src/router/segment-resolution/revalidation.ts +483 -289
  171. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  172. package/src/router/segment-wrappers.ts +2 -0
  173. package/src/router/substitute-pattern-params.ts +56 -0
  174. package/src/router/telemetry.ts +99 -0
  175. package/src/router/trie-matching.ts +38 -15
  176. package/src/router/types.ts +9 -0
  177. package/src/router/url-params.ts +49 -0
  178. package/src/router.ts +120 -32
  179. package/src/rsc/handler-context.ts +2 -2
  180. package/src/rsc/handler.ts +524 -370
  181. package/src/rsc/helpers.ts +91 -43
  182. package/src/rsc/index.ts +1 -21
  183. package/src/rsc/loader-fetch.ts +23 -3
  184. package/src/rsc/manifest-init.ts +5 -1
  185. package/src/rsc/origin-guard.ts +28 -10
  186. package/src/rsc/progressive-enhancement.ts +39 -10
  187. package/src/rsc/response-route-handler.ts +46 -53
  188. package/src/rsc/rsc-rendering.ts +69 -89
  189. package/src/rsc/runtime-warnings.ts +9 -10
  190. package/src/rsc/server-action.ts +39 -47
  191. package/src/rsc/ssr-setup.ts +144 -0
  192. package/src/rsc/types.ts +19 -3
  193. package/src/search-params.ts +20 -17
  194. package/src/segment-content-promise.ts +67 -0
  195. package/src/segment-loader-promise.ts +122 -0
  196. package/src/segment-system.tsx +219 -67
  197. package/src/serialize.ts +243 -0
  198. package/src/server/context.ts +285 -63
  199. package/src/server/cookie-store.ts +28 -4
  200. package/src/server/handle-store.ts +19 -0
  201. package/src/server/loader-registry.ts +9 -8
  202. package/src/server/request-context.ts +228 -65
  203. package/src/server.ts +6 -0
  204. package/src/ssr/index.tsx +9 -1
  205. package/src/static-handler.ts +19 -7
  206. package/src/testing/cache-status.ts +166 -0
  207. package/src/testing/collect-handle.ts +63 -0
  208. package/src/testing/dispatch.ts +440 -0
  209. package/src/testing/dom.entry.ts +22 -0
  210. package/src/testing/e2e/fixture.ts +154 -0
  211. package/src/testing/e2e/index.ts +149 -0
  212. package/src/testing/e2e/matchers.ts +51 -0
  213. package/src/testing/e2e/page-helpers.ts +272 -0
  214. package/src/testing/e2e/parity.ts +306 -0
  215. package/src/testing/e2e/server.ts +183 -0
  216. package/src/testing/flight-matchers.ts +104 -0
  217. package/src/testing/flight-runtime.d.ts +21 -0
  218. package/src/testing/flight.entry.ts +22 -0
  219. package/src/testing/flight.ts +182 -0
  220. package/src/testing/generated-routes.ts +223 -0
  221. package/src/testing/index.ts +98 -0
  222. package/src/testing/internal/context.ts +151 -0
  223. package/src/testing/render-route.tsx +536 -0
  224. package/src/testing/run-loader.ts +296 -0
  225. package/src/testing/run-middleware.ts +170 -0
  226. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  227. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  228. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  229. package/src/testing/vitest-stubs/version.ts +5 -0
  230. package/src/testing/vitest.ts +112 -0
  231. package/src/theme/index.ts +4 -13
  232. package/src/types/cache-types.ts +4 -4
  233. package/src/types/global-namespace.ts +39 -26
  234. package/src/types/handler-context.ts +197 -79
  235. package/src/types/index.ts +1 -0
  236. package/src/types/loader-types.ts +41 -15
  237. package/src/types/request-scope.ts +126 -0
  238. package/src/types/route-config.ts +17 -8
  239. package/src/types/route-entry.ts +19 -1
  240. package/src/types/segments.ts +37 -6
  241. package/src/urls/include-helper.ts +34 -67
  242. package/src/urls/index.ts +0 -3
  243. package/src/urls/path-helper-types.ts +50 -9
  244. package/src/urls/path-helper.ts +63 -63
  245. package/src/urls/pattern-types.ts +48 -19
  246. package/src/urls/response-types.ts +25 -22
  247. package/src/urls/type-extraction.ts +26 -116
  248. package/src/urls/urls-function.ts +1 -5
  249. package/src/use-loader.tsx +487 -44
  250. package/src/vite/debug.ts +185 -0
  251. package/src/vite/discovery/bundle-postprocess.ts +63 -91
  252. package/src/vite/discovery/discover-routers.ts +106 -53
  253. package/src/vite/discovery/discovery-errors.ts +194 -0
  254. package/src/vite/discovery/gate-state.ts +171 -0
  255. package/src/vite/discovery/prerender-collection.ts +222 -107
  256. package/src/vite/discovery/route-types-writer.ts +40 -84
  257. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  258. package/src/vite/discovery/state.ts +50 -13
  259. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  260. package/src/vite/index.ts +10 -3
  261. package/src/vite/plugin-types.ts +111 -72
  262. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  263. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  265. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  266. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  267. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  268. package/src/vite/plugins/expose-action-id.ts +55 -33
  269. package/src/vite/plugins/expose-id-utils.ts +24 -8
  270. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  271. package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
  272. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  273. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  274. package/src/vite/plugins/expose-internal-ids.ts +544 -317
  275. package/src/vite/plugins/performance-tracks.ts +92 -0
  276. package/src/vite/plugins/refresh-cmd.ts +127 -0
  277. package/src/vite/plugins/use-cache-transform.ts +65 -50
  278. package/src/vite/plugins/version-injector.ts +39 -23
  279. package/src/vite/plugins/version-plugin.ts +72 -3
  280. package/src/vite/plugins/virtual-entries.ts +2 -2
  281. package/src/vite/rango.ts +265 -226
  282. package/src/vite/router-discovery.ts +924 -137
  283. package/src/vite/utils/ast-handler-extract.ts +15 -15
  284. package/src/vite/utils/banner.ts +4 -4
  285. package/src/vite/utils/bundle-analysis.ts +4 -2
  286. package/src/vite/utils/client-chunks.ts +190 -0
  287. package/src/vite/utils/forward-user-plugins.ts +193 -0
  288. package/src/vite/utils/manifest-utils.ts +21 -5
  289. package/src/vite/utils/package-resolution.ts +41 -1
  290. package/src/vite/utils/prerender-utils.ts +98 -5
  291. package/src/vite/utils/shared-utils.ts +109 -27
  292. package/src/browser/action-response-classifier.ts +0 -99
  293. package/src/route-definition/route-function.ts +0 -119
package/AGENTS.md CHANGED
@@ -3,3 +3,7 @@
3
3
  A file-system based React Server Components router.
4
4
 
5
5
  Run `/rango` to understand the API. Detailed guides for each feature are in the `skills/` directory (e.g. `node_modules/@rangojs/router/skills/loader`, `skills/caching`, `skills/middleware`, etc.).
6
+
7
+ ## Development rules
8
+
9
+ - Always commit generated files (e.g. `*.gen.ts`) alongside the source changes that produced them.
package/README.md CHANGED
@@ -10,6 +10,7 @@ Named-route RSC router with structural composability and type-safe partial rende
10
10
  - **Structural composability** — Attach routes, loaders, middleware, handles, caching, prerendering, and static generation without hiding the route tree
11
11
  - **Composable URL patterns** — Django-style `urls()` DSL with `path`, `layout`, `include`
12
12
  - **Data loaders** — `createLoader()` with automatic streaming and Suspense integration
13
+ - **Server actions** — `"use server"` mutations with `useActionState`, `useOptimistic`, and per-segment + per-loader `revalidate()` rules
13
14
  - **Live data layer** — Pre-render or cache the UI shell while loaders stay live by default at request time
14
15
  - **Layouts & nesting** — Nested layouts with `<Outlet />` and parallel routes
15
16
  - **Segment-level caching** — `cache()` DSL with TTL/SWR and pluggable cache stores
@@ -45,6 +46,30 @@ For Cloudflare Workers:
45
46
  npm install @cloudflare/vite-plugin
46
47
  ```
47
48
 
49
+ ## Import Paths
50
+
51
+ Use these import paths consistently:
52
+
53
+ - `@rangojs/router` — server/RSC router APIs, route DSL, `createRouter`, `urls`, `redirect`, `Prerender`, `Static`, shared types
54
+ - `@rangojs/router/client` — hooks and components such as `Link`, `Outlet`, `href`, `useNavigation`, `useLoader`, `useAction`, `useLocationState`
55
+ - `@rangojs/router/cache` — public cache APIs such as `CFCacheStore`, `MemorySegmentCacheStore`, `createDocumentCacheMiddleware`
56
+ - `@rangojs/router/host`, `@rangojs/router/theme`, `@rangojs/router/vite` — specialized public subpaths
57
+ - `@rangojs/router/rsc`, `@rangojs/router/ssr` — advanced server-only integration subpaths for custom request/HTML pipelines
58
+
59
+ Use only subpaths that are explicitly exported from the package. Avoid deep imports such as `@rangojs/router/cache/cf`.
60
+
61
+ `@rangojs/router` is conditionally resolved. Server-only root APIs such as
62
+ `createRouter()`, `urls()`, `redirect()`, `Prerender()`, and `cookies()` rely on
63
+ the `react-server` export condition and are meant to run in router definitions,
64
+ handlers, and other RSC/server modules. Outside that environment the root entry
65
+ falls back to stub implementations that throw guidance errors.
66
+
67
+ If you hit a root-entrypoint stub error:
68
+
69
+ - hooks and components like `Link`, `Outlet`, `useLoader`, `useNavigation`, and `MetaTags` belong in `@rangojs/router/client`
70
+ - cache APIs like `CFCacheStore` and `createDocumentCacheMiddleware` belong in `@rangojs/router/cache`
71
+ - host-router APIs belong in `@rangojs/router/host`
72
+
48
73
  ## Quick Start
49
74
 
50
75
  ### Vite Config
@@ -62,26 +87,34 @@ export default defineConfig({
62
87
 
63
88
  ### Router
64
89
 
90
+ This file is a server/RSC module and should import router construction APIs from
91
+ `@rangojs/router`.
92
+
65
93
  ```tsx
66
94
  // src/router.tsx
67
- import { createRouter, urls } from "@rangojs/router";
68
- import { Document } from "./document";
95
+ import { createRouter } from "@rangojs/router";
69
96
 
70
- const blogPatterns = urls(({ path }) => [
71
- path("/", BlogIndexPage, { name: "index" }),
72
- path("/:slug", BlogPostPage, { name: "post" }),
97
+ export const router = createRouter().routes(({ path }) => [
98
+ path("/", HomePage, { name: "home" }),
99
+ path("/about", AboutPage, { name: "about" }),
73
100
  ]);
74
101
 
102
+ export const reverse = router.reverse;
103
+ // reverse("home") -> "/"
104
+ ```
105
+
106
+ For larger apps, extract route modules with `urls()` and compose with `include()`:
107
+
108
+ ```tsx
109
+ import { createRouter, urls } from "@rangojs/router";
110
+ import { blogPatterns } from "./urls/blog";
111
+
75
112
  const urlpatterns = urls(({ path, include }) => [
76
113
  path("/", HomePage, { name: "home" }),
77
114
  include("/blog", blogPatterns, { name: "blog" }),
78
115
  ]);
79
116
 
80
- export const router = createRouter({ document: Document }).routes(urlpatterns);
81
-
82
- // Export typed reverse function for URL generation by route name
83
- export const reverse = router.reverse;
84
-
117
+ export const router = createRouter().routes(urlpatterns);
85
118
  // reverse("blog.post", { slug: "hello-world" }) -> "/blog/hello-world"
86
119
  ```
87
120
 
@@ -129,13 +162,18 @@ const urlpatterns = urls(({ path }) => [
129
162
  ]);
130
163
  ```
131
164
 
132
- Use `reverse()` as the default way to link to routes:
165
+ Use `ctx.reverse()` from handler context as the default way to link to routes from server code:
133
166
 
134
167
  ```tsx
135
- router.reverse("product", { slug: "widget" }); // "/product/widget"
136
- router.reverse("search", undefined, { q: "rsc" }); // "/search?q=rsc"
168
+ const ProductPage: Handler<"product"> = (ctx) => {
169
+ const url = ctx.reverse("product", { slug: "widget" }); // "/product/widget"
170
+ const searchUrl = ctx.reverse("search", undefined, { q: "rsc" }); // "/search?q=rsc"
171
+ return <Link to={url}>Widget</Link>;
172
+ };
137
173
  ```
138
174
 
175
+ `router.reverse()` (exported from the router module) is the same function without a handler context, useful in scripts or tests. In request code, prefer `ctx.reverse()` — it auto-fills mount params from the current match.
176
+
139
177
  ### Composable URL Modules
140
178
 
141
179
  Local route names compose cleanly with `include(..., { name })`:
@@ -248,7 +286,8 @@ All handler typing styles are supported, but they solve different problems:
248
286
  Example of a scoped local name inside a mounted module:
249
287
 
250
288
  ```tsx
251
- import type { Handler, ScopedRouteMap } from "@rangojs/router";
289
+ import type { Handler } from "@rangojs/router";
290
+ import type { ScopedRouteMap } from "@rangojs/router/__internal";
252
291
 
253
292
  type BlogRoutes = ScopedRouteMap<"blog">;
254
293
 
@@ -444,41 +483,130 @@ const urlpatterns = urls(({ path, loader }) => [
444
483
  ]);
445
484
  ```
446
485
 
447
- ## Navigation & Links
486
+ ## Server Actions
448
487
 
449
- ### Named Routes with `reverse()` (Server Components)
488
+ Server actions are React's RSC mutation primitive. Define them with the
489
+ `"use server"` directive — Rango uses standard React 19 hooks
490
+ (`useActionState`, `useFormStatus`, `useOptimistic`) with no framework wrapper.
450
491
 
451
- In server components, use `reverse()` to generate URLs by route name:
492
+ ```tsx
493
+ // app/actions/cart.ts
494
+ "use server";
495
+
496
+ import { getRequestContext } from "@rangojs/router";
497
+
498
+ export async function addToCart(productId: string): Promise<void> {
499
+ const ctx = getRequestContext();
500
+ const userId = ctx.get("user").id;
501
+ await db.cart.insert({ userId, productId });
502
+ }
503
+ ```
452
504
 
453
505
  ```tsx
454
- import { Link } from "@rangojs/router/client";
455
- import { reverse } from "./router";
506
+ // Client form with progressive enhancement + pending state
507
+ "use client";
508
+ import { useActionState } from "react";
509
+ import { saveProfile } from "../actions/profile";
456
510
 
457
- function BlogIndex() {
511
+ export function ProfileForm() {
512
+ const [state, action, pending] = useActionState(saveProfile, null);
458
513
  return (
459
- <nav>
460
- <Link to={reverse("home")}>Home</Link>
461
- <Link to={reverse("blogPost", { slug: "my-post" })}>My Post</Link>
462
- <Link to={reverse("about")}>About</Link>
463
- </nav>
514
+ <form action={action}>
515
+ <input name="name" defaultValue={state?.values?.name} />
516
+ {state?.errors?.name && <p role="alert">{state.errors.name}</p>}
517
+ <button disabled={pending}>{pending ? "Saving…" : "Save"}</button>
518
+ </form>
464
519
  );
465
520
  }
466
521
  ```
467
522
 
468
- `reverse()` is type-safe route names and required params are checked at compile time. Included routes use dotted names: `reverse("api.health")`.
523
+ After an action runs, matched route segments (path/layout/parallel/intercept)
524
+ and loaders can re-render/re-resolve so the UI reflects the new state.
525
+ Attach a `revalidate(({ actionId }) => ...)` rule on any segment or loader
526
+ that owns data the action touched:
469
527
 
470
- Handlers also have `ctx.reverse()` directly on the context:
528
+ ```tsx
529
+ urls(({ path, loader, revalidate }) => [
530
+ // Segment-level: re-render the cart page handler after cart actions.
531
+ // Nest loaders that belong to this route inside the same path() so the
532
+ // segment owns its data dependencies.
533
+ path("/cart", CartPage, { name: "cart" }, () => [
534
+ revalidate(
535
+ ({ actionId }) => actionId?.startsWith("src/actions/cart.ts#") ?? false,
536
+ ),
537
+ loader(CartLoader, () => [
538
+ revalidate(
539
+ ({ actionId }) => actionId?.startsWith("src/actions/cart.ts#") ?? false,
540
+ ),
541
+ ]),
542
+ ]),
543
+ ]);
544
+ ```
545
+
546
+ For the full guide — validation with Zod, error handling, file uploads,
547
+ `useOptimistic`, redirects, and progressive enhancement — see the
548
+ `/server-actions` skill.
549
+
550
+ ## Navigation & Links
551
+
552
+ ### Named Routes with `ctx.reverse()` (Server)
553
+
554
+ In server components and handlers, use `ctx.reverse()` to generate URLs by route name. This is the default — it is typed, auto-fills mount params from the current match, and resolves both local (`.name`) and absolute (`name.sub`) names:
471
555
 
472
556
  ```tsx
557
+ import { Link } from "@rangojs/router/client";
558
+ import type { Handler } from "@rangojs/router";
559
+
473
560
  const BlogPostPage: Handler<"blogPost"> = (ctx) => {
474
561
  const backUrl = ctx.reverse("blog");
475
562
  return <Link to={backUrl}>Back to blog</Link>;
476
563
  };
477
564
  ```
478
565
 
566
+ `reverse()` is type-safe — route names and required params are checked at compile time. Included routes use dotted names: `ctx.reverse("api.health")`.
567
+
568
+ For scripts, tests, or other code without a handler context, import the router-level `reverse`:
569
+
570
+ ```tsx
571
+ import { reverse } from "./router";
572
+ reverse("blogPost", { slug: "my-post" });
573
+ ```
574
+
575
+ ### Client Components
576
+
577
+ **`reverse()` is server-only.** It depends on the route manifest and handler context — neither is available in the browser bundle. Client components receive URLs as props, loader data, or server-action return values:
578
+
579
+ ```tsx
580
+ // server
581
+ function BlogIndex(ctx: HandlerContext) {
582
+ return (
583
+ <Nav
584
+ home={ctx.reverse("home")}
585
+ post={ctx.reverse("blogPost", { slug: "my-post" })}
586
+ />
587
+ );
588
+ }
589
+ ```
590
+
591
+ ```tsx
592
+ "use client";
593
+ import { Link } from "@rangojs/router/client";
594
+
595
+ export function Nav({ home, post }: { home: string; post: string }) {
596
+ return (
597
+ <nav>
598
+ <Link to={home}>Home</Link>
599
+ <Link to={post}>My Post</Link>
600
+ </nav>
601
+ );
602
+ }
603
+ ```
604
+
605
+ For client-side navigation to static paths (no named-route lookup), use `href()` — see below. For URLs tied to named routes, you have two options: import the per-module generated `routes` map and use `useReverse(routes)` for in-module names (see [`/links` skill](./skills/links/SKILL.md)), or generate the URL on the server and pass the string in for cross-module URLs.
606
+
479
607
  ### `href()` for Path Validation (Client Components)
480
608
 
481
- In client components, use `href()` for compile-time path validation:
609
+ In client components, use `href()` for compile-time path validation on static path strings:
482
610
 
483
611
  ```tsx
484
612
  "use client";
@@ -488,7 +616,7 @@ function Nav() {
488
616
  return (
489
617
  <nav>
490
618
  <Link to={href("/")}>Home</Link>
491
- <Link to={href("/blog")} prefetch="hybrid">
619
+ <Link to={href("/blog")} prefetch="adaptive">
492
620
  Blog
493
621
  </Link>
494
622
  <Link to={href("/about")}>About</Link>
@@ -683,10 +811,12 @@ export const BlogPost = Prerender(
683
811
 
684
812
  ### Passthrough for Unknown Params
685
813
 
814
+ Wrap a `Prerender` definition with `Passthrough()` to add a live handler for unknown params at runtime. The build handler runs at build time, the live handler runs at request time for params not in the prerender cache.
815
+
686
816
  ```tsx
687
- import { Prerender } from "@rangojs/router";
817
+ import { Prerender, Passthrough } from "@rangojs/router";
688
818
 
689
- export const ProductPage = Prerender(
819
+ export const ProductPageDef = Prerender(
690
820
  async () => {
691
821
  const featured = await db.getFeaturedProducts();
692
822
  return featured.map((p) => ({ id: p.id }));
@@ -695,16 +825,22 @@ export const ProductPage = Prerender(
695
825
  const product = await db.getProduct(ctx.params.id);
696
826
  return <Product data={product} />;
697
827
  },
698
- { passthrough: true },
699
828
  );
700
- ```
701
829
 
702
- With `passthrough: true`, known params are served from the build-time cache and unknown params fall through to live rendering.
830
+ // In route definition:
831
+ path(
832
+ "/products/:id",
833
+ Passthrough(ProductPageDef, async (ctx) => {
834
+ const product = await ctx.env.DB.getProduct(ctx.params.id);
835
+ return <Product data={product} />;
836
+ }),
837
+ );
838
+ ```
703
839
 
704
- Handlers can also skip individual param sets with `ctx.passthrough()`, deferring them to the live handler at runtime:
840
+ Build handlers can also skip individual param sets with `ctx.passthrough()`, deferring them to the live handler:
705
841
 
706
842
  ```tsx
707
- export const ProductPage = Prerender(
843
+ export const ProductPageDef = Prerender(
708
844
  async () => {
709
845
  const all = await db.getAllProducts();
710
846
  return all.map((p) => ({ id: p.id }));
@@ -714,10 +850,55 @@ export const ProductPage = Prerender(
714
850
  if (!product.published) return ctx.passthrough();
715
851
  return <Product data={product} />;
716
852
  },
717
- { passthrough: true },
718
853
  );
719
854
  ```
720
855
 
856
+ ### Build-Time Environment Bindings
857
+
858
+ Prerender handlers can access platform bindings (KV, D1, R2) at build time when `buildEnv` is configured in the Vite plugin:
859
+
860
+ ```ts
861
+ // vite.config.ts
862
+ import { rango } from "@rangojs/router/vite";
863
+
864
+ rango({ preset: "cloudflare", buildEnv: "auto" });
865
+ ```
866
+
867
+ With `buildEnv: "auto"`, the plugin calls `wrangler.getPlatformProxy()` to provide local bindings. Handlers then access `ctx.env` during build:
868
+
869
+ ```tsx
870
+ export const BlogPosts = Prerender<{ slug: string }>(
871
+ async (ctx) => {
872
+ const rows = await ctx.env.DB.prepare("SELECT slug FROM posts").all();
873
+ return rows.map((r) => ({ slug: r.slug }));
874
+ },
875
+ async (ctx) => {
876
+ const post = await ctx.env.DB.prepare("SELECT * FROM posts WHERE slug = ?")
877
+ .bind(ctx.params.slug)
878
+ .first();
879
+ return <BlogPost post={post} />;
880
+ },
881
+ );
882
+ ```
883
+
884
+ `buildEnv` also accepts a factory function or plain object:
885
+
886
+ ```ts
887
+ // Custom factory
888
+ rango({
889
+ buildEnv: async (ctx) => {
890
+ const { getPlatformProxy } = await import("wrangler");
891
+ const proxy = await getPlatformProxy();
892
+ return { env: proxy.env, dispose: proxy.dispose };
893
+ },
894
+ });
895
+
896
+ // Plain object (Node.js)
897
+ rango({ buildEnv: { DATABASE_URL: process.env.DATABASE_URL } });
898
+ ```
899
+
900
+ Build-time env applies to both production builds and dev on-demand prerender. Without `buildEnv`, accessing `ctx.env` in a Prerender handler throws with a clear error.
901
+
721
902
  ## Theme
722
903
 
723
904
  ### Router Configuration
@@ -762,9 +943,9 @@ import { createHostRouter } from "@rangojs/router/host";
762
943
 
763
944
  const hostRouter = createHostRouter();
764
945
 
765
- hostRouter.host(["*.localhost"]).map(() => import("./apps/admin/handler.js"));
766
- hostRouter.host(["localhost"]).map(() => import("./apps/site/handler.js"));
767
- hostRouter.fallback().map(() => import("./apps/site/handler.js"));
946
+ hostRouter.host(["*.localhost"]).lazy(() => import("./apps/admin/handler.js"));
947
+ hostRouter.host(["localhost"]).lazy(() => import("./apps/site/handler.js"));
948
+ hostRouter.fallback().lazy(() => import("./apps/site/handler.js"));
768
949
 
769
950
  export default {
770
951
  async fetch(request, env, ctx) {
@@ -773,7 +954,7 @@ export default {
773
954
  };
774
955
  ```
775
956
 
776
- 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.
957
+ Use `.lazy(() => import("./sub-app"))` to mount a lazily-imported sub-app (a module whose `default` export is a handler or nested host router), and `.map((request) => Response)` for an inline request handler. Only `.lazy()` mounts are imported during build-time discovery; `.map(() => import(...))` is a type error. Each sub-app has its own `createRouter()` and `urls()`. Patterns are matched in registration order — register more specific patterns (subdomains) before catch-alls.
777
958
 
778
959
  ## Meta Tags
779
960
 
@@ -812,16 +993,16 @@ Auto-detects file type:
812
993
 
813
994
  ## Type Safety
814
995
 
815
- 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.
996
+ The Vite plugin automatically generates a `router.named-routes.gen.ts` file that globally registers route names, patterns, and search schemas via `Rango.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.
816
997
 
817
- 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.
998
+ Use the generated map by default. Augment `Rango.RegisteredRoutes` only when you need the richer `typeof router.routeMap` shape globally, especially for response-aware and path-based utilities.
818
999
 
819
1000
  ```typescript
820
1001
  // router.tsx
821
1002
  const router = createRouter<AppBindings>({}).routes(urlpatterns);
822
1003
 
823
1004
  declare global {
824
- namespace RSCRouter {
1005
+ namespace Rango {
825
1006
  interface Env extends AppEnv {}
826
1007
  interface Vars extends AppVars {}
827
1008
  interface RegisteredRoutes extends typeof router.routeMap {}
@@ -833,7 +1014,7 @@ Quick rule of thumb:
833
1014
 
834
1015
  - `GeneratedRouteMap` (auto-generated) — use for server-side named-route typing: `Handler<"name">`, `ctx.reverse()`, `Prerender<"name">`
835
1016
  - `typeof router.routeMap` — use when you need route entries with response metadata
836
- - `RegisteredRoutes` (manual augmentation) — use to expose `typeof router.routeMap` globally for `href()`, `PathResponse`, `ValidPaths`, and other path/response-aware utilities
1017
+ - `RegisteredRoutes` (manual augmentation) — use to expose `typeof router.routeMap` globally for `href()`, `Rango.Path`, `Rango.PathResponse`, and other path/response-aware utilities
837
1018
 
838
1019
  For extracted reusable loaders or middleware, prefer global dotted names on
839
1020
  `ctx.reverse()` by default. If you want type-safe local names for a specific
@@ -842,16 +1023,22 @@ module, use `scopedReverse<typeof localPatterns>(ctx.reverse)` or
842
1023
 
843
1024
  ## Subpath Exports
844
1025
 
845
- | Export | Description |
846
- | ------------------------ | --------------------------------------------------------------------------------- |
847
- | `@rangojs/router` | Core: `createRouter`, `urls`, `createLoader`, `Handler`, `Prerender`, `Meta` |
848
- | `@rangojs/router/client` | Client: `Link`, `Outlet`, `href`, `useNavigation`, `useLoader`, `MetaTags` |
849
- | `@rangojs/router/cache` | Cache: `CFCacheStore`, `MemorySegmentCacheStore`, `createDocumentCacheMiddleware` |
850
- | `@rangojs/router/theme` | Theme: `useTheme`, `ThemeProvider`, `ThemeScript` |
851
- | `@rangojs/router/host` | Host routing: `createHostRouter`, `defineHosts` |
852
- | `@rangojs/router/vite` | Vite plugin: `rango()` |
853
- | `@rangojs/router/server` | Server utilities |
854
- | `@rangojs/router/build` | Build utilities |
1026
+ | Export | Description |
1027
+ | ------------------------ | -------------------------------------------------------------------------------------------------------- |
1028
+ | `@rangojs/router` | Server/RSC core and shared types: `createRouter`, `urls`, `createLoader`, `Handler`, `Prerender`, `Meta` |
1029
+ | `@rangojs/router/client` | Client: `Link`, `Outlet`, `href`, `useNavigation`, `useLoader`, `MetaTags` |
1030
+ | `@rangojs/router/cache` | Cache: `CFCacheStore`, `MemorySegmentCacheStore`, `createDocumentCacheMiddleware` |
1031
+ | `@rangojs/router/theme` | Theme: `useTheme`, `ThemeProvider`, `ThemeScript` |
1032
+ | `@rangojs/router/host` | Host routing: `createHostRouter`, `defineHosts` |
1033
+ | `@rangojs/router/vite` | Vite plugin: `rango()` |
1034
+ | `@rangojs/router/rsc` | Advanced server pipeline APIs: `createRSCHandler`, request-context access |
1035
+ | `@rangojs/router/ssr` | Advanced SSR bridge APIs: `createSSRHandler` |
1036
+ | `@rangojs/router/server` | Internal build/runtime utilities for advanced integrations |
1037
+ | `@rangojs/router/build` | Build utilities |
1038
+
1039
+ The root entrypoint is not a generic client/runtime barrel. If you need hooks
1040
+ or components, import from `@rangojs/router/client`; if you need cache or host
1041
+ APIs, use their dedicated subpaths.
855
1042
 
856
1043
  ## Examples
857
1044