@rangojs/router 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +254 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  4. package/dist/types/browser/partial-update.d.ts +11 -0
  5. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  6. package/dist/types/browser/rsc-router.d.ts +1 -1
  7. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  8. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  9. package/dist/types/cache/cache-scope.d.ts +34 -5
  10. package/dist/types/cache/handle-capture.d.ts +41 -3
  11. package/dist/types/cache/handle-snapshot.d.ts +53 -13
  12. package/dist/types/cache/segment-codec.d.ts +10 -3
  13. package/dist/types/cache/taint.d.ts +1 -1
  14. package/dist/types/cache/types.d.ts +15 -3
  15. package/dist/types/client.d.ts +4 -4
  16. package/dist/types/client.rsc.d.ts +4 -5
  17. package/dist/types/components/DefaultDocument.d.ts +5 -2
  18. package/dist/types/context-var.d.ts +6 -5
  19. package/dist/types/escape-script.d.ts +1 -1
  20. package/dist/types/handles/MetaTags.d.ts +5 -4
  21. package/dist/types/handles/Scripts.d.ts +8 -7
  22. package/dist/types/handles/meta.d.ts +2 -2
  23. package/dist/types/handles/script.d.ts +17 -16
  24. package/dist/types/href-client.d.ts +1 -1
  25. package/dist/types/html.d.ts +12 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/index.rsc.d.ts +3 -2
  28. package/dist/types/loader-store.d.ts +7 -4
  29. package/dist/types/reverse.d.ts +36 -4
  30. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  31. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  32. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  33. package/dist/types/route-types.d.ts +1 -1
  34. package/dist/types/router/debug-manifest.d.ts +3 -2
  35. package/dist/types/router/error-handling.d.ts +20 -0
  36. package/dist/types/router/handler-context.d.ts +6 -0
  37. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  38. package/dist/types/router/middleware-types.d.ts +3 -2
  39. package/dist/types/router/router-context.d.ts +3 -1
  40. package/dist/types/router/router-interfaces.d.ts +24 -2
  41. package/dist/types/router/router-options.d.ts +8 -5
  42. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  43. package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
  44. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  45. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
  46. package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
  47. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  48. package/dist/types/router/segment-wrappers.d.ts +1 -1
  49. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  50. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  51. package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
  52. package/dist/types/rsc/shell-capture.d.ts +5 -4
  53. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  54. package/dist/types/rsc/types.d.ts +10 -0
  55. package/dist/types/server/context.d.ts +33 -3
  56. package/dist/types/server/handle-store.d.ts +26 -2
  57. package/dist/types/server/request-context.d.ts +24 -21
  58. package/dist/types/ssr/index.d.ts +15 -0
  59. package/dist/types/static-handler.d.ts +4 -1
  60. package/dist/types/testing/generated-routes.d.ts +2 -2
  61. package/dist/types/testing/internal/context.d.ts +1 -1
  62. package/dist/types/testing/render-route.d.ts +32 -6
  63. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  64. package/dist/types/testing/vitest.d.ts +35 -1
  65. package/dist/types/theme/ThemeScript.d.ts +3 -3
  66. package/dist/types/types/cache-types.d.ts +14 -10
  67. package/dist/types/types/handler-context.d.ts +14 -0
  68. package/dist/types/types/loader-types.d.ts +8 -6
  69. package/dist/types/urls/response-types.d.ts +3 -12
  70. package/dist/types/use-loader.d.ts +12 -3
  71. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  72. package/dist/types/vite/rango.d.ts +1 -1
  73. package/dist/vite/index.js +1381 -1346
  74. package/package.json +2 -2
  75. package/skills/api-client/SKILL.md +22 -10
  76. package/skills/breadcrumbs/SKILL.md +65 -22
  77. package/skills/bundle-analysis/SKILL.md +22 -22
  78. package/skills/cache-guide/SKILL.md +207 -97
  79. package/skills/caching/SKILL.md +295 -93
  80. package/skills/catalog.json +16 -16
  81. package/skills/client-urls/SKILL.md +53 -31
  82. package/skills/cloudflare/SKILL.md +72 -20
  83. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  84. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  85. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  86. package/skills/comparison/SKILL.md +5 -0
  87. package/skills/comparison/references/framework-comparison.md +39 -33
  88. package/skills/composability/SKILL.md +38 -29
  89. package/skills/css/SKILL.md +10 -13
  90. package/skills/debug-manifest/SKILL.md +74 -31
  91. package/skills/defer-hydration/SKILL.md +2 -1
  92. package/skills/deployment-caching/SKILL.md +6 -1
  93. package/skills/document-cache/SKILL.md +92 -28
  94. package/skills/fonts/SKILL.md +16 -8
  95. package/skills/handler-use/SKILL.md +53 -17
  96. package/skills/hooks/SKILL.md +28 -20
  97. package/skills/hooks/data.md +23 -3
  98. package/skills/hooks/handle-and-actions.md +31 -16
  99. package/skills/hooks/navigation.md +39 -10
  100. package/skills/hooks/outlets.md +13 -6
  101. package/skills/hooks/state.md +37 -15
  102. package/skills/hooks/urls.md +7 -5
  103. package/skills/host-router/SKILL.md +64 -12
  104. package/skills/i18n/SKILL.md +60 -34
  105. package/skills/intercept/SKILL.md +161 -26
  106. package/skills/layout/SKILL.md +106 -50
  107. package/skills/links/SKILL.md +51 -19
  108. package/skills/loader/SKILL.md +242 -106
  109. package/skills/middleware/SKILL.md +119 -23
  110. package/skills/migrate-nextjs/SKILL.md +100 -45
  111. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  112. package/skills/migrate-react-router/SKILL.md +49 -38
  113. package/skills/migrate-react-router/component-migration.md +25 -6
  114. package/skills/migrate-react-router/data-and-actions.md +18 -5
  115. package/skills/migrate-react-router/route-mapping.md +12 -6
  116. package/skills/mime-routes/SKILL.md +12 -7
  117. package/skills/observability/SKILL.md +28 -14
  118. package/skills/parallel/SKILL.md +75 -39
  119. package/skills/ppr/SKILL.md +345 -150
  120. package/skills/prerender/SKILL.md +189 -100
  121. package/skills/rango/SKILL.md +65 -34
  122. package/skills/react-compiler/SKILL.md +6 -5
  123. package/skills/response-routes/SKILL.md +133 -25
  124. package/skills/route/SKILL.md +165 -60
  125. package/skills/router-setup/SKILL.md +418 -240
  126. package/skills/scripts/SKILL.md +31 -20
  127. package/skills/server-actions/SKILL.md +220 -59
  128. package/skills/shell-manifest/SKILL.md +12 -7
  129. package/skills/streams-and-websockets/SKILL.md +32 -14
  130. package/skills/tailwind/SKILL.md +37 -33
  131. package/skills/testing/SKILL.md +68 -32
  132. package/skills/testing/cache-prerender.md +55 -29
  133. package/skills/testing/client-components.md +59 -6
  134. package/skills/testing/e2e-parity.md +12 -2
  135. package/skills/testing/flight.md +19 -11
  136. package/skills/testing/handles.md +8 -5
  137. package/skills/testing/loader.md +8 -5
  138. package/skills/testing/middleware.md +8 -3
  139. package/skills/testing/predicates.md +120 -0
  140. package/skills/testing/render-handler.md +70 -22
  141. package/skills/testing/response-routes.md +11 -4
  142. package/skills/testing/reverse-and-types.md +20 -14
  143. package/skills/testing/server-actions.md +12 -3
  144. package/skills/testing/server-tree.md +9 -0
  145. package/skills/testing/setup.md +9 -5
  146. package/skills/theme/SKILL.md +106 -29
  147. package/skills/typesafety/SKILL.md +20 -9
  148. package/skills/typesafety/env-and-bindings.md +126 -47
  149. package/skills/typesafety/generated-files-and-cli.md +26 -14
  150. package/skills/typesafety/params-and-search.md +44 -19
  151. package/skills/typesafety/route-types.md +50 -14
  152. package/skills/use-cache/SKILL.md +200 -82
  153. package/skills/vercel/SKILL.md +21 -11
  154. package/skills/view-transitions/SKILL.md +41 -20
  155. package/src/__augment-tests__/augmented.check.ts +26 -1
  156. package/src/browser/index.ts +2 -2
  157. package/src/browser/navigation-bridge.ts +3 -4
  158. package/src/browser/navigation-transaction.ts +22 -2
  159. package/src/browser/partial-update.ts +35 -25
  160. package/src/browser/react/ScrollRestoration.tsx +6 -6
  161. package/src/browser/rsc-router.tsx +1 -1
  162. package/src/browser/scroll-restoration.ts +53 -6
  163. package/src/cache/cache-exec-scope.ts +37 -4
  164. package/src/cache/cache-key-utils.ts +1 -6
  165. package/src/cache/cache-runtime.ts +230 -61
  166. package/src/cache/cache-scope.ts +76 -12
  167. package/src/cache/document-cache.ts +15 -0
  168. package/src/cache/handle-capture.ts +90 -10
  169. package/src/cache/handle-snapshot.ts +132 -28
  170. package/src/cache/segment-codec.ts +26 -7
  171. package/src/cache/tag-invalidation.ts +4 -3
  172. package/src/cache/taint.ts +1 -1
  173. package/src/cache/types.ts +16 -3
  174. package/src/client.rsc.tsx +10 -9
  175. package/src/client.tsx +5 -4
  176. package/src/components/DefaultDocument.tsx +7 -2
  177. package/src/context-var.ts +6 -5
  178. package/src/escape-script.ts +1 -1
  179. package/src/handles/MetaTags.tsx +13 -11
  180. package/src/handles/Scripts.tsx +11 -10
  181. package/src/handles/meta.ts +2 -2
  182. package/src/handles/script.ts +17 -16
  183. package/src/href-client.ts +1 -1
  184. package/src/html.ts +12 -0
  185. package/src/index.rsc.ts +5 -0
  186. package/src/index.ts +1 -0
  187. package/src/loader-store.ts +7 -4
  188. package/src/prerender/build-shell-capture.ts +8 -11
  189. package/src/reverse.ts +44 -4
  190. package/src/route-definition/dsl-helpers.ts +117 -30
  191. package/src/route-definition/helpers-types.ts +33 -9
  192. package/src/route-definition/resolve-handler-use.ts +121 -6
  193. package/src/route-types.ts +0 -3
  194. package/src/router/debug-manifest.ts +10 -2
  195. package/src/router/error-handling.ts +51 -15
  196. package/src/router/handler-context.ts +34 -25
  197. package/src/router/intercept-resolution.ts +125 -39
  198. package/src/router/loader-resolution.ts +56 -11
  199. package/src/router/match-api.ts +12 -17
  200. package/src/router/match-middleware/background-revalidation.ts +161 -96
  201. package/src/router/match-middleware/cache-lookup.ts +93 -10
  202. package/src/router/match-middleware/cache-store.ts +17 -90
  203. package/src/router/middleware-types.ts +3 -5
  204. package/src/router/middleware.ts +52 -9
  205. package/src/router/prerender-match.ts +53 -19
  206. package/src/router/route-snapshot.ts +11 -1
  207. package/src/router/router-context.ts +2 -0
  208. package/src/router/router-interfaces.ts +25 -2
  209. package/src/router/router-options.ts +8 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -12
  211. package/src/router/segment-resolution/loader-cache.ts +205 -45
  212. package/src/router/segment-resolution/loader-mask.ts +12 -22
  213. package/src/router/segment-resolution/loader-snapshot.ts +6 -6
  214. package/src/router/segment-resolution/mask-nested.ts +7 -7
  215. package/src/router/segment-resolution/revalidation.ts +34 -6
  216. package/src/router/segment-wrappers.ts +4 -0
  217. package/src/rsc/render-pipeline.ts +3 -0
  218. package/src/rsc/response-route-handler.ts +11 -3
  219. package/src/rsc/rsc-rendering.ts +8 -45
  220. package/src/rsc/shell-capture-constants.ts +13 -0
  221. package/src/rsc/shell-capture.ts +111 -90
  222. package/src/rsc/ssr-setup.ts +14 -8
  223. package/src/rsc/types.ts +11 -0
  224. package/src/server/context.ts +88 -11
  225. package/src/server/cookie-store.ts +5 -4
  226. package/src/server/handle-store.ts +232 -7
  227. package/src/server/request-context.ts +35 -30
  228. package/src/ssr/index.tsx +40 -7
  229. package/src/static-handler.ts +4 -1
  230. package/src/testing/flight-runtime.d.ts +21 -0
  231. package/src/testing/generated-routes.ts +2 -2
  232. package/src/testing/internal/context.ts +1 -1
  233. package/src/testing/internal/flight-client-globals.ts +7 -5
  234. package/src/testing/render-route.tsx +85 -24
  235. package/src/testing/run-loader.ts +5 -1
  236. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  237. package/src/testing/vitest.ts +62 -1
  238. package/src/theme/ThemeScript.tsx +3 -3
  239. package/src/types/cache-types.ts +14 -10
  240. package/src/types/handler-context.ts +14 -0
  241. package/src/types/loader-types.ts +8 -6
  242. package/src/urls/response-types.ts +3 -23
  243. package/src/use-loader.tsx +12 -3
  244. package/src/vite/plugins/use-cache-transform.ts +118 -74
  245. package/src/vite/rango.ts +7 -3
