@rangojs/router 0.16.0 → 0.18.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 (309) hide show
  1. package/README.md +255 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/cookie-name.d.ts +6 -0
  4. package/dist/types/browser/history-state.d.ts +16 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  6. package/dist/types/browser/partial-update.d.ts +11 -0
  7. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  8. package/dist/types/browser/rsc-router.d.ts +1 -1
  9. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  10. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  11. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  12. package/dist/types/cache/cache-policy.d.ts +17 -0
  13. package/dist/types/cache/cache-scope.d.ts +132 -19
  14. package/dist/types/cache/cache-tag.d.ts +92 -7
  15. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  18. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  19. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  20. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  21. package/dist/types/cache/handle-capture.d.ts +41 -3
  22. package/dist/types/cache/handle-snapshot.d.ts +54 -13
  23. package/dist/types/cache/index.d.ts +1 -0
  24. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  25. package/dist/types/cache/segment-codec.d.ts +10 -3
  26. package/dist/types/cache/shell-memo.d.ts +178 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  28. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  29. package/dist/types/cache/taint.d.ts +1 -1
  30. package/dist/types/cache/types.d.ts +220 -44
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  32. package/dist/types/client.d.ts +4 -4
  33. package/dist/types/client.rsc.d.ts +4 -5
  34. package/dist/types/components/DefaultDocument.d.ts +5 -2
  35. package/dist/types/context-var.d.ts +6 -5
  36. package/dist/types/escape-script.d.ts +1 -1
  37. package/dist/types/handles/MetaTags.d.ts +5 -4
  38. package/dist/types/handles/Scripts.d.ts +8 -7
  39. package/dist/types/handles/meta.d.ts +2 -2
  40. package/dist/types/handles/script.d.ts +17 -16
  41. package/dist/types/href-client.d.ts +1 -1
  42. package/dist/types/html.d.ts +12 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +3 -2
  45. package/dist/types/loader-store.d.ts +7 -4
  46. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  47. package/dist/types/reverse.d.ts +36 -4
  48. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  49. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  50. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  51. package/dist/types/route-types.d.ts +1 -1
  52. package/dist/types/router/debug-manifest.d.ts +3 -2
  53. package/dist/types/router/error-handling.d.ts +20 -0
  54. package/dist/types/router/handler-context.d.ts +6 -0
  55. package/dist/types/router/instrument.d.ts +8 -0
  56. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  57. package/dist/types/router/middleware-types.d.ts +9 -3
  58. package/dist/types/router/router-context.d.ts +3 -1
  59. package/dist/types/router/router-interfaces.d.ts +24 -2
  60. package/dist/types/router/router-options.d.ts +8 -5
  61. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  62. package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
  63. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  64. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
  65. package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  67. package/dist/types/router/segment-wrappers.d.ts +1 -1
  68. package/dist/types/rsc/full-payload.d.ts +14 -0
  69. package/dist/types/rsc/handler.d.ts +1 -1
  70. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  71. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  72. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
  74. package/dist/types/rsc/shell-capture.d.ts +151 -19
  75. package/dist/types/rsc/shell-serve.d.ts +140 -17
  76. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  77. package/dist/types/rsc/types.d.ts +17 -0
  78. package/dist/types/server/context.d.ts +139 -15
  79. package/dist/types/server/cookie-store.d.ts +21 -0
  80. package/dist/types/server/handle-store.d.ts +26 -2
  81. package/dist/types/server/request-context.d.ts +182 -53
  82. package/dist/types/ssr/index.d.ts +24 -0
  83. package/dist/types/ssr/ssr-root.d.ts +9 -8
  84. package/dist/types/static-handler.d.ts +4 -1
  85. package/dist/types/testing/flight.entry.d.ts +7 -0
  86. package/dist/types/testing/generated-routes.d.ts +2 -2
  87. package/dist/types/testing/internal/context.d.ts +1 -1
  88. package/dist/types/testing/render-route.d.ts +32 -6
  89. package/dist/types/testing/run-loader.d.ts +18 -3
  90. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  91. package/dist/types/testing/shell-status.d.ts +23 -10
  92. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  93. package/dist/types/testing/vitest.d.ts +35 -1
  94. package/dist/types/theme/ThemeScript.d.ts +3 -3
  95. package/dist/types/types/cache-types.d.ts +14 -10
  96. package/dist/types/types/handler-context.d.ts +28 -1
  97. package/dist/types/types/loader-types.d.ts +8 -6
  98. package/dist/types/urls/pattern-types.d.ts +22 -18
  99. package/dist/types/urls/response-types.d.ts +3 -12
  100. package/dist/types/use-loader.d.ts +12 -3
  101. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  102. package/dist/types/vite/rango.d.ts +1 -1
  103. package/dist/types/vite/router-discovery.d.ts +29 -2
  104. package/dist/vite/index.js +1420 -1348
  105. package/package.json +2 -2
  106. package/skills/api-client/SKILL.md +22 -10
  107. package/skills/breadcrumbs/SKILL.md +75 -22
  108. package/skills/bundle-analysis/SKILL.md +22 -22
  109. package/skills/cache-guide/SKILL.md +306 -124
  110. package/skills/caching/SKILL.md +530 -98
  111. package/skills/catalog.json +16 -16
  112. package/skills/client-urls/SKILL.md +65 -38
  113. package/skills/cloudflare/SKILL.md +87 -20
  114. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  115. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  116. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  117. package/skills/comparison/SKILL.md +5 -0
  118. package/skills/comparison/references/framework-comparison.md +75 -57
  119. package/skills/composability/SKILL.md +38 -29
  120. package/skills/css/SKILL.md +10 -13
  121. package/skills/debug-manifest/SKILL.md +74 -31
  122. package/skills/defer-hydration/SKILL.md +52 -31
  123. package/skills/deployment-caching/SKILL.md +11 -3
  124. package/skills/document-cache/SKILL.md +98 -28
  125. package/skills/fonts/SKILL.md +16 -8
  126. package/skills/handler-use/SKILL.md +55 -17
  127. package/skills/hooks/SKILL.md +28 -20
  128. package/skills/hooks/data.md +23 -3
  129. package/skills/hooks/handle-and-actions.md +34 -17
  130. package/skills/hooks/navigation.md +39 -10
  131. package/skills/hooks/outlets.md +13 -6
  132. package/skills/hooks/state.md +39 -15
  133. package/skills/hooks/urls.md +7 -5
  134. package/skills/host-router/SKILL.md +64 -12
  135. package/skills/i18n/SKILL.md +60 -34
  136. package/skills/intercept/SKILL.md +161 -26
  137. package/skills/layout/SKILL.md +106 -50
  138. package/skills/links/SKILL.md +51 -19
  139. package/skills/loader/SKILL.md +386 -128
  140. package/skills/middleware/SKILL.md +129 -26
  141. package/skills/migrate-nextjs/SKILL.md +156 -70
  142. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  143. package/skills/migrate-react-router/SKILL.md +51 -39
  144. package/skills/migrate-react-router/component-migration.md +28 -7
  145. package/skills/migrate-react-router/data-and-actions.md +21 -6
  146. package/skills/migrate-react-router/route-mapping.md +19 -10
  147. package/skills/mime-routes/SKILL.md +12 -7
  148. package/skills/observability/SKILL.md +92 -14
  149. package/skills/parallel/SKILL.md +88 -47
  150. package/skills/ppr/SKILL.md +866 -379
  151. package/skills/prerender/SKILL.md +210 -108
  152. package/skills/rango/SKILL.md +115 -65
  153. package/skills/react-compiler/SKILL.md +6 -5
  154. package/skills/response-routes/SKILL.md +133 -25
  155. package/skills/route/SKILL.md +170 -60
  156. package/skills/router-setup/SKILL.md +420 -240
  157. package/skills/scripts/SKILL.md +39 -21
  158. package/skills/server-actions/SKILL.md +231 -59
  159. package/skills/shell-manifest/SKILL.md +28 -14
  160. package/skills/streams-and-websockets/SKILL.md +32 -14
  161. package/skills/tailwind/SKILL.md +37 -33
  162. package/skills/testing/SKILL.md +73 -33
  163. package/skills/testing/cache-prerender.md +60 -33
  164. package/skills/testing/client-components.md +59 -6
  165. package/skills/testing/e2e-parity.md +12 -2
  166. package/skills/testing/flight.md +19 -11
  167. package/skills/testing/handles.md +8 -5
  168. package/skills/testing/loader.md +26 -6
  169. package/skills/testing/middleware.md +8 -3
  170. package/skills/testing/predicates.md +120 -0
  171. package/skills/testing/render-handler.md +70 -22
  172. package/skills/testing/response-routes.md +11 -4
  173. package/skills/testing/reverse-and-types.md +20 -14
  174. package/skills/testing/serve-shell-request.md +188 -0
  175. package/skills/testing/server-actions.md +12 -3
  176. package/skills/testing/server-tree.md +9 -0
  177. package/skills/testing/setup.md +9 -5
  178. package/skills/theme/SKILL.md +155 -30
  179. package/skills/typesafety/SKILL.md +20 -9
  180. package/skills/typesafety/env-and-bindings.md +137 -50
  181. package/skills/typesafety/generated-files-and-cli.md +26 -14
  182. package/skills/typesafety/params-and-search.md +44 -19
  183. package/skills/typesafety/route-types.md +50 -14
  184. package/skills/use-cache/SKILL.md +224 -83
  185. package/skills/vercel/SKILL.md +35 -13
  186. package/skills/view-transitions/SKILL.md +42 -21
  187. package/src/__augment-tests__/augmented.check.ts +26 -1
  188. package/src/browser/cookie-name.ts +23 -0
  189. package/src/browser/history-state.ts +23 -0
  190. package/src/browser/index.ts +2 -2
  191. package/src/browser/navigation-bridge.ts +3 -4
  192. package/src/browser/navigation-transaction.ts +22 -2
  193. package/src/browser/partial-update.ts +35 -25
  194. package/src/browser/react/ScrollRestoration.tsx +6 -6
  195. package/src/browser/rsc-router.tsx +6 -1
  196. package/src/browser/scroll-restoration.ts +53 -6
  197. package/src/cache/cache-exec-scope.ts +37 -4
  198. package/src/cache/cache-key-utils.ts +23 -8
  199. package/src/cache/cache-policy.ts +22 -0
  200. package/src/cache/cache-runtime.ts +264 -68
  201. package/src/cache/cache-scope.ts +419 -43
  202. package/src/cache/cache-tag.ts +375 -11
  203. package/src/cache/cf/cf-base64.ts +36 -4
  204. package/src/cache/cf/cf-cache-constants.ts +12 -0
  205. package/src/cache/cf/cf-cache-store.ts +990 -348
  206. package/src/cache/cf/cf-cache-types.ts +32 -4
  207. package/src/cache/cf/cf-shell-frame.ts +258 -0
  208. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  209. package/src/cache/document-cache.ts +15 -0
  210. package/src/cache/handle-capture.ts +90 -10
  211. package/src/cache/handle-snapshot.ts +133 -28
  212. package/src/cache/index.ts +2 -0
  213. package/src/cache/isolate-tag-memo.ts +308 -0
  214. package/src/cache/segment-codec.ts +26 -7
  215. package/src/cache/shell-memo.ts +341 -0
  216. package/src/cache/shell-snapshot.ts +217 -27
  217. package/src/cache/tag-invalidation.ts +108 -22
  218. package/src/cache/taint.ts +1 -1
  219. package/src/cache/types.ts +225 -44
  220. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  221. package/src/client.rsc.tsx +10 -9
  222. package/src/client.tsx +5 -4
  223. package/src/components/DefaultDocument.tsx +7 -2
  224. package/src/context-var.ts +6 -5
  225. package/src/escape-script.ts +1 -1
  226. package/src/handles/MetaTags.tsx +13 -11
  227. package/src/handles/Scripts.tsx +11 -10
  228. package/src/handles/meta.ts +2 -2
  229. package/src/handles/script.ts +17 -16
  230. package/src/href-client.ts +1 -1
  231. package/src/html.ts +12 -0
  232. package/src/index.rsc.ts +5 -0
  233. package/src/index.ts +1 -0
  234. package/src/loader-store.ts +7 -4
  235. package/src/prerender/build-shell-capture.ts +73 -22
  236. package/src/reverse.ts +44 -4
  237. package/src/route-definition/dsl-helpers.ts +121 -33
  238. package/src/route-definition/helpers-types.ts +33 -9
  239. package/src/route-definition/resolve-handler-use.ts +121 -6
  240. package/src/route-types.ts +0 -3
  241. package/src/router/debug-manifest.ts +10 -2
  242. package/src/router/error-handling.ts +51 -15
  243. package/src/router/handler-context.ts +71 -28
  244. package/src/router/instrument.ts +54 -2
  245. package/src/router/intercept-resolution.ts +141 -43
  246. package/src/router/loader-resolution.ts +115 -39
  247. package/src/router/match-api.ts +40 -26
  248. package/src/router/match-handlers.ts +5 -0
  249. package/src/router/match-middleware/background-revalidation.ts +161 -96
  250. package/src/router/match-middleware/cache-lookup.ts +110 -10
  251. package/src/router/match-middleware/cache-store.ts +48 -101
  252. package/src/router/middleware-types.ts +9 -6
  253. package/src/router/middleware.ts +75 -13
  254. package/src/router/prerender-match.ts +59 -19
  255. package/src/router/route-snapshot.ts +11 -1
  256. package/src/router/router-context.ts +2 -0
  257. package/src/router/router-interfaces.ts +25 -2
  258. package/src/router/router-options.ts +8 -5
  259. package/src/router/segment-resolution/fresh.ts +62 -21
  260. package/src/router/segment-resolution/helpers.ts +4 -2
  261. package/src/router/segment-resolution/loader-cache.ts +407 -76
  262. package/src/router/segment-resolution/loader-mask.ts +12 -22
  263. package/src/router/segment-resolution/loader-snapshot.ts +208 -56
  264. package/src/router/segment-resolution/mask-nested.ts +138 -44
  265. package/src/router/segment-resolution/revalidation.ts +61 -16
  266. package/src/router/segment-wrappers.ts +4 -0
  267. package/src/rsc/full-payload.ts +25 -1
  268. package/src/rsc/handler.ts +14 -2
  269. package/src/rsc/progressive-enhancement.ts +3 -2
  270. package/src/rsc/render-pipeline.ts +3 -0
  271. package/src/rsc/response-cache-serve.ts +9 -6
  272. package/src/rsc/response-route-handler.ts +11 -3
  273. package/src/rsc/rsc-rendering.ts +608 -225
  274. package/src/rsc/shell-build-manifest.ts +27 -9
  275. package/src/rsc/shell-capture-constants.ts +73 -6
  276. package/src/rsc/shell-capture.ts +1068 -296
  277. package/src/rsc/shell-serve.ts +316 -34
  278. package/src/rsc/ssr-setup.ts +14 -8
  279. package/src/rsc/types.ts +18 -0
  280. package/src/server/context.ts +300 -32
  281. package/src/server/cookie-store.ts +87 -61
  282. package/src/server/handle-store.ts +232 -7
  283. package/src/server/request-context.ts +297 -67
  284. package/src/ssr/index.tsx +136 -44
  285. package/src/ssr/ssr-root.tsx +9 -8
  286. package/src/static-handler.ts +4 -1
  287. package/src/testing/flight-runtime.d.ts +21 -0
  288. package/src/testing/flight.entry.ts +14 -0
  289. package/src/testing/generated-routes.ts +2 -2
  290. package/src/testing/internal/context.ts +1 -1
  291. package/src/testing/internal/flight-client-globals.ts +7 -5
  292. package/src/testing/render-route.tsx +85 -24
  293. package/src/testing/run-loader.ts +68 -6
  294. package/src/testing/serve-shell-request.ts +378 -0
  295. package/src/testing/shell-status.ts +34 -12
  296. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  297. package/src/testing/vitest.ts +62 -1
  298. package/src/theme/ThemeProvider.tsx +7 -6
  299. package/src/theme/ThemeScript.tsx +3 -3
  300. package/src/types/cache-types.ts +14 -10
  301. package/src/types/handler-context.ts +28 -1
  302. package/src/types/loader-types.ts +8 -6
  303. package/src/urls/pattern-types.ts +22 -18
  304. package/src/urls/response-types.ts +3 -23
  305. package/src/use-loader.tsx +12 -3
  306. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  307. package/src/vite/plugins/use-cache-transform.ts +118 -74
  308. package/src/vite/rango.ts +7 -3
  309. package/src/vite/router-discovery.ts +14 -4
@@ -6,6 +6,13 @@ argument-hint: [path-to-nextjs-app]
6
6
 
7
7
  # Migrate from Next.js App Router to @rangojs/router
8
8
 
9
+ This skill maps a Next.js App Router project onto Rango: project setup, the
10
+ `app/` file conventions to the `urls()` DSL, data fetching and rendering modes
11
+ (SSG, ISR, PPR), middleware, navigation, server actions, metadata, API routes,
12
+ and theming. Use it when porting a Next.js app, or when a Next.js habit needs a
13
+ Rango equivalent. For a side-by-side evaluation rather than a port, see
14
+ `/comparison`; for React Router or Remix apps, see `/migrate-react-router`.
15
+
9
16
  ## Why Rango
10
17
 
11
18
  Common reasons to migrate:
@@ -33,8 +40,10 @@ Common reasons to migrate:
33
40
  build-time rendering instead of mixing rendering and caching behind conventions.
34
41
  See: `/prerender`
35
42
  - **Partial prerendering, shipped** — the `ppr` path option caches a page's
36
- HTML shell and resumes only the live holes on each request; loaders stay
37
- fresh. The equivalent of Next's `experimental_ppr`, stable and per-route.
43
+ HTML shell and resumes its holes per request: loaders without `ssr: false`,
44
+ read under `loading()` or an inline `<Suspense>`. Handler output bakes into
45
+ the shell. It maps to Next's `experimental_ppr`, stable and per-route, but
46
+ holes come from loaders, not from any `<Suspense>`.
38
47
  See: `/ppr`