@@ -6,7 +6,12 @@ argument-hint: path-to-react-router-app
6
6
 
7
7
  # Migrate from React Router to @rangojs/router
8
8
 
9
- Covers React Router v7 (framework mode and library mode), v6, and Remix v2.
9
+ Covers React Router v7 (framework mode and library mode), v6, and Remix v2:
10
+ choosing a target shape per route group, replacing imports, route mapping,
11
+ loaders and actions, middleware, error/loading states, navigation, metadata,
12
+ resource routes, theming, and Workers deployment. Use it when porting one of
13
+ those apps to Rango. For Next.js apps see `/migrate-nextjs`; for a feature
14
+ comparison rather than a port see `/comparison`.
10
15
 
11
16
  ## Why Rango
12
17
 
@@ -109,19 +114,19 @@ unsupported behavior until runtime, and keep the old packages in the dependency
109
114
  graph. Replace every `react-router` / `react-router-dom` / `@remix-run/*`
110
115
  import at its call site:
111
116
 
112
- | React Router import | Replace with |
113
- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
114
- | `Link`, `NavLink` | `Link` from `@rangojs/router/client` (`NavLink` active state via `usePathname()`, see §6) |
115
- | `Outlet` | `Outlet` from `@rangojs/router/client` |
116
- | `useNavigate` | `useRouter()` from `@rangojs/router/client` (see §6) |
117
- | `useLocation`, `useSearchParams`, `useParams` | `usePathname()`, `useSearchParams()` (same `[params, setParams]` tuple), `useParams()` from `@rangojs/router/client` |
118
- | `useLoaderData` | merge the loader into the handler; `useLoader()` for live client data or `clientUrls()` routes (see §3) |
119
- | `useActionData` | `useActionState` (standard React, see §3) |
120
- | `Form` | `<form action={serverAction}>` with a `"use server"` function (see §3) |
121
- | `useFetcher` | submits → server actions + `useActionState`/`useOptimistic`; reads → `useLoader()` |
122
- | `defer` / `Await` | `loading()` DSL / plain `<Suspense>` (see §5) |
123
- | `json()`, `redirect()` | plain return values; `redirect` from `@rangojs/router` |
124
- | `useRouteError` | the `error` prop of `errorBoundary()` (see §5) |
117
+ | React Router import | Replace with |
118
+ | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
119
+ | `Link`, `NavLink` | `Link` from `@rangojs/router/client` (`NavLink` active state via `usePathname()`, see §6) |
120
+ | `Outlet` | `Outlet` from `@rangojs/router/client` |
121
+ | `useNavigate` | `useRouter()` from `@rangojs/router/client` (see §6) |
122
+ | `useLocation`, `useSearchParams`, `useParams` | `usePathname()`, `useSearchParams()` (same `[params, setParams]` tuple), `useParams()` from `@rangojs/router/client`; `location.state` → typed `useLocationState()` (see `/hooks`) |
123
+ | `useLoaderData` | merge the loader into the handler; `useLoader()` for live client data or `clientUrls()` routes (see §3) |
124
+ | `useActionData` | `useActionState` (standard React, see §3) |
125
+ | `Form` | `<form action={serverAction}>` with a `"use server"` function (see §3) |
126
+ | `useFetcher` | submits → server actions + `useActionState`/`useOptimistic`; reads → `useLoader()` / `useFetchLoader()` (see `/hooks`) |
127
+ | `defer` / `Await` | `loading()` DSL / plain `<Suspense>` (see §5) |
128
+ | `json()`, `redirect()` | plain return values; `redirect` from `@rangojs/router` |
129
+ | `useRouteError` | the `error` prop of `errorBoundary()` (see §5) |
125
130
 