39
48
  - **Composable route tree** — layouts, includes, middleware, parallels, and
40
49
  intercepts compose directly in the route definition.
@@ -86,6 +95,13 @@ its call site with the real Rango API:
86
95
  | `next/font` | see `/fonts` |
87
96
  | `next/script` `Script` | see `/scripts` |
88
97
  | `next-themes` | `theme: true` in `createRouter` (see §10) |
98
+ | `next/dynamic` `dynamic(() => import(...), { ssr: false })` | render it after mount in a `"use client"` component (see the note below) |
99
+
100
+ `next/dynamic`'s `{ ssr: false }` keeps a component out of server rendering:
101
+ render it only after mount in a `"use client"` component (a `useEffect` flag,
102
+ with `React.lazy()` if its code should split out). Do not map it to
103
+ `loader(L, { ssr: false })`, which does the opposite: the server settles that
104
+ loader before the first flush, and under `ppr` it bakes into the shell.
89
105
 
90
106
  If an import has no row here and no obvious Rango equivalent, stop and surface
91
107
  it to the user — do not mock it to keep the build green.
@@ -99,10 +115,14 @@ Replace Next.js tooling with Vite + Rango:
99
115
 
100
116
  ```bash
101
117
  npm remove next @next/env
102
- npm install @rangojs/router @vitejs/plugin-react
118
+ npm install @rangojs/router # keep react and react-dom
103
119
  npm install -D vite
104
120
  ```