126
131
  If an import has no row here and no obvious Rango equivalent, stop and surface
127
132
  it to the user — do not mock it to keep the build green.
@@ -142,48 +147,54 @@ Each numbered step's full walkthrough lives in a companion file linked below.
142
147
 
143
148
  ## 11. Key Conceptual Differences
144
149
 
145
- | Concept | React Router | Rango |
146
- | ------------------- | ----------------------------------- | -------------------------------------- |
147
- | Rendering | Client-side by default, SSR opt-in | Server components by default (RSC) |
148
- | Data loading | `loader()` + `useLoaderData()` | Direct fetch in server components |
149
- | Form actions | Route-scoped `action()` | Function-scoped `"use server"` |
150
- | Route definition | File-based or `createBrowserRouter` | `urls()` DSL with `path()`, `layout()` |
151
- | Middleware | Not built-in (use loaders) | `router.use()` + DSL `middleware()` |
152
- | Parallel routes | Not built-in | `parallel()` DSL |
153
- | Intercepting routes | Not built-in | `intercept()` DSL |
154
- | Caching | Not built-in | `cache()` DSL, `"use cache"` |
155
- | Type-safe routes | Partial (v7 framework mode) | Full: params, names, href, reverse |
150
+ | Concept | React Router | Rango |
151
+ | ------------------- | --------------------------------------------------------------------------- | -------------------------------------- |
152
+ | Rendering | Client-side by default, SSR opt-in | Server components by default (RSC) |
153
+ | Data loading | `loader()` + `useLoaderData()` | Direct fetch in server components |
154
+ | Form actions | Route-scoped `action()` | Function-scoped `"use server"` |
155
+ | Route definition | File-based or `createBrowserRouter` | `urls()` DSL with `path()`, `layout()` |
156
+ | Middleware | v7: route `middleware` export; v6 / Remix v2: none (guards live in loaders) | `router.use()` + DSL `middleware()` |
157
+ | Parallel routes | Not built-in | `parallel()` DSL |
158
+ | Intercepting routes | Not built-in | `intercept()` DSL |
159
+ | Caching | Not built-in | `cache()` DSL, `"use cache"` |
160
+ | Type-safe routes | Partial (v7 framework mode) | Full: params, names, href, reverse |
156
161
 