105
121
 
122
+ `rango()` already includes `@vitejs/plugin-rsc` and supplies the client and
123
+ server entries. Add `@vitejs/plugin-react` only if you want Fast Refresh or the
124
+ React Compiler (see `/react-compiler`).
125
+
106
126
  ```typescript
107
127
  // vite.config.ts
108
128
  import { defineConfig } from "vite";
@@ -119,7 +139,7 @@ import { createRouter } from "@rangojs/router";
119
139
  import { Document } from "./document";
120
140
  import { urlpatterns } from "./urls";
121
141
 
122
- export default createRouter({
142
+ export const router = createRouter({
123
143
  document: Document,
124
144
  }).routes(urlpatterns);
125
145
  ```
@@ -223,8 +243,8 @@ The `include()` name has three deliberate modes:
223
243
  | Form | Child route names |
224
244
  | ----------------------------------------------- | ----------------------------------------------------------------------- |
225
245
  | `include("/", patterns)` | private to the included module; omitted from the app-wide generated map |
226
- | `include("/", patterns, { name: "marketing" })` | globally registered as `marketing.home`, `marketing.pricing`, ... |
227
- | `include("/", patterns, { name: "" })` | flattened into the parent map as `home`, `pricing`, ... |
246
+ | `include("/", patterns, { name: "marketing" })` | globally registered as `marketing.landing`, `marketing.pricing`, ... |
247
+ | `include("/", patterns, { name: "" })` | flattened into the parent map as `landing`, `pricing`, ... |
228
248
 
229
249
  Use the empty-string form only when the child names are intentionally global
230
250
  and unique. Inside a private/namespaced module, prefer dot-local reversal or
@@ -300,17 +320,23 @@ run the Phase 0 audit before carrying those calls over unchanged.
300
320
  ```typescript
301
321
  // Next.js:
302
322
  async function ProductPage({ params }) {
303
- const product = await fetch(`/api/products/${params.slug}`).then(r => r.json());
323
+ const product = await fetch(`https://api.example.com/products/${params.slug}`).then(r => r.json());
304
324
  return <div>{product.name}</div>;
305
325
  }
306
326
 
307
327
  // Rango: same pattern, params come from ctx
328
+ import type { Handler } from "@rangojs/router";
329
+
308
330
  const ProductPage: Handler<"product"> = async (ctx) => {
309
- const product = await fetch(`/api/products/${ctx.params.slug}`).then(r => r.json());
331
+ const product = await fetch(`https://api.example.com/products/${ctx.params.slug}`).then(r => r.json());
310
332
  return <div>{product.name}</div>;
311
333
  };
312
334
  ```
313
335
 
336
+ If the Next page fetched its own `/api/...` route, call the underlying service
337
+ function directly instead of making a request back to the same app (see
338
+ [backend-host-swap.md](backend-host-swap.md#extract-shared-server-services)).
339
+
314
340
  ### When to use createLoader
315
341
 
316
342
  Loaders are Rango's live data layer. Use them when you need:
@@ -360,9 +386,11 @@ export const ProductDef = Prerender<{ slug: string }>(
360
386
  );
361
387
 
362
388
  // Rango (with live fallback — matches Next.js dynamicParams behavior):
363
- import { Prerender, Passthrough } from "@rangojs/router";
389
+ import { Prerender, Passthrough, notFound } from "@rangojs/router";
364
390
 
365
- const ProductDef = Prerender<{ slug: string }>(
391
+ // Keep the definition exported: the Vite plugin injects the stable id only
392
+ // into `export const X = Prerender(...)` (or `const X = ...; export { X }`).
393
+ export const ProductDef = Prerender<{ slug: string }>(
366
394
  async () => [{ slug: "a" }, { slug: "b" }],
367
395
  async (ctx) => {
368
396
  const product = await getProduct(ctx.params.slug);
@@ -373,8 +401,12 @@ const ProductDef = Prerender<{ slug: string }>(
373
401
 
374
402
  export const Product = Passthrough(ProductDef, async (ctx) => {
375
403
  const product = await getProduct(ctx.params.slug);
404
+ if (!product) notFound();
376
405
  return <ProductPage product={product} />;
377
406
  });
407
+
408
+ // In urls:
409
+ path("/products/:slug", Product, { name: "product" });
378
410
  ```
379
411
 
380
412
  Use `Passthrough()` whenever the Next.js route has `dynamicParams: true` (the
@@ -395,9 +427,12 @@ Next.js route segment config maps onto Rango's explicit primitives:
395
427
  ### Partial prerendering → the `ppr` path option
396
428
 
397
429
  Next.js PPR statically prerenders a shell at build time and streams the parts
398
- inside `<Suspense>` at request time. Rango ships the same model as a path
430
+ inside `<Suspense>` at request time. Rango ships a similar model as a path
399
431
  option — the shell is captured at runtime into the app cache store and resumed
400
- on later requests, with the holes rendered fresh per request:
432
+ on later requests, with the holes rendered fresh per request. The difference is
433
+ what counts as a hole: in Rango everything a handler produces is shell
434
+ material, as it is under `cache()`, including a promise it hands to a component
435
+ under `<Suspense>`. A live hole is loader data:
401
436
 
402
437
  ```typescript
403
438
  // Next.js: app/products/[id]/page.tsx
@@ -412,11 +447,14 @@ export default async function Page({ params }) {
412
447
  );
413
448
  }
414
449
 
415
- // Rango, step 1 — direct carry-over. Your Suspense tree IS the hole model:
416
- // hand the un-awaited promise down, keep the boundary, add the ppr option.
417
- // No loader, no loading(), no restructuring.
450
+ // Rango: the direct carry-over BAKES. Under ppr the capture awaits a promise
451
+ // the handler hands down (bounded by ppr.captureTimeout), and every shell HIT
452
+ // shows the captured price.
453
+ import { Suspense } from "react";
454
+ import type { HandlerContext } from "@rangojs/router";
455
+
418
456
  function ProductPage(ctx: HandlerContext) {
419
- const price = fetchPrice(ctx.params.id); // pending promise — NOT awaited
457
+ const price = fetchPrice(ctx.params.id); // awaited at capture, then frozen
420
458
  return (
421
459
  <ProductShell>
422
460
  <Suspense fallback={<PriceSkeleton />}>
@@ -425,44 +463,54 @@ function ProductPage(ctx: HandlerContext) {
425
463
  </ProductShell>
426
464
  );
427
465
  }
428
- path("/products/:id", ProductPage, {
429
- name: "product",
430
- ppr: { ttl: 600, swr: 120 }, // or ppr: true (default ttl 300s)
431
- });
432
466
 
433
- // Rango, step 2 (optional refinement) — promote the fetch to a loader for a
434
- // GUARANTEED hole: loaders are masked at capture and fresh on every serve,
435
- // even when the value resolves instantly (a raw promise that settles fast
436
- // would bake into the shell). loading() is the loader's hole boundary.
467
+ // Rango, live price: a loader without ssr: false, read with
468
+ // useLoader(LivePriceLoader) in a client component. loading() (or an inline
469
+ // <Suspense> around the reader) is the hole boundary (/ppr → The loader lane
470
+ // rule).
471
+ function ProductPageLive() {
472
+ return (
473
+ <ProductShell>
474
+ <LivePriceFromLoader /> {/* "use client": useLoader(LivePriceLoader) */}
475
+ </ProductShell>
476
+ );
477
+ }
437
478
  path(
438
479
  "/products/:id",
439
- ProductPage,
440
- { name: "product", ppr: { ttl: 600, swr: 120 } },
480
+ ProductPageLive,
481
+ { name: "product", ppr: { ttl: 600, swr: 120 } }, // or ppr: true (ttl 300s)
441
482
  () => [loader(LivePriceLoader), loading(<PriceSkeleton />)],
442
483
  ),
443
484
  ```
444
485
 
445
486
  Differences that matter during migration:
446
487
 
447
- - **The Suspense/promise model carries over.** As in Next, a still-pending
448
- promise handed to a component that suspends under its own `<Suspense>`
449
- postpones at capture and becomes a hole — existing Next PPR trees keep
450
- working as-is, no `loading()` required. One container rule everywhere
451
- (handlers, handles, loaders): awaited/settled data bakes into the shell; a
452
- promise nested inside your data stays a live hole. For loaders, `loading()`
453
- selects the lane: present = guaranteed live (masked at capture, fresh every
454
- serve, immune to fast resolution — prefer it for per-request data); absent =
455
- the bake lane (the settled container bakes and is snapshot-pinned per shell,
456
- nested promises stay live). Identity reads (`cookies()`/`headers()`) where
457
- the value would bake refuse the capture by construction.
488
+ - **Handler output bakes; loader data is the hole.** Unlike Next, a pending
489
+ promise the handler hands to a component under `<Suspense>` does NOT make a
490
+ hole. Everything a handler produces — that promise, an async server
491
+ component, a nested promise in a handle it pushes, a loader it awaits — is
492
+ awaited at capture (bounded by `ppr.captureTimeout`) and served frozen for
493
+ the shell's lifetime; a shell HIT never runs a handler. Keep the parts that
494
+ must be fresh in loaders without `ssr: false`, read with `useLoader` under
495
+ `loading()` or an inline `<Suspense>`; inside an `ssr: false` loader, return
496
+ them as nested promises. For loaders, `ssr: false` (not `loading()`) selects
497
+ the lane — see `/ppr` → The loader lane rule.
458
498
  - **Shell freshness is explicit.** Next's PPR shell is fixed until the next
459
499
  build; Rango's has `ttl`/`swr`/`tags` per route, and `updateTag()` /
460
500
  `revalidateTag()` drop the shell (`revalidate()` does not — it is a data
461
- lever and never touches shell HTML).
462
- - **`cookies()`/`headers()` in shell material THROW during capture** (in Next
463
- they silently force dynamic rendering). Per-user reads must move behind a
464
- `loading()` boundary (the live loader lane) or into a nested promise — the
465
- refusal surfaces at migration time, which is the point.
501
+ lever and never touches shell HTML). Under a route `cache()`, the shell
502
+ lives no longer than that entry: its ttl/swr are capped by the entry's (dev
503
+ warns when an explicit `ppr` window is reduced). A route `cache({ key })`
504
+ gives one shell per key value, and `cache(false)` or a false `condition()`
505
+ means no shell.
506
+ - **Request-scoped reads in shell material refuse the capture** (in Next they
507
+ silently force dynamic rendering). `cookies()`, `headers()`, a
508
+ `{ cache: false }` variable, and `ctx.dynamic()` refuse it anywhere the
509
+ capture waits: a handler, a promise it passes or pushes, an async component,
510
+ a loader it awaits. Per-user reads must move into a live loader (no
511
+ `ssr: false`; a nested promise does not help). A normal `ctx.get()` value is
512
+ not guarded and bakes as the capturing request's value. The refusal surfaces
513
+ at migration time, which is the point.
466
514
  - **A store is required.** PPR needs the app-level `createRouter({ cache })`
467
515
  store to implement the shell family (`MemorySegmentCacheStore`,
468
516
  `CFCacheStore`, `VercelCacheStore`). Without one the route quietly stays
@@ -483,20 +531,27 @@ tag-based cache invalidation now maps directly.
483
531
  with `cache({ tags })` or runtime `cacheTag(...tags)`. `cacheTag()` works inside a
484
532
  `"use cache"` function (tags that entry) AND render-callable in a plain server
485
533
  component (no `"use cache"` needed — it tags the document / PPR shell the component
486
- renders into). Then invalidate by tag:
534
+ renders into, and the route `cache()` entry it renders inside). Then invalidate by tag:
487
535
 
488
536
  ```typescript
489
537
  // Next.js Rango
490
538
  // revalidateTag("products") → await updateTag("products") // in a server action: awaitable,
491
539
  // // read-your-own-writes (next render is fresh)
492
540
  // or revalidateTag("products") // in a route handler / webhook:
493
- // // background, non-blocking (hard-purge)
541
+ // // background, non-blocking (evicts)
494
542
  ```
495
543
 
496
544
  `updateTag` is awaitable and immediate; `revalidateTag` is fire-and-forget. Both