157
162
  ## Migration Checklist
158
163
 
159
164
  1. [ ] Set up Vite config with `rango()` plugin
160
- 2. [ ] Create Document component with `<MetaTags />` in head
165
+ 2. [ ] Create Document component with `<Html.Meta />` in head
161
166
  3. [ ] Create `router.tsx` with `createRouter()`
162
167
  4. [ ] Convert route config / file routes to `urls()` DSL
163
168
  5. [ ] Migrate layouts — keep `<Outlet />` (import from `@rangojs/router/client`)
164
- 6. [ ] Merge loaders + components into handler functions (fetch + render in one place)
169
+ 6. [ ] Merge loaders + components into handler functions (fetch + render in one place),
170
+ or port hook-heavy groups with `clientUrls()` (see "Two target shapes")
165
171
  7. [ ] Convert React Router actions to `"use server"` functions
166
172
  8. [ ] Migrate auth guards from loaders to `router.use()`
167
173
  9. [ ] Replace `react-router-dom` Link/navigation with `@rangojs/router/client`
168
174
  10. [ ] Convert error boundaries to `errorBoundary()` DSL
169
- 11. [ ] Update metadata to use `Meta` handle + `<MetaTags />`
175
+ 11. [ ] Update metadata to use `Meta` handle + `<Html.Meta />`
170
176
  12. [ ] Replace custom theme provider with `theme: true` in createRouter (see `/theme`)