497
- hard-purge (the next read re-renders fresh); the only difference is awaitability —
498
- despite the Next.js name, `revalidateTag` here is NOT stale-while-revalidate.
499
- Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`) index by tag. Next's
545
+ evict (despite the Next.js name, `revalidateTag` here is NOT
546
+ stale-while-revalidate), and the request that calls either reads its own
547
+ writes. `updateTag()` also waits for the durable write and rejects when it
548
+ fails; `revalidateTag()` runs it in the background. The mutating user's next
549
+ requests skip the stores' per-isolate PPR shell memos via the fresh-reads
550
+ cookie (`rango-state-fresh`); other users, and other locations on
551
+ `CFCacheStore`, see the invalidation once the memo refreshes and KV
552
+ propagates (`/caching` → "The fresh-reads cookie").
553
+ Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`, `VercelCacheStore`)
554
+ index by tag. Next's
500
555
  `revalidatePath` has no path-based equivalent — tag the relevant entries instead.
501
556
 
502
557
  **2. Partial-render selection (which segments re-run after an action).** This is
@@ -507,7 +562,8 @@ re-rendering:
507
562
  ```typescript
508
563
  import { updateBlog } from "./actions/blog";
509
564
 
510
- // Re-run this layout when a blog action fires
565
+ // Re-run this layout when a blog action fires (layouts above the route are
566
+ // skipped after actions by default; undefined defers otherwise)
511
567
  layout(BlogLayout, () => [
512
568
  revalidate((ctx) => ctx.isAction(updateBlog) || undefined),
513
569
  path("/blog/:slug", BlogPost, { name: "blogPost" }),
@@ -617,12 +673,14 @@ path("/dashboard", DashboardPage, { name: "dashboard" }, () => [
617
673
  "use client";
618
674
  export default function Error({ error, reset }) { ... }
619
675
 
620
- // Rango: errorBoundary wrapping a group of routes
676
+ // Rango: errorBoundary wrapping a group of routes. The fallback renders on the
677
+ // server and receives only `error` — there is no `reset` (a server render
678
+ // cannot be retried in place; the user navigates away or reloads).
621
679
  layout(<DashboardLayout />, () => [
622
- errorBoundary(({ error, reset }) => (
680
+ errorBoundary(({ error }) => (
623
681
  <div>
624
682
  <h2>Something went wrong</h2>
625
- <button onClick={reset}>Try again</button>
683
+ <p>{error.message}</p>
626
684
  </div>
627
685
  )),
628
686
  path("/dashboard", DashboardIndex, { name: "dashboard" }),
@@ -630,6 +688,30 @@ layout(<DashboardLayout />, () => [
630
688
  ])
631
689
  ```
632
690
 
691
+ For client-side render errors with a "Try again" button (Next's `reset`), wrap
692
+ the client subtree in `ErrorBoundary` from `@rangojs/router/client`; its
693
+ function fallback receives `{ error, reset }`:
694
+
695
+ ```tsx
696
+ "use client";
697
+ import { ErrorBoundary } from "@rangojs/router/client";
698
+
699
+ export function ChartPanel() {
700
+ return (
701
+ <ErrorBoundary
702
+ fallback={({ error, reset }) => (
703
+ <div>
704
+ <p>{error.message}</p>
705
+ <button onClick={reset}>Try again</button>
706
+ </div>
707
+ )}
708
+ >
709
+ <Chart />
710
+ </ErrorBoundary>
711
+ );
712
+ }
713
+ ```
714
+
633
715
  ```typescript
634
716
  // Next.js: app/not-found.tsx
635
717
  export default function NotFound() { ... }
@@ -657,15 +739,17 @@ children in their scope — handlers, loaders, and nested segments.
657
739
 
658
740
  ## 6. Navigation
659
741
 
660
- | Next.js | Rango |
661
- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
662
- | `import Link from "next/link"` | `import { Link } from "@rangojs/router/client"` |
663
- | `<Link href="/about">` | `<Link to="/about">` |
664
- | `useRouter().push("/about")` | `useRouter().push("/about")` |
665
- | `useRouter().replace("/about")` | `useRouter().replace("/about")` |
666
- | `usePathname()` | `usePathname()` from `@rangojs/router/client` |
667
- | `useSearchParams()` | `useSearchParams()` from `@rangojs/router/client` — returns an RR-style TUPLE, so destructure the reader: `const [searchParams] = useSearchParams()`; the second element is a setter Next does not have |
668
- | `redirect("/login")` (server) | `redirect("/login")` from `@rangojs/router` |
742
+ | Next.js | Rango |
743
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
744
+ | `import Link from "next/link"` | `import { Link } from "@rangojs/router/client"` |
745
+ | `<Link href="/about">` | `<Link to="/about">` |
746
+ | `useRouter().push("/about")` | `useRouter().push("/about")` |
747
+ | `useRouter().replace("/about")` | `useRouter().replace("/about")` |
748
+ | `useRouter().back()` / `refresh()` / `prefetch(url)` | same methods on `useRouter()` from `@rangojs/router/client` |
749
+ | `useParams()` | `useParams()` from `@rangojs/router/client` (or `ctx.params` in a server handler) |
750
+ | `usePathname()` | `usePathname()` from `@rangojs/router/client` |
751
+ | `useSearchParams()` | `useSearchParams()` from `@rangojs/router/client` — returns an RR-style TUPLE, so destructure the reader: `const [searchParams] = useSearchParams()`; the second element is a setter Next does not have |
752
+ | `redirect("/login")` (server) | `redirect("/login")` from `@rangojs/router` |
669
753
 
670
754
  ### "Instant navigations" (Link prefetching)
671
755
 
@@ -699,22 +783,22 @@ has. Server-component routes and `clientUrls()` groups compose in one tree.
699
783
 
700
784
  ## 7. Server Actions
701
785
 
702
- Server actions work the same way — `"use server"` directive, `useActionState`, form actions. No migration needed for action logic.
786
+ Server actions work the same way — `"use server"` directive, `useActionState`, form actions. No migration needed for action logic. Keep passing the imported action itself to `<form action={...}>` or `useActionState(action, initial)` (not a client-side closure around it) so the form still submits with JavaScript disabled.
703
787
 
704
788
  Key difference: in Rango, route middleware does NOT wrap action execution. Actions only see global middleware context. Use `getRequestContext()` in actions to access `ctx.set()`/`ctx.get()`.
705
789
 
706
- Next.js's `revalidateTag()` maps directly: tag entries via `cache({ tags })` / `cacheTag(...)`, then invalidate. **In a server action use `await updateTag(tag)`** — it is read-your-own-writes, so the action's own re-render sees fresh data; `revalidateTag(tag)` is a background (non-blocking) hard-purge and is NOT read-your-own-writes, so reserve it for route handlers / webhooks (calling it from an action can leave that action's re-render stale). `revalidatePath()` has no path-based equivalent — tag the route's entries instead. Separately, to force specific matched segments (path/layout/parallel/intercept) and their loaders to re-render after an action, attach a `revalidate(({ actionId }) => ...)` rule to that segment or loader registration. See `/server-actions` for the full pattern (validation, error handling, file uploads), `/caching` for tag invalidation, and `/loader` for revalidation rule semantics.
790
+ Next.js's `revalidateTag()` maps directly: tag entries via `cache({ tags })` / `cacheTag(...)`, then invalidate. **In a server action use `await updateTag(tag)`** — it is read-your-own-writes, so the action's own re-render sees fresh data, and it rejects when the store's durable write fails; `revalidateTag(tag)` is a background (non-blocking) hard-purge for route handlers / webhooks. The request that calls `revalidateTag()` also reads its own writes, but a failed durable write only reaches `onError`. `revalidatePath()` has no path-based equivalent — tag the route's entries instead. Separately, the route, the segments inside its `path()`, and all loaders already re-run after every action; layouts and parallels above the route are skipped. To re-render one of those skipped segments after an action, attach `revalidate((ctx) => ctx.isAction(updateBlog) || undefined)` to it; to narrow a loader or route segment to one action, use `revalidate((ctx) => (ctx.isAction() ? ctx.isAction(updateBlog) : undefined))` (match the imported action by reference, as in §3). See `/server-actions` for the full pattern (validation, error handling, file uploads), `/caching` for tag invalidation, and `/loader` for revalidation rule semantics.
707
791
 
708
792
  ## 8. Metadata / Head
709
793
 
710
- Rango uses the `Meta` handle + `<MetaTags />` client component:
794
+ Rango uses the `Meta` handle + `<Html.Meta />` client component:
711
795
 
712
796
  ```typescript
713
797
  // Next.js: export const metadata = { title: "Home" }
714
798
  // Next.js: export function generateMetadata({ params }) { ... }
715
799
 
716
- // Rango: Meta handle in handlers (server), MetaTags in document <head> (client)
717
- import { Meta } from "@rangojs/router";
800
+ // Rango: Meta handle in handlers (server), Html.Meta in document <head> (client)
801
+ import { Meta, type Handler } from "@rangojs/router";
718
802
 
719
803
  const HomePage: Handler<"home"> = (ctx) => {
720
804
  const meta = ctx.use(Meta);
@@ -751,16 +835,16 @@ Without the flag the push still applies, but a slow loader's title lands
751
835
  post-hydration instead of in the document — see `/loader` → "Writing Handles
752
836
  from Loaders" for the delivery race.
753
837
 
754
- Add `<MetaTags />` in the Document component's `<head>`:
838
+ Add `<Html.Meta />` in the Document component's `<head>`:
755
839
 
756
840
  ```typescript
757
- import { MetaTags } from "@rangojs/router/client";
841
+ import { Html } from "@rangojs/router/client";
758
842
 
759
843
  function Document({ children }: { children: ReactNode }) {
760
844
  return (
761
845
  <html>
762
846
  <head>
763
- <MetaTags />
847
+ <Html.Meta />
764
848
  </head>
765
849
  <body>{children}</body>
766
850
  </html>
@@ -835,13 +919,15 @@ See `/theme` for full API including system detection and cookie persistence.
835
919
  4. [ ] Create `router.tsx` with `createRouter()`
836
920
  5. [ ] Convert file-based routes to `urls()` DSL in `urls.tsx`
837
921
  6. [ ] Migrate layouts to `layout()` with `<Outlet />`
838
- 7. [ ] Convert data fetching to `createLoader()` + `ctx.use()`
922
+ 7. [ ] Keep inline server `fetch`/DB calls in handlers; add `createLoader()` +
923
+ `loader()` only where you need live, refreshable, or independently
924
+ cached data (§3)
839
925
  8. [ ] Migrate `middleware.ts` to `router.use()` (auth, guards, logging)
840
926
  9. [ ] Replace `next/link` with `Link` from `@rangojs/router/client`; keep
841
927
  "instant navigations" via `prefetch="viewport"`/`defaultPrefetch` (§6)
842
928
  10. [ ] Convert loading/error files to `loading()` / `errorBoundary()`
843
929
  11. [ ] Migrate API routes to `path.json()` / `path.text()`
844
- 12. [ ] Update metadata to use `Meta` handle + `<MetaTags />` in document head
930
+ 12. [ ] Update metadata to use `Meta` handle + `<Html.Meta />` in document head
845
931
  (`generateMetadata` → loader push + `{ ssr: false }`)
846
932
  13. [ ] Replace `next-themes` with `theme: true` in createRouter (see `/theme`)
847
933
  14. [ ] Map rendering-mode segment config: `revalidate = N` → `cache({ ttl })`,
@@ -55,7 +55,9 @@ actor and tenant scope at the repository boundary, verify membership/role, and
55
55
  perform the data query through that scope. Avoid exporting an unrestricted DB
56
56
  handle to page handlers.
57
57
 
58
- Tests must prove denial, not only success:
58
+ Tests must prove denial, not only success (the `/testing` skill's `runMiddleware`,
59
+ `runLoader`, and `runInRequestContext` take your DB double through `env`; see its
60
+ `bindings.md`):
59
61
 
60
62
  - a user cannot read another tenant by guessing an ID;
61
63
  - a member cannot perform an owner/admin mutation;
@@ -102,7 +104,8 @@ For a Node-to-Workers move, search for:
102
104
 
103
105
  Prefer web standards (`fetch`, `Request`, `Response`, WebCrypto) and provider
104
106
  bindings. Test the production bundle in `vite preview`; a successful TypeScript
105
- check does not prove an SDK runs in workerd.
107
+ check does not prove an SDK runs in workerd. `/cloudflare` covers bindings,
108
+ local D1, streaming, and deploying the built Worker.
106
109
 
107
110
  ## Plan cutover and rollback
108
111
 
@@ -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
 
@@ -28,7 +33,8 @@ Common reasons to migrate:
28
33
  See: `/middleware`, `/loader`
29
34
  - **Live data layer** — `createLoader()` and `loader()` keep data fresh
30
35
  independently of cached UI. A route can serve cached segments while loaders
31
- still resolve live on every request.
36
+ still resolve live on every request (on a `ppr` route, a loader registered
37
+ with `ssr: false` bakes into the cached HTML shell instead; see `/ppr`).
32
38
  See: `/loader`, `/caching`, `/cache-guide`
33
39
  - **Explicit caching model** — `cache()` DSL, `revalidate()`, `use cache`, and
34
40
  custom cache stores make data and render caching a first-class part of the router.
@@ -109,19 +115,19 @@ unsupported behavior until runtime, and keep the old packages in the dependency
109
115
  graph. Replace every `react-router` / `react-router-dom` / `@remix-run/*`
110
116
  import at its call site:
111
117
 
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) |
118
+ | React Router import | Replace with |
119
+ | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
120
+ | `Link`, `NavLink` | `Link` from `@rangojs/router/client` (`NavLink` active state via `usePathname()`, see §6) |
121
+ | `Outlet` | `Outlet` from `@rangojs/router/client` |
122
+ | `useNavigate` | `useRouter()` from `@rangojs/router/client` (see §6) |
123
+ | `useLocation`, `useSearchParams`, `useParams` | `usePathname()`, `useSearchParams()` (same `[params, setParams]` tuple), `useParams()` from `@rangojs/router/client`; `location.state` → typed `useLocationState()` (see `/hooks`) |
124
+ | `useLoaderData` | merge the loader into the handler; `useLoader()` for live client data or `clientUrls()` routes (see §3) |
125
+ | `useActionData` | `useActionState` (standard React, see §3) |
126
+ | `Form` | `<form action={serverAction}>` with a `"use server"` function (see §3) |
127
+ | `useFetcher` | submits → server actions + `useActionState`/`useOptimistic`; reads → `useLoader()` / `useFetchLoader()` (see `/hooks`) |
128
+ | `defer` / `Await` | `loading()` DSL / plain `<Suspense>` (see §5) |
129
+ | `json()`, `redirect()` | plain return values; `redirect` from `@rangojs/router` |
130
+ | `useRouteError` | the `error` prop of `errorBoundary()` (see §5) |
125
131
 
126
132
  If an import has no row here and no obvious Rango equivalent, stop and surface
127
133
  it to the user — do not mock it to keep the build green.
@@ -142,48 +148,54 @@ Each numbered step's full walkthrough lives in a companion file linked below.
142
148
 
143
149
  ## 11. Key Conceptual Differences
144
150
 
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 |
151
+ | Concept | React Router | Rango |
152
+ | ------------------- | --------------------------------------------------------------------------- | -------------------------------------- |
153
+ | Rendering | Client-side by default, SSR opt-in | Server components by default (RSC) |
154
+ | Data loading | `loader()` + `useLoaderData()` | Direct fetch in server components |
155
+ | Form actions | Route-scoped `action()` | Function-scoped `"use server"` |
156
+ | Route definition | File-based or `createBrowserRouter` | `urls()` DSL with `path()`, `layout()` |
157
+ | Middleware | v7: route `middleware` export; v6 / Remix v2: none (guards live in loaders) | `router.use()` + DSL `middleware()` |
158
+ | Parallel routes | Not built-in | `parallel()` DSL |
159
+ | Intercepting routes | Not built-in | `intercept()` DSL |
160
+ | Caching | Not built-in | `cache()` DSL, `"use cache"` |
161
+ | Type-safe routes | Partial (v7 framework mode) | Full: params, names, href, reverse |
156
162
 
157
163
  ## Migration Checklist
158
164
 
159
165
  1. [ ] Set up Vite config with `rango()` plugin
160
- 2. [ ] Create Document component with `<MetaTags />` in head
166
+ 2. [ ] Create Document component with `<Html.Meta />` in head
161
167
  3. [ ] Create `router.tsx` with `createRouter()`
162
168
  4. [ ] Convert route config / file routes to `urls()` DSL
163
169
  5. [ ] Migrate layouts — keep `<Outlet />` (import from `@rangojs/router/client`)
164
- 6. [ ] Merge loaders + components into handler functions (fetch + render in one place)
170
+ 6. [ ] Merge loaders + components into handler functions (fetch + render in one place),
171
+ or port hook-heavy groups with `clientUrls()` (see "Two target shapes")
165
172
  7. [ ] Convert React Router actions to `"use server"` functions
166
173
  8. [ ] Migrate auth guards from loaders to `router.use()`
167
174
  9. [ ] Replace `react-router-dom` Link/navigation with `@rangojs/router/client`
168
175
  10. [ ] Convert error boundaries to `errorBoundary()` DSL
169
- 11. [ ] Update metadata to use `Meta` handle + `<MetaTags />`
176
+ 11. [ ] Update metadata to use `Meta` handle + `<Html.Meta />`
170
177
  12. [ ] Replace custom theme provider with `theme: true` in createRouter (see `/theme`)
171
178
  13. [ ] Run `npx rango generate src/` to generate route types
172
179
  14. [ ] Verify no shims: `grep -rnE "from ['\"](react-router|@remix-run)" src/ app/`
173
180
  returns nothing, no mock modules or aliases exist, and the packages are
174
181
  out of `package.json`
175
182
 
176
- **Cloudflare Workers (if migrating an RR7-on-Workers app):**
183
+ **Cloudflare Workers (if migrating an RR7-on-Workers app):** the linked
184
+ sections are in [`../cloudflare/references/streaming-and-deploy.md`](../cloudflare/references/streaming-and-deploy.md).
177
185
 
178
- 14. [ ] Audit the custom worker entry — the `router.fetch()` response must pass
186
+ 15. [ ] Audit the custom worker entry — the `router.fetch()` response must pass
179
187
  through as a **stream** (`new Response(response.body, response)`); remove
180
188
  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
189
+ path (keep it only on legacy/proxy branches). See
190
+ [Keep streamed responses intact](../cloudflare/references/streaming-and-deploy.md#keep-streamed-responses-intact).
191
+ 16. [ ] Switch local dev to `vite dev` / `vite preview` (they stream + load
183
192
  `.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.
193
+ verification — its local gzip can buffer and hide streaming. See
194
+ [Use Vite for local RSC work](../cloudflare/references/streaming-and-deploy.md#use-vite-for-local-rsc-work).
195
+ 17. [ ] Move build output paths from RR7's `build/` to vite's `dist/`: client
196
+ assets land in `dist/client/`; update cleanup scripts to match.
197
+ 18. [ ] Deploy with the generated config (`wrangler deploy -c dist/rsc/wrangler.json`,
198
+ or `dist/<name>/wrangler.json` for a custom RSC environment name), not a
199
+ root `wrangler deploy` against the source worker entry (Wrangler cannot
200
+ rebuild Rango's Vite virtual modules). See
201
+ [Deploy the built Worker](../cloudflare/references/streaming-and-deploy.md#deploy-the-built-worker).