171
177
  13. [ ] Run `npx rango generate src/` to generate route types
172
178
  14. [ ] Verify no shims: `grep -rnE "from ['\"](react-router|@remix-run)" src/ app/`
173
179
  returns nothing, no mock modules or aliases exist, and the packages are
174
180
  out of `package.json`
175
181
 
176
- **Cloudflare Workers (if migrating an RR7-on-Workers app):**
182
+ **Cloudflare Workers (if migrating an RR7-on-Workers app):** the linked
183
+ sections are in [`../cloudflare/references/streaming-and-deploy.md`](../cloudflare/references/streaming-and-deploy.md).
177
184
 
178
- 14. [ ] Audit the custom worker entry — the `router.fetch()` response must pass
185
+ 15. [ ] Audit the custom worker entry — the `router.fetch()` response must pass
179
186
  through as a **stream** (`new Response(response.body, response)`); remove
180
187
  any `.text()`/`.arrayBuffer()`/`HTMLRewriter` buffering from the Rango
181
- path (keep it only on legacy/proxy branches). See §10a.
182
- 15. [ ] Switch local dev to `vite dev` / `vite preview` (they stream + load
188
+ path (keep it only on legacy/proxy branches). See
189
+ [Keep streamed responses intact](../cloudflare/references/streaming-and-deploy.md#keep-streamed-responses-intact).
190
+ 16. [ ] Switch local dev to `vite dev` / `vite preview` (they stream + load
183
191
  `.dev.vars` + provide bindings); stop using `wrangler dev` for local
184
- verification — it gzip-buffers and kills streaming. See §10b.
185
- 16. [ ] Move build output paths from RR7's `build/` to vite's `dist/` in
186
- `wrangler.toml` (`assets` → `./dist/client/`) and cleanup scripts. See §10d.
187
- 17. [ ] Fix deploy to use the built config `dist/<env>/wrangler.json`
188
- (`no_bundle: true`), not a root `wrangler deploy` against the source
189
- worker entry (which can't bundle Rango's virtual modules). See §10e.
192
+ verification — its local gzip can buffer and hide streaming. See
193
+ [Use Vite for local RSC work](../cloudflare/references/streaming-and-deploy.md#use-vite-for-local-rsc-work).
194
+ 17. [ ] Move build output paths from RR7's `build/` to vite's `dist/`: client
195
+ assets land in `dist/client/`; update cleanup scripts to match.
196
+ 18. [ ] Deploy with the generated config (`wrangler deploy -c dist/rsc/wrangler.json`,
197
+ or `dist/<name>/wrangler.json` for a custom RSC environment name), not a
198
+ root `wrangler deploy` against the source worker entry (Wrangler cannot
199
+ rebuild Rango's Vite virtual modules). See
200
+ [Deploy the built Worker](../cloudflare/references/streaming-and-deploy.md#deploy-the-built-worker).
@@ -2,7 +2,8 @@
2
2
 
3
3
  ## 4. Middleware / Route Protection
4
4
 
5
- React Router doesn't have built-in middleware. Protection is typically done in loaders:
5
+ React Router v6 and Remix v2 have no middleware, so protection is typically done
6
+ in loaders (v7 route `middleware` exports map to the same Rango shape below):
6
7
 
7
8
  ```typescript
8
9
  // React Router: auth check in loader
@@ -13,9 +14,27 @@ export async function loader({ request }) {
13
14
  }
14
15
 
15
16
  // Rango: router.use() for request-level auth
17
+ import {
18
+ createRouter,
19
+ cookies,
20
+ redirect,
21
+ type Middleware,
22
+ } from "@rangojs/router";
23
+
24
+ const authInit: Middleware = async (ctx, next) => {
25
+ const session = cookies().get("session")?.value;
26
+ if (session) ctx.set("user", await getUserFromSession(session));
27
+ await next();
28
+ };
29
+
30
+ const requireAuth: Middleware = async (ctx, next) => {
31
+ if (!ctx.get("user")) return redirect("/login");
32
+ await next();
33
+ };
34
+
16
35
  const router = createRouter({})
17
36
  .use(authInit) // all routes — resolves session
18
- .use("/dashboard/*", requireAuth) // scoped guard
37
+ .use("/dashboard/*", requireAuth) // /dashboard and everything under it
19
38
  .routes(urlpatterns);
20
39
  ```
21
40
 
@@ -122,7 +141,7 @@ export function meta() {
122
141
  }
123
142
 
124
143
  // Rango: Meta handle in server handlers
125
- import { Meta } from "@rangojs/router";
144
+ import { Meta, type Handler } from "@rangojs/router";
126
145
 
127
146
  const HomePage: Handler<"home"> = (ctx) => {
128
147
  const meta = ctx.use(Meta);
@@ -137,16 +156,16 @@ owns the data — `ctx.use(Meta)({ title: data.name })` in the loader body, with
137
156
  `loader(Def, { ssr: false })` when the title must be in the SSR'd
138
157
  head. See `/loader` → "Writing Handles from Loaders".
139
158
 
140
- Add `<MetaTags />` in the Document component's `<head>`:
159
+ Add `<Html.Meta />` in the Document component's `<head>`:
141
160
 
142
161
  ```typescript
143
- import { MetaTags } from "@rangojs/router/client";
162
+ import { Html } from "@rangojs/router/client";
144
163
 
145
164
  function Document({ children }: { children: ReactNode }) {
146
165
  return (
147
166
  <html>
148
167
  <head>
149
- <MetaTags />
168
+ <Html.Meta />
150
169
  </head>
151
170
  <body>{children}</body>
152
171
  </html>
@@ -20,6 +20,8 @@ function ProductPage() {
20
20
  }
21
21
 
22
22
  // Rango: handler fetches and renders directly
23
+ import type { Handler } from "@rangojs/router";
24
+
23
25
  const ProductPage: Handler<"product"> = async (ctx) => {
24
26
  const product = await getProduct(ctx.params.slug);
25
27
  return <div>{product.name}</div>;
@@ -76,6 +78,7 @@ function EditProfile() {
76
78
  }
77
79
 
78
80
  // Rango: "use server" action + native form or useActionState
81
+ // actions/profile.ts
79
82
  "use server";
80
83
  import { redirect } from "@rangojs/router";
81
84
 
@@ -84,7 +87,10 @@ export async function updateProfile(formData: FormData): Promise<void> {
84
87
  throw redirect("/profile");
85
88
  }
86
89
 
87
- // Client component:
90
+ // EditProfile.tsx — a server or client component; passing the action itself
91
+ // (not a wrapper function) keeps the form working with JavaScript disabled.
92
+ import { updateProfile } from "./actions/profile";
93
+
88
94
  function EditProfile() {
89
95
  return (
90
96
  <form action={updateProfile}>
@@ -122,9 +128,12 @@ function ProductPrice() {
122
128
  }
123
129
  ```
124
130
 
125
- `useLoader()` provides live data that stays fresh — it re-fetches on navigation
126
- and after actions (controlled by `revalidate()`). This is different from
127
- `useLoaderData()` which just reads a snapshot.
131
+ `useLoader()` provides live data that stays fresh — the loader re-runs on
132
+ navigation and after actions (controlled by `revalidate()`), and the result also
133
+ exposes `isLoading`, `error`, `load`, and `refetch`. Register the loader on the
134
+ route (`loader(PriceLoader)`) so its data is in the tree the component reads;
135
+ the read suspends until it arrives, so keep a `<Suspense>` or `loading()` above
136
+ it. See `/loader` and `/hooks`.
128
137
 
129
138
  ### useActionData
130
139
 
@@ -155,8 +164,12 @@ function EditForm() {
155
164
  // Rango: useActionState (standard React hook)
156
165
  "use client";
157
166
  import { useActionState } from "react";
158
- import { saveForm } from "../actions"; // "use server" function
167
+ // "use server" function with the useActionState shape:
168
+ // export async function saveForm(prev: State, formData: FormData): Promise<State>
169
+ import { saveForm } from "../actions";
159
170
 
171
+ // Pass the imported action straight to useActionState — not an inline closure —
172
+ // so the form still posts and re-renders with JavaScript disabled.
160
173
  function EditForm() {
161
174
  const [state, action, pending] = useActionState(saveForm, null);
162
175
  return (
@@ -10,9 +10,13 @@ npm remove react-router @react-router/dev @react-router/node @react-router/serve
10
10
  # Library mode:
11
11
  npm remove react-router react-router-dom
12
12
 
13
- npm install @rangojs/router
13
+ npm install @rangojs/router # keep react and react-dom
14
14
  ```
15
15
 
16
+ `rango()` includes `@vitejs/plugin-rsc` and supplies the client and server
17
+ entries. Add `@vitejs/plugin-react` only if you want Fast Refresh or the React
18
+ Compiler.
19
+
16
20
  Replace the `@react-router/dev` Vite plugin with `rango()`:
17
21
 
18
22
  ```typescript
@@ -34,7 +38,7 @@ import { createRouter } from "@rangojs/router";
34
38
  import { Document } from "./document";
35
39
  import { urlpatterns } from "./urls";
36
40
 
37
- export default createRouter({
41
+ export const router = createRouter({
38
42
  document: Document,
39
43
  }).routes(urlpatterns);
40
44
  ```
@@ -58,7 +62,7 @@ loader → fetch in handler, or createLoader() — a Rango
58
62
  action → "use server" function
59
63
  meta → ctx.use(Meta) in handler; meta({ data }) →
60
64
  ctx.use(Meta) push in the loader that owns data
61
- headers → ctx.header() in handler or middleware
65
+ headers → ctx.headers.set() in a handler; ctx.header() in middleware
62
66
  shouldRevalidate → revalidate() DSL
63
67
  ErrorBoundary → errorBoundary() DSL
64
68
  HydrateFallback → loading() DSL
@@ -107,15 +111,15 @@ export function ErrorBoundary() {
107
111
 
108
112
  ```typescript
109
113
  // Rango: urls.tsx + handler
110
- import { notFound } from "@rangojs/router";
114
+ import { Meta, notFound, type Handler } from "@rangojs/router";
111
115
 
112
116
  const ProductPage: Handler<"product"> = async (ctx) => {
113
117
  const product = await getProduct(ctx.params.slug);
114
- if (!product) notFound("Product not found");
118
+ if (!product) notFound("Product not found"); // throws: nearest notFoundBoundary, else the router's notFound
115
119
 
116
120
  const meta = ctx.use(Meta);
117
121
  meta({ title: product.name });
118
- ctx.header("Cache-Control", "max-age=300");
122
+ ctx.headers.set("Cache-Control", "max-age=300"); // response headers
119
123
 
120
124
  return <div>{product.name}</div>;
121
125
  };
@@ -130,6 +134,8 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
130
134
 
131
135
  Key shift: the route module's scattered exports consolidate into the handler
132
136
  (data fetching, meta, headers) and the DSL (revalidation, error boundary, loading).
137
+ The `action` export becomes a `"use server"` function (see
138
+ [`./data-and-actions.md`](./data-and-actions.md)).
133
139
 
134
140
  The loader-shaped variant is equally valid — and closer to the RR module when
135
141
  the loader carried authority. A `createLoader()` body can throw `notFound()`
@@ -15,12 +15,14 @@ See also: `/response-routes` for the base response route API (path.json, path.te
15
15
  ## Defining Negotiated Routes
16
16
 
17
17
  Declare the same URL pattern with both an RSC route and one or more response-type routes.
18
- Order within the `urls()` array does not matter — the trie merges them at build time.
18
+ The trie merges them into one negotiated leaf at build time, whatever their order. Order
19
+ only decides the default for `*/*`, a missing `Accept`, or an `Accept` no variant serves:
20
+ the first-defined route wins (rule 4 below).
19
21
 
20
22
  ```typescript
21
23
  import { urls } from "@rangojs/router";
22
24
 
23
- export const urlpatterns = urls(({ path, layout, include }) => [
25
+ export const urlpatterns = urls(({ path }) => [
24
26
  // RSC page + JSON API on the same URL
25
27
  path("/products/:id", ProductPage, { name: "product" }),
26
28
  path.json(
@@ -38,10 +40,10 @@ When an API client requests the same URL (`Accept: application/json`), the JSON
38
40
 
39
41
  ## Negotiation Rules
40
42
 
41
- 1. **Q-value priority** — higher `q` wins (`Accept: application/json;q=0.9, text/html;q=1.0` serves RSC)
43
+ 1. **Q-value priority** — higher `q` wins (`Accept: application/json;q=0.9, text/html;q=1.0` serves RSC); `q=0` entries are ignored
42
44
  2. **Client order tiebreaker** — when q-values are equal, the type listed first in Accept wins (matches Express/Hono behavior)
43
- 3. **Specific MIME match** — the variant whose MIME type appears in Accept wins
44
- 4. **Wildcard / empty Accept** — `*/*` and missing Accept fall back to route definition order (the first-defined variant wins); when the RSC route wins this way, it serves the HTML document
45
+ 3. **Specific MIME match** — the variant whose MIME type appears in Accept wins; a type wildcard such as `text/*` picks the first candidate of that type
46
+ 4. **Wildcard / empty / unmatched Accept** — `*/*`, a missing Accept, and an Accept that no candidate serves fall back to route definition order (the first-defined variant wins); when the RSC route wins this way, it serves the HTML document
45
47
  5. **All responses** on a negotiated URL get `Vary: Accept` header, including the RSC side
46
48
 
47
49
  RSC participates as a candidate alongside response-type variants under two MIME
@@ -150,9 +152,12 @@ no payloads, so response types resolve to `never`.
150
152
  1. **Build time**: `buildRouteTrie()` calls `mergeLeaves()` when multiple routes share a pattern.
151
153
  RSC routes become the primary trie leaf; response-type routes are stored in the `nv`
152
154
  (negotiate variants) array on the leaf. The `rf` (rsc-first) flag tracks definition order.
153
- 2. **Runtime**: `previewRoute()` reads `negotiateVariants` from the trie match result.
155
+ 2. **Runtime**: `negotiateRoute()` (called during request classification) reads `negotiateVariants` from the trie match result.
154
156
  It parses the `Accept` header (extracting q-values and order), builds a candidate list
155
- (RSC as `text/html` + response-type variants), and calls `pickNegotiateVariant()`.
157
+ (the response-type variants in definition order, plus RSC as `text/html` and `text/x-component` —
158
+ placed first when the RSC route was defined first, otherwise last),
159
+ and calls `pickNegotiateVariant()`. When a variant with different param names wins
160
+ (`/widgets/:id` vs `/widgets/:file`), params are re-keyed to the variant's names.
156
161
  3. **Candidate matching**: walks the client's sorted Accept list (by q desc, then order asc),
157
162
  matching each entry against candidates. Wildcards (`*/*`, `text/*`) fall back to definition order.
158
163
  4. **Vary header**: both the response-route handler wrapper and the RSC handler wrapper
@@ -9,15 +9,17 @@ argument-hint:
9
9
  Use this when you need to understand request latency, cache decisions,
10
10
  revalidation behavior, loader overlap, or production traces.
11
11
 
12
- Rango exposes two complementary observability surfaces:
12
+ Rango exposes three complementary observability surfaces:
13
13
 
14
14
  1. **Performance timeline** (`debugPerformance`) — per-request waterfall for
15
15
  local or targeted debugging. It prints to the console and emits
16
16
  `Server-Timing`.
17
- 2. **Structured telemetry** (`telemetry`) — lifecycle events sent to a pluggable
18
- sink for production monitoring, OpenTelemetry, or custom metrics.
17
+ 2. **Structured telemetry** (`telemetry`) — discrete lifecycle events sent to a
18
+ pluggable sink for production monitoring, OpenTelemetry, or custom metrics.
19
+ 3. **Tracing** (`tracing`) — the same phases as the timeline, emitted as spans
20
+ into OpenTelemetry, Cloudflare Workers, or Vercel traces.
19
21
 
20
- The essentials are below. The exported `TelemetryEvent` union type
22
+ All three are off by default. The essentials are below. The exported `TelemetryEvent` union type
21
23
  (`import type { TelemetryEvent } from "@rangojs/router"`) is the full event
22
24
  contract — every event kind and its fields are typed there.
23
25
 
@@ -35,7 +37,8 @@ const router = createRouter({
35
37
  });
36
38
  ```
37
39
 
38
- Or enable for selected requests from middleware:
40
+ Or enable for selected requests from middleware (route `middleware()` inside
41
+ `urls()`, or `router.use()` for every request):
39
42
 
40
43
  ```typescript
41
44
  middleware(async (ctx, next) => {
@@ -52,11 +55,19 @@ shared-axis waterfall and adds a `Server-Timing` header.
52
55
  Read the timeline as intervals:
53
56
 
54
57
  - `handler:total` is the whole router request.
55
- - `render:total` / `ssr-render-html` show the render pass.
56
- - `loader:*` rows should overlap render work. If a loader starts only after the
57
- render bar, it is serialized latency.
58
- - Cache, route matching, middleware pre/post, RSC serialization, and SSR phases
59
- appear as separate spans, so the slow phase is visible without guessing.
58
+ - `render:total:<routeName>` (or `render:total` for unnamed routes) and
59
+ `ssr:render-html` show the render pass.
60
+ - `loader:<id>` rows should overlap render work. If a loader starts only after
61
+ the render bar, it is serialized latency.
62
+ - Route matching (`match:*`), middleware (`middleware:<name>:pre` / `:post`),
63
+ actions (`action:<id>`), cache, RSC serialization, and SSR setup appear as
64
+ separate rows, so the slow phase is visible without guessing.
65
+
66
+ The console waterfall uses these labels as written. In the `Server-Timing`
67
+ header, colons become hyphens and other non-alphanumeric characters are
68
+ dropped (`ssr:render-html` → `ssr-render-html`, `handler:total` →
69
+ `handler-total`), and nested rows get a `d<depth>-` prefix. The full metric
70
+ list is in the repository's `packages/rangojs-router/docs/telemetry.md`.
60
71
 
61
72
  **Deployed Cloudflare caveat**: on production Workers, timers are frozen
62
73
  during request execution (Spectre mitigation), so `Server-Timing` durations
@@ -106,7 +117,9 @@ const router = createRouter({
106
117
  On **Cloudflare Workers**, use `createCloudflareTracing` for the `tracing` slot
107
118
  instead — it emits the same phases as native Cloudflare custom spans (in the
108
119
  Workers trace waterfall, next to the automatic KV/D1/fetch spans), with no
109
- `@opentelemetry/api` dependency:
120
+ `@opentelemetry/api` dependency. It reads the tracer from the `ctx` your Worker
121
+ passes to `router.fetch(request, { env, ctx })`, and is a pass-through when
122
+ Workers tracing is not enabled for the Worker:
110
123
 
111
124
  ```typescript
112
125
  import { createRouter } from "@rangojs/router";
@@ -226,6 +239,7 @@ Then inspect:
226
239
 
227
240
  ## Zero-overhead defaults
228
241
 
229
- `debugPerformance` is off by default, and `telemetry` emits nothing unless a sink
230
- is configured. Per-request `ctx.debugPerformance()` lets you turn on the
231
- waterfall only for the route, user, or query param you are investigating.
242
+ `debugPerformance` is off by default, `telemetry` emits nothing unless a sink
243
+ is configured, and with `tracing` unset every span call runs the work directly.
244
+ Per-request `ctx.debugPerformance()` lets you turn on the waterfall only for the
245
+ route, user, or query param you are investigating.