@rangojs/router 0.0.0-experimental.e9c0b2f2 → 0.0.0-experimental.ea9f40f2

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 (222) hide show
  1. package/AGENTS.md +6 -10
  2. package/README.md +289 -938
  3. package/dist/bin/rango.js +271 -46
  4. package/dist/vite/index.js +673 -193
  5. package/package.json +10 -8
  6. package/skills/api-client/SKILL.md +1 -1
  7. package/skills/breadcrumbs/SKILL.md +31 -14
  8. package/skills/cache-guide/SKILL.md +5 -2
  9. package/skills/caching/SKILL.md +59 -4
  10. package/skills/catalog.json +271 -0
  11. package/skills/comparison/SKILL.md +50 -0
  12. package/skills/comparison/agents/openai.yaml +4 -0
  13. package/skills/comparison/references/framework-comparison.md +837 -0
  14. package/skills/composability/SKILL.md +83 -2
  15. package/skills/debug-manifest/SKILL.md +1 -1
  16. package/skills/defer-hydration/SKILL.md +235 -0
  17. package/skills/document-cache/SKILL.md +9 -1
  18. package/skills/fonts/SKILL.md +1 -1
  19. package/skills/handler-use/SKILL.md +8 -8
  20. package/skills/hooks/SKILL.md +54 -892
  21. package/skills/hooks/data.md +273 -0
  22. package/skills/hooks/handle-and-actions.md +103 -0
  23. package/skills/hooks/navigation.md +110 -0
  24. package/skills/hooks/outlets.md +41 -0
  25. package/skills/hooks/state.md +228 -0
  26. package/skills/hooks/urls.md +135 -0
  27. package/skills/host-router/SKILL.md +4 -4
  28. package/skills/i18n/SKILL.md +1 -1
  29. package/skills/intercept/SKILL.md +46 -14
  30. package/skills/layout/SKILL.md +27 -10
  31. package/skills/links/SKILL.md +1 -1
  32. package/skills/loader/SKILL.md +23 -1
  33. package/skills/middleware/SKILL.md +7 -3
  34. package/skills/migrate-nextjs/SKILL.md +167 -6
  35. package/skills/migrate-react-router/SKILL.md +59 -677
  36. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  37. package/skills/migrate-react-router/component-migration.md +196 -0
  38. package/skills/migrate-react-router/data-and-actions.md +225 -0
  39. package/skills/migrate-react-router/route-mapping.md +271 -0
  40. package/skills/mime-routes/SKILL.md +1 -1
  41. package/skills/observability/SKILL.md +9 -1
  42. package/skills/parallel/SKILL.md +23 -4
  43. package/skills/ppr/SKILL.md +622 -0
  44. package/skills/prerender/SKILL.md +28 -18
  45. package/skills/rango/SKILL.md +84 -25
  46. package/skills/response-routes/SKILL.md +15 -1
  47. package/skills/route/SKILL.md +71 -4
  48. package/skills/router-setup/SKILL.md +14 -3
  49. package/skills/scripts/SKILL.md +1 -1
  50. package/skills/server-actions/SKILL.md +3 -2
  51. package/skills/shell-manifest/SKILL.md +185 -0
  52. package/skills/streams-and-websockets/SKILL.md +1 -1
  53. package/skills/tailwind/SKILL.md +1 -1
  54. package/skills/testing/SKILL.md +2 -1
  55. package/skills/testing/handles.md +4 -2
  56. package/skills/testing/render-handler.md +15 -14
  57. package/skills/testing/reverse-and-types.md +8 -7
  58. package/skills/theme/SKILL.md +1 -1
  59. package/skills/typesafety/SKILL.md +45 -919
  60. package/skills/typesafety/env-and-bindings.md +254 -0
  61. package/skills/typesafety/generated-files-and-cli.md +335 -0
  62. package/skills/typesafety/params-and-search.md +153 -0
  63. package/skills/typesafety/route-types.md +209 -0
  64. package/skills/use-cache/SKILL.md +30 -3
  65. package/skills/vercel/SKILL.md +1 -1
  66. package/skills/view-transitions/SKILL.md +44 -1
  67. package/src/browser/event-controller.ts +62 -10
  68. package/src/browser/logging.ts +28 -0
  69. package/src/browser/merge-segment-loaders.ts +6 -4
  70. package/src/browser/navigation-bridge.ts +65 -16
  71. package/src/browser/navigation-client.ts +32 -2
  72. package/src/browser/navigation-store.ts +128 -14
  73. package/src/browser/network-error-handler.ts +34 -7
  74. package/src/browser/partial-update.ts +76 -17
  75. package/src/browser/prefetch/cache.ts +51 -11
  76. package/src/browser/prefetch/fetch.ts +59 -21
  77. package/src/browser/prefetch/queue.ts +19 -4
  78. package/src/browser/react/Link.tsx +13 -3
  79. package/src/browser/react/NavigationProvider.tsx +108 -4
  80. package/src/browser/response-adapter.ts +38 -9
  81. package/src/browser/rsc-router.tsx +54 -4
  82. package/src/browser/scroll-restoration.ts +7 -5
  83. package/src/browser/segment-reconciler.ts +31 -21
  84. package/src/browser/server-action-bridge.ts +22 -10
  85. package/src/browser/types.ts +54 -1
  86. package/src/build/generate-manifest.ts +155 -131
  87. package/src/build/index.ts +3 -1
  88. package/src/build/route-trie.ts +35 -7
  89. package/src/build/route-types/include-resolution.ts +347 -47
  90. package/src/build/runtime-discovery.ts +4 -1
  91. package/src/cache/cache-key-utils.ts +29 -0
  92. package/src/cache/cache-runtime.ts +262 -71
  93. package/src/cache/cache-scope.ts +2 -17
  94. package/src/cache/cache-tag.ts +60 -14
  95. package/src/cache/cf/cf-cache-store.ts +243 -20
  96. package/src/cache/document-cache.ts +54 -21
  97. package/src/cache/index.ts +1 -0
  98. package/src/cache/memory-segment-store.ts +110 -3
  99. package/src/cache/profile-registry.ts +15 -0
  100. package/src/cache/read-through-swr.ts +15 -1
  101. package/src/cache/segment-codec.ts +4 -4
  102. package/src/cache/shell-snapshot.ts +417 -0
  103. package/src/cache/types.ts +158 -0
  104. package/src/cache/vercel/vercel-cache-store.ts +401 -124
  105. package/src/client.rsc.tsx +0 -3
  106. package/src/client.tsx +0 -3
  107. package/src/cloudflare/tracing.ts +7 -8
  108. package/src/defer.ts +11 -22
  109. package/src/handle.ts +37 -15
  110. package/src/handles/MetaTags.tsx +16 -82
  111. package/src/handles/breadcrumbs.ts +12 -14
  112. package/src/handles/deferred-resolution.ts +127 -0
  113. package/src/handles/is-thenable.ts +7 -8
  114. package/src/handles/meta.ts +7 -44
  115. package/src/host/errors.ts +15 -0
  116. package/src/host/index.ts +1 -0
  117. package/src/index.rsc.ts +8 -2
  118. package/src/index.ts +19 -13
  119. package/src/internal-debug.ts +11 -8
  120. package/src/prerender.ts +17 -4
  121. package/src/redirect-origin.ts +14 -0
  122. package/src/render-error-thrower.tsx +20 -0
  123. package/src/route-content-wrapper.tsx +12 -5
  124. package/src/route-definition/dsl-helpers.ts +21 -32
  125. package/src/route-definition/helper-factories.ts +0 -2
  126. package/src/route-definition/helpers-types.ts +43 -43
  127. package/src/route-definition/index.ts +1 -2
  128. package/src/route-definition/resolve-handler-use.ts +0 -1
  129. package/src/route-definition/use-item-types.ts +3 -6
  130. package/src/route-map-builder.ts +41 -4
  131. package/src/route-types.ts +0 -5
  132. package/src/router/find-match.ts +86 -8
  133. package/src/router/instrument.ts +9 -4
  134. package/src/router/lazy-includes.ts +72 -12
  135. package/src/router/loader-resolution.ts +14 -2
  136. package/src/router/manifest.ts +56 -11
  137. package/src/router/match-api.ts +76 -32
  138. package/src/router/match-handlers.ts +181 -135
  139. package/src/router/match-middleware/background-revalidation.ts +40 -23
  140. package/src/router/match-middleware/cache-store.ts +39 -24
  141. package/src/router/match-result.ts +35 -15
  142. package/src/router/middleware.ts +64 -38
  143. package/src/router/navigation-snapshot.ts +7 -5
  144. package/src/router/parse-pattern.ts +115 -0
  145. package/src/router/pattern-matching.ts +53 -64
  146. package/src/router/prefetch-limits.ts +37 -0
  147. package/src/router/prerender-match.ts +11 -5
  148. package/src/router/preview-match.ts +3 -1
  149. package/src/router/request-classification.ts +23 -8
  150. package/src/router/route-snapshot.ts +14 -2
  151. package/src/router/router-context.ts +3 -1
  152. package/src/router/router-interfaces.ts +32 -1
  153. package/src/router/router-options.ts +30 -0
  154. package/src/router/segment-resolution/fresh.ts +39 -3
  155. package/src/router/segment-resolution/loader-cache.ts +93 -2
  156. package/src/router/segment-resolution/loader-mask.ts +60 -0
  157. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  158. package/src/router/segment-resolution/mask-nested.ts +83 -0
  159. package/src/router/segment-resolution/revalidation.ts +3 -0
  160. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  161. package/src/router/substitute-pattern-params.ts +54 -35
  162. package/src/router/telemetry-otel.ts +6 -8
  163. package/src/router/telemetry.ts +9 -1
  164. package/src/router/tracing.ts +14 -5
  165. package/src/router/trie-matching.ts +19 -11
  166. package/src/router/url-params.ts +13 -0
  167. package/src/router.ts +47 -16
  168. package/src/rsc/full-payload.ts +70 -0
  169. package/src/rsc/handler.ts +60 -33
  170. package/src/rsc/manifest-init.ts +1 -1
  171. package/src/rsc/nonce.ts +10 -1
  172. package/src/rsc/progressive-enhancement.ts +61 -4
  173. package/src/rsc/redirect-guard.ts +2 -1
  174. package/src/rsc/rsc-rendering.ts +429 -37
  175. package/src/rsc/server-action.ts +25 -2
  176. package/src/rsc/shell-capture.ts +1190 -0
  177. package/src/rsc/shell-serve.ts +181 -0
  178. package/src/rsc/transition-gate.ts +89 -0
  179. package/src/rsc/types.ts +30 -0
  180. package/src/segment-loader-promise.ts +18 -0
  181. package/src/segment-system.tsx +149 -14
  182. package/src/server/context.ts +67 -9
  183. package/src/server/cookie-store.ts +73 -1
  184. package/src/server/loader-registry.ts +13 -1
  185. package/src/server/request-context.ts +169 -10
  186. package/src/ssr/index.tsx +462 -178
  187. package/src/ssr/inject-rsc-eager.ts +167 -0
  188. package/src/ssr/ssr-root.tsx +228 -0
  189. package/src/testing/collect-handle.ts +14 -8
  190. package/src/testing/dispatch.ts +152 -40
  191. package/src/testing/generated-routes.ts +27 -11
  192. package/src/testing/index.ts +6 -0
  193. package/src/testing/render-handler.ts +14 -0
  194. package/src/testing/render-route.tsx +13 -10
  195. package/src/testing/run-transition-when.ts +164 -0
  196. package/src/theme/ThemeProvider.tsx +36 -26
  197. package/src/types/handler-context.ts +1 -1
  198. package/src/types/index.ts +2 -0
  199. package/src/types/route-config.ts +19 -7
  200. package/src/types/segments.ts +100 -0
  201. package/src/urls/include-helper.ts +10 -8
  202. package/src/urls/include-provider.ts +71 -0
  203. package/src/urls/index.ts +1 -0
  204. package/src/urls/path-helper-types.ts +44 -12
  205. package/src/urls/path-helper.ts +5 -0
  206. package/src/urls/pattern-types.ts +36 -0
  207. package/src/urls/type-extraction.ts +43 -18
  208. package/src/urls/urls-function.ts +0 -1
  209. package/src/vercel/tracing.ts +7 -7
  210. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  211. package/src/vite/discovery/discover-routers.ts +1 -1
  212. package/src/vite/discovery/discovery-errors.ts +61 -0
  213. package/src/vite/index.ts +7 -0
  214. package/src/vite/inject-client-debug.ts +88 -0
  215. package/src/vite/plugins/vercel-output.ts +114 -25
  216. package/src/vite/plugins/version-injector.ts +22 -7
  217. package/src/vite/plugins/virtual-entries.ts +80 -22
  218. package/src/vite/rango.ts +29 -19
  219. package/src/vite/router-discovery.ts +171 -43
  220. package/src/vite/utils/prerender-utils.ts +17 -4
  221. package/src/vite/utils/shared-utils.ts +47 -0
  222. package/src/network-error-thrower.tsx +0 -18
@@ -64,683 +64,47 @@ framework mode respectively.
64
64
  Work route-by-route, bottom-up. Start with leaf routes, then layouts, then
65
65
  loaders/actions. Verify each route works before moving to the next.
66
66
 
67
- ## 1. Project Setup
68
-
69
- Replace React Router tooling with Vite + Rango:
70
-
71
- ```bash
72
- # Framework mode:
73
- npm remove react-router @react-router/dev @react-router/node @react-router/serve
74
- # Library mode:
75
- npm remove react-router react-router-dom
76
-
77
- npm install @rangojs/router
78
- ```
79
-
80
- Replace the `@react-router/dev` Vite plugin with `rango()`:
81
-
82
- ```typescript
83
- // vite.config.ts
84
- // Before: import { reactRouter } from "@react-router/dev/vite";
85
- import { defineConfig } from "vite";
86
- import { rango } from "@rangojs/router/vite";
87
-
88
- export default defineConfig({
89
- plugins: [rango()],
90
- });
91
- ```
92
-
93
- Delete `react-router.config.ts` route configuration moves to the `urls()` DSL.
94
-
95
- ```typescript
96
- // src/router.tsx
97
- import { createRouter } from "@rangojs/router";
98
- import { Document } from "./document";
99
- import { urlpatterns } from "./urls";
100
-
101
- export default createRouter({
102
- document: Document,
103
- }).routes(urlpatterns);
104
- ```
105
-
106
- ## 2. Route Mapping
107
-
108
- ### RR7 framework mode: route modules → urls() DSL
109
-
110
- In framework mode, each route is a file with conventional exports (`loader`,
111
- `action`, `default`, `meta`, `headers`, `shouldRevalidate`, `handle`,
112
- `ErrorBoundary`, `HydrateFallback`). In Rango, all of these become part of the
113
- `urls()` DSL or move into the server component handler:
114
-
115
- ```text
116
- RR7 route module export → Rango equivalent
117
- ─────────────────────────────────────────────────────
118
- default (Component) → handler in path()
119
- loader → fetch in handler, or createLoader()
120
- action → "use server" function
121
- meta → ctx.use(Meta) in handler
122
- headers → ctx.header() in handler or middleware
123
- shouldRevalidate → revalidate() DSL
124
- ErrorBoundary → errorBoundary() DSL
125
- HydrateFallback → loading() DSL
126
- handle → createHandle() for cross-segment data (breadcrumbs, etc.)
127
- clientLoader / clientAction → "use client" component with React hooks
128
- ```
129
-
130
- #### Example: full route module migration
131
-
132
- ```typescript
133
- // RR7 framework mode: app/routes/product.$slug.tsx
134
- import type { Route } from "./+types/product.$slug";
135
-
136
- export async function loader({ params }: Route.LoaderArgs) {
137
- const product = await getProduct(params.slug);
138
- if (!product) throw new Response("Not Found", { status: 404 });
139
- return { product };
140
- }
141
-
142
- export async function action({ request }: Route.ActionArgs) {
143
- const formData = await request.formData();
144
- await addToCart(formData.get("productId") as string);
145
- return { ok: true };
146
- }
147
-
148
- export function meta({ data }: Route.MetaArgs) {
149
- return [{ title: data.product.name }];
150
- }
151
-
152
- export function headers() {
153
- return { "Cache-Control": "max-age=300" };
154
- }
155
-
156
- export function shouldRevalidate({ actionResult }) {
157
- return !!actionResult;
158
- }
159
-
160
- export default function ProductPage({ loaderData }: Route.ComponentProps) {
161
- return <div>{loaderData.product.name}</div>;
162
- }
163
-
164
- export function ErrorBoundary() {
165
- return <div>Product error</div>;
166
- }
167
- ```
168
-
169
- ```typescript
170
- // Rango: urls.tsx + handler
171
- import { notFound } from "@rangojs/router";
172
-
173
- const ProductPage: Handler<"product"> = async (ctx) => {
174
- const product = await getProduct(ctx.params.slug);
175
- if (!product) notFound("Product not found");
176
-
177
- const meta = ctx.use(Meta);
178
- meta({ title: product.name });
179
- ctx.header("Cache-Control", "max-age=300");
180
-
181
- return <div>{product.name}</div>;
182
- };
183
-
184
- // In urls.tsx:
185
- path("/product/:slug", ProductPage, { name: "product" }, () => [
186
- revalidate(({ actionId }) => !!actionId),
187
- errorBoundary(() => <div>Product error</div>),
188
- loading(<ProductSkeleton />),
189
- ])
190
- ```
191
-
192
- Key shift: the route module's scattered exports consolidate into the handler
193
- (data fetching, meta, headers) and the DSL (revalidation, error boundary, loading).
194
-
195
- ### RR7 file routing → urls() DSL
196
-
197
- | RR7 file path | Rango |
198
- | ---------------------------------------- | ------------------------------------------------------------- |
199
- | `app/routes/_index.tsx` | `path("/", HomePage, { name: "home" })` |
200
- | `app/routes/about.tsx` | `path("/about", AboutPage, { name: "about" })` |
201
- | `app/routes/blog.$slug.tsx` | `path("/blog/:slug", BlogPost, { name: "blogPost" })` |
202
- | `app/routes/files.$.tsx` (splat) | `path("/files/:path+", FileBrowser, { name: "files" })` |
203
- | `app/routes/dashboard.tsx` (layout) | `layout(<DashboardLayout />, () => [...])` |
204
- | `app/routes/dashboard._index.tsx` | `path("/dashboard", DashboardIndex, { name: "dashboard" })` |
205
- | `app/routes/dashboard.settings.tsx` | `path("/dashboard/settings", Settings, { name: "settings" })` |
206
- | `app/routes/_auth.tsx` (pathless layout) | `layout(<AuthLayout />, () => [...])` |
207
- | `app/routes/_auth.login.tsx` | `path("/login", LoginPage, { name: "login" })` |
208
-
209
- ### Library mode: config routes → urls() DSL
210
-
211
- | React Router | Rango |
212
- | -------------------------------------- | ------------------------------------------------------- |
213
- | `path: "/"` | `path("/", HomePage, { name: "home" })` |
214
- | `path: "about"` | `path("/about", AboutPage, { name: "about" })` |
215
- | `path: "blog/:slug"` | `path("/blog/:slug", BlogPost, { name: "blogPost" })` |
216
- | `path: "files/*"` (splat) | `path("/files/:path+", FileBrowser, { name: "files" })` |
217
- | `path: "docs/:lang?"` (optional param) | `path("/docs/:lang?", Docs, { name: "docs" })` |
218
-
219
- ### Layouts
220
-
221
- React Router layouts use `<Outlet />` — same concept in Rango:
222
-
223
- ```typescript
224
- // React Router:
225
- function DashboardLayout() {
226
- return (
227
- <div className="dashboard">
228
- <Outlet />
229
- </div>
230
- );
231
- }
232
-
233
- // route config:
234
- { path: "dashboard", element: <DashboardLayout />, children: [...] }
235
-
236
- // Rango: same <Outlet />, from @rangojs/router/client
237
- import { Outlet } from "@rangojs/router/client";
238
-
239
- layout(<DashboardLayout />, () => [
240
- path("/dashboard", DashboardIndex, { name: "dashboard" }),
241
- path("/dashboard/settings", Settings, { name: "settings" }),
242
- ])
243
- ```
244
-
245
- ### Dynamic layouts (with data)
246
-
247
- ```typescript
248
- // React Router: useLoaderData() in layout component
249
- function DashboardLayout() {
250
- const { user } = useLoaderData();
251
- return <Shell user={user}><Outlet /></Shell>;
252
- }
253
-
254
- // Rango: handler function layout (server component)
255
- layout(async (ctx) => {
256
- const user = ctx.get("user");
257
- return (
258
- <Shell user={user}>
259
- <Outlet />
260
- </Shell>
261
- );
262
- }, () => [
263
- path("/dashboard", DashboardIndex, { name: "dashboard" }),
264
- ])
265
- ```
266
-
267
- ### Nested routes
268
-
269
- React Router's nested route tree maps directly to Rango's `layout()` nesting:
270
-
271
- ```typescript
272
- // React Router:
273
- createBrowserRouter([{
274
- path: "/",
275
- element: <RootLayout />,
276
- children: [
277
- { path: "dashboard",
278
- element: <DashboardLayout />,
279
- children: [
280
- { index: true, element: <DashboardIndex /> },
281
- { path: "settings", element: <Settings /> },
282
- ]
283
- },
284
- ]
285
- }])
286
-
287
- // Rango:
288
- urls(({ path, layout }) => [
289
- layout(<RootLayout />, () => [
290
- layout(<DashboardLayout />, () => [
291
- path("/dashboard", DashboardIndex, { name: "dashboard" }),
292
- path("/dashboard/settings", Settings, { name: "settings" }),
293
- ]),
294
- ]),
295
- ])
296
- ```
297
-
298
- ### Route groups / pathless layouts
299
-
300
- React Router's pathless routes (layout routes without a path) are Rango's
301
- layouts without a URL prefix:
302
-
303
- ```typescript
304
- // React Router: { element: <AuthLayout />, children: [...] }
305
-
306
- // Rango: layout with no URL segment
307
- layout(<AuthLayout />, () => [
308
- path("/login", LoginPage, { name: "login" }),
309
- path("/register", RegisterPage, { name: "register" }),
310
- ])
311
- ```
312
-
313
- ### Index routes
314
-
315
- ```typescript
316
- // React Router: { index: true, element: <Home /> }
317
-
318
- // Rango: path with "/" inside a layout
319
- layout(<RootLayout />, () => [
320
- path("/", HomePage, { name: "home" }),
321
- ])
322
- ```
323
-
324
- ## 3. Data Fetching
325
-
326
- ### Loaders → handler (the default migration)
327
-
328
- In React Router, loaders and components are separate: the loader fetches data,
329
- the component renders it via `useLoaderData()`. In Rango, server component
330
- handlers do both — combine the loader and component into a single handler:
331
-
332
- ```typescript
333
- // React Router: separate loader + component
334
- export async function loader({ params }) {
335
- const product = await getProduct(params.slug);
336
- return { product };
337
- }
338
- function ProductPage() {
339
- const { product } = useLoaderData();
340
- return <div>{product.name}</div>;
341
- }
342
-
343
- // Rango: handler fetches and renders directly
344
- const ProductPage: Handler<"product"> = async (ctx) => {
345
- const product = await getProduct(ctx.params.slug);
346
- return <div>{product.name}</div>;
347
- };
348
- ```
349
-
350
- This is the standard migration path. The handler IS the loader — it fetches
351
- data, then returns JSX. No separate data-fetching layer needed.
352
-
353
- ### When to use createLoader()
354
-
355
- Rango's `createLoader()` is a live data layer, not a loader migration target.
356
- Use it only when you need capabilities beyond what the handler provides:
357
-
358
- - **Client-side reactive data** — `useLoader()` in client components for data
359
- that updates without a full page navigation
360
- - **Shared data across segments** — a loader registered on a layout is available
361
- to all child routes via `ctx.use(Loader)` or `useLoader(Loader)`
362
- - **Independent revalidation** — `revalidate()` on a specific loader after actions
363
- - **Per-loader caching** — `loader(L, () => [cache({ ttl: 60 })])`
364
-
365
- If the React Router loader just fetches data for its page component, merge it
366
- into the handler. See `/loader` for when the live data layer is useful.
367
-
368
- ### Actions
369
-
370
- React Router form actions map to Rango server actions:
371
-
372
- ```typescript
373
- // React Router:
374
- export async function action({ request }) {
375
- const formData = await request.formData();
376
- await updateUser(formData.get("name"));
377
- return redirect("/profile");
378
- }
379
- function EditProfile() {
380
- return (
381
- <Form method="post">
382
- <input name="name" />
383
- <button type="submit">Save</button>
384
- </Form>
385
- );
386
- }
387
-
388
- // Rango: "use server" action + native form or useActionState
389
- "use server";
390
- import { redirect } from "@rangojs/router";
391
-
392
- export async function updateProfile(formData: FormData): Promise<void> {
393
- await updateUser(formData.get("name") as string);
394
- throw redirect("/profile");
395
- }
396
-
397
- // Client component:
398
- function EditProfile() {
399
- return (
400
- <form action={updateProfile}>
401
- <input name="name" />
402
- <button type="submit">Save</button>
403
- </form>
404
- );
405
- }
406
- ```
407
-
408
- Key difference: React Router actions are route-scoped (declared per route).
409
- Rango actions are function-scoped (`"use server"` on any exported async function).
410
-
411
- ### useLoaderData
412
-
413
- There is no `useLoaderData()` in Rango. For most cases, the handler fetches
414
- and renders directly (see above). When a client component needs live reactive
415
- data, use `createLoader()` + `useLoader()`:
416
-
417
- ```typescript
418
- // React Router: useLoaderData() in client component
419
- function ProductPrice() {
420
- const { price } = useLoaderData();
421
- return <span>{price}</span>;
422
- }
423
-
424
- // Rango: useLoader() reads from a registered loader (live data layer)
425
- "use client";
426
- import { useLoader } from "@rangojs/router/client";
427
- import { PriceLoader } from "../loaders";
428
-
429
- function ProductPrice() {
430
- const { data } = useLoader(PriceLoader);
431
- return <span>{data.price}</span>;
432
- }
433
- ```
434
-
435
- `useLoader()` provides live data that stays fresh — it re-fetches on navigation
436
- and after actions (controlled by `revalidate()`). This is different from
437
- `useLoaderData()` which just reads a snapshot.
438
-
439
- ### useActionData
440
-
441
- React Router's `useActionData()` reads the return value of a route-scoped
442
- `action()`. In Rango, actions are standard React server actions (`"use server"`),
443
- so all React patterns apply directly:
444
-
445
- ```typescript
446
- // React Router:
447
- export async function action({ request }) {
448
- const form = await request.formData();
449
- const errors = validate(form);
450
- if (errors) return { errors };
451
- await save(form);
452
- return { ok: true };
453
- }
454
- function EditForm() {
455
- const data = useActionData();
456
- return (
457
- <Form method="post">
458
- {data?.errors && <p>{data.errors}</p>}
459
- <input name="title" />
460
- <button>Save</button>
461
- </Form>
462
- );
463
- }
464
-
465
- // Rango: useActionState (standard React hook)
466
- "use client";
467
- import { useActionState } from "react";
468
- import { saveForm } from "../actions"; // "use server" function
469
-
470
- function EditForm() {
471
- const [state, action, pending] = useActionState(saveForm, null);
472
- return (
473
- <form action={action}>
474
- {state?.errors && <p>{state.errors}</p>}
475
- <input name="title" />
476
- <button disabled={pending}>Save</button>
477
- </form>
478
- );
479
- }
480
- ```
481
-
482
- Since Rango uses RSC server actions, all React action patterns work:
483
- `useActionState`, `useOptimistic`, `useTransition`, `startTransition`,
484
- and plain `<form action={serverAction}>`. No framework-specific hook needed.
485
-
486
- For the full guide — defining actions, validation with Zod, error handling,
487
- revalidation rules, file uploads, and progressive enhancement — see
488
- `/server-actions`.
489
-
490
- ### clientLoader / clientAction (framework mode)
491
-
492
- RR7 framework mode's `clientLoader` and `clientAction` run in the browser.
493
- Rango does not have a framework-level client loader/action concept — these
494
- migrate to standard React client-side code:
495
-
496
- ```typescript
497
- // RR7: clientLoader fetching from a third-party API
498
- export async function clientLoader() {
499
- const res = await fetch("https://api.weather.com/current?city=london");
500
- return res.json();
501
- }
502
-
503
- // Rango: "use client" component with hooks
504
- "use client";
505
- import { useState, useEffect } from "react";
506
-
507
- function WeatherWidget() {
508
- const [weather, setWeather] = useState(null);
509
- useEffect(() => {
510
- fetch("https://api.weather.com/current?city=london")
511
- .then((r) => r.json())
512
- .then(setWeather);
513
- }, []);
514
- if (!weather) return <span>Loading...</span>;
515
- return <span>{weather.temp}°C</span>;
516
- }
517
- ```
518
-
519
- The general rule: anything that ran in `clientLoader`/`clientAction` moves into
520
- React hooks (`useState`, `useEffect`, `useActionState`, `useOptimistic`) inside
521
- a `"use client"` component. There is no framework wrapper — it's just React.
522
-
523
- ### shouldRevalidate (framework mode)
524
-
525
- RR7's `shouldRevalidate` export maps directly to Rango's `revalidate()` DSL:
526
-
527
- ```typescript
528
- // RR7:
529
- export function shouldRevalidate({ actionResult, currentParams, nextParams }) {
530
- if (actionResult) return true;
531
- return currentParams.slug !== nextParams.slug;
532
- }
533
-
534
- // Rango:
535
- path("/product/:slug", ProductPage, { name: "product" }, () => [
536
- revalidate(({ actionId, currentParams, nextParams }) => {
537
- if (actionId) return true;
538
- return currentParams.slug !== nextParams.slug;
539
- }),
540
- ]);
541
- ```
542
-
543
- Note: RR7's `shouldRevalidate` controls client-side loader re-fetching. Rango's
544
- `revalidate()` controls which segments re-run during partial rendering after
545
- navigation or actions. The intent is the same — skip unnecessary work — but
546
- the mechanism is segment-level rather than loader-level.
547
-
548
- ## 4. Middleware / Route Protection
549
-
550
- React Router doesn't have built-in middleware. Protection is typically done in loaders:
551
-
552
- ```typescript
553
- // React Router: auth check in loader
554
- export async function loader({ request }) {
555
- const user = await getUser(request);
556
- if (!user) throw redirect("/login");
557
- return { user };
558
- }
559
-
560
- // Rango: router.use() for request-level auth
561
- const router = createRouter({})
562
- .use(authInit) // all routes — resolves session
563
- .use("/dashboard/*", requireAuth) // scoped guard
564
- .routes(urlpatterns);
565
- ```
566
-
567
- Use `router.use()` for auth guards (wraps entire request including actions).
568
- Use DSL `middleware()` for render-level concerns (context shaping, headers).
569
- See `/middleware`.
570
-
571
- ## 5. Loading & Error States
572
-
573
- ### Loading / Suspense
574
-
575
- ```typescript
576
- // React Router: defer() + Suspense, or HydrateFallback
577
- export async function loader() {
578
- return defer({ data: fetchData() });
579
- }
580
-
581
- // Rango: loading() DSL for automatic Suspense boundaries
582
- path("/dashboard", DashboardPage, { name: "dashboard" }, () => [
583
- loading(<DashboardSkeleton />),
584
- ])
585
- ```
586
-
587
- ### Error boundaries
588
-
589
- ```typescript
590
- // React Router:
591
- { path: "dashboard", element: <Dashboard />, errorElement: <ErrorPage /> }
592
-
593
- // or with ErrorBoundary component:
594
- function ErrorBoundary() {
595
- const error = useRouteError();
596
- return <div>Error: {error.message}</div>;
597
- }
598
-
599
- // Rango: errorBoundary() wrapping a group of routes
600
- // Server-side error boundaries only receive `error` (no `reset` — server render
601
- // cannot be retried; users can navigate away or refresh).
602
- layout(<DashboardLayout />, () => [
603
- errorBoundary(({ error }) => (
604
- <div>
605
- <h2>Something went wrong</h2>
606
- <p>{error.message}</p>
607
- </div>
608
- )),
609
- path("/dashboard", DashboardIndex, { name: "dashboard" }),
610
- path("/dashboard/settings", Settings, { name: "settings" }),
611
- ])
612
- ```
613
-
614
- ### Not found
615
-
616
- ```typescript
617
- // React Router: { path: "*", element: <NotFound /> }
618
-
619
- // Rango (app-level):
620
- createRouter({
621
- notFound: ({ pathname }) => <NotFoundPage pathname={pathname} />,
622
- })
623
-
624
- // Rango (route-level — catches notFound() thrown in handlers/loaders):
625
- layout(<ShopLayout />, () => [
626
- notFoundBoundary(<ProductNotFound />),
627
- path("/product/:slug", ProductPage, { name: "product" }),
628
- ])
629
- ```
630
-
631
- ## 6. Navigation
632
-
633
- | React Router | Rango |
634
- | ----------------------------------------- | -------------------------------------------------------------------------------- |
635
- | `import { Link } from "react-router-dom"` | `import { Link } from "@rangojs/router/client"` |
636
- | `<Link to="/about">` | `<Link to="/about">` |
637
- | `useNavigate()` | `useRouter()` from `@rangojs/router/client` |
638
- | `navigate("/about")` | `useRouter().push("/about")` |
639
- | `navigate("/about", { replace: true })` | `useRouter().replace("/about")` |
640
- | `navigate(-1)` | `useRouter().back()` |
641
- | `useLocation().pathname` | `usePathname()` from `@rangojs/router/client` |
642
- | `useSearchParams()` | `useSearchParams()` from `@rangojs/router/client` |
643
- | `useParams()` | `useParams()` from `@rangojs/router/client` (or `ctx.params` in server handlers) |
644
- | `useParams<T>()` | `useParams<T>()` — same generic annotation pattern |
645
- | `<NavLink>` | `<Link>` with `usePathname()` for active state |
646
-
647
- ### useNavigate → useRouter
648
-
649
- ```typescript
650
- // React Router:
651
- const navigate = useNavigate();
652
- navigate("/dashboard");
653
- navigate(-1);
654
-
655
- // Rango:
656
- const router = useRouter();
657
- router.push("/dashboard");
658
- router.back();
659
- ```
660
-
661
- ## 7. Metadata / Head
662
-
663
- ```typescript
664
- // React Router: meta function export (framework mode)
665
- export function meta() {
666
- return [{ title: "Home" }, { name: "description", content: "Welcome" }];
667
- }
668
-
669
- // Rango: Meta handle in server handlers
670
- import { Meta } from "@rangojs/router";
671
-
672
- const HomePage: Handler<"home"> = (ctx) => {
673
- const meta = ctx.use(Meta);
674
- meta({ title: "Home" });
675
- meta({ name: "description", content: "Welcome" });
676
- return <div>Home page</div>;
677
- };
678
- ```
679
-
680
- Add `<MetaTags />` in the Document component's `<head>`:
681
-
682
- ```typescript
683
- import { MetaTags } from "@rangojs/router/client";
684
-
685
- function Document({ children }: { children: ReactNode }) {
686
- return (
687
- <html>
688
- <head>
689
- <MetaTags />
690
- </head>
691
- <body>{children}</body>
692
- </html>
693
- );
694
- }
695
- ```
696
-
697
- ## 8. API / Resource Routes
698
-
699
- ```typescript
700
- // React Router (framework mode):
701
- // app/routes/api.users.ts
702
- export async function loader() {
703
- return Response.json(await getUsers());
704
- }
705
-
706
- // Rango: response routes
707
- path.json(
708
- "/api/users",
709
- async () => {
710
- return await getUsers();
711
- },
712
- { name: "apiUsers" },
713
- );
714
- ```
715
-
716
- See `/response-routes` for `path.json()`, `path.text()`, `path.html()`, etc.
717
-
718
- ## 9. Theme / Dark Mode
719
-
720
- Rango has a built-in theme system with FOUC prevention. If the React Router app
721
- uses a custom theme provider or `next-themes`, replace it with Rango's theme API:
722
-
723
- ```typescript
724
- const router = createRouter({
725
- theme: true, // or { defaultTheme: "system", attribute: "class" }
726
- });
727
- ```
728
-
729
- Client components use `useTheme()` to read and toggle:
730
-
731
- ```typescript
732
- "use client";
733
- import { useTheme } from "@rangojs/router/theme";
734
-
735
- function ThemeToggle() {
736
- const { theme, setTheme } = useTheme();
737
- return <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>{theme}</button>;
738
- }
739
- ```
740
-
741
- See `/theme` for full API including system detection and cookie persistence.
742
-
743
- ## 10. Key Conceptual Differences
67
+ ## Replace imports, never shim React Router
68
+
69
+ Do NOT create mock `react-router` modules, Vite aliases, or compatibility
70
+ wrappers (a local `useLoaderData` backed by context, a `Form` that wraps
71
+ `<form>`, a fake `useFetcher`). Shims freeze RR semantics into the app, hide
72
+ unsupported behavior until runtime, and keep the old packages in the dependency
73
+ graph. Replace every `react-router` / `react-router-dom` / `@remix-run/*`
74
+ import at its call site:
75
+
76
+ | React Router import | Replace with |
77
+ | --------------------------------------------- | ----------------------------------------------------------------------------------------- |
78
+ | `Link`, `NavLink` | `Link` from `@rangojs/router/client` (`NavLink` active state via `usePathname()`, see §6) |
79
+ | `Outlet` | `Outlet` from `@rangojs/router/client` |
80
+ | `useNavigate` | `useRouter()` from `@rangojs/router/client` (see §6) |
81
+ | `useLocation`, `useSearchParams`, `useParams` | `usePathname()`, `useSearchParams()`, `useParams()` from `@rangojs/router/client` |
82
+ | `useLoaderData` | merge the loader into the handler; `useLoader()` only for live client data (see §3) |
83
+ | `useActionData` | `useActionState` (standard React, see §3) |
84
+ | `Form` | `<form action={serverAction}>` with a `"use server"` function (see §3) |
85
+ | `useFetcher` | submits server actions + `useActionState`/`useOptimistic`; reads → `useLoader()` |
86
+ | `defer` / `Await` | `loading()` DSL / plain `<Suspense>` (see §5) |
87
+ | `json()`, `redirect()` | plain return values; `redirect` from `@rangojs/router` |
88
+ | `useRouteError` | the `error` prop of `errorBoundary()` (see §5) |
89
+
90
+ If an import has no row here and no obvious Rango equivalent, stop and surface
91
+ it to the user — do not mock it to keep the build green.
92
+
93
+ Done means: `grep -rnE "from ['\"](react-router|@remix-run)" src/ app/` returns
94
+ nothing, and the packages are out of `package.json`.
95
+
96
+ ## Migration steps
97
+
98
+ Each numbered step's full walkthrough lives in a companion file linked below.
99
+
100
+ | Step | File |
101
+ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
102
+ | §1 Project Setup, §2 Route Mapping | [`./route-mapping.md`](./route-mapping.md) |
103
+ | §3 Data Fetching (loaders, actions, `useLoaderData`/`useActionData`, `clientLoader`/`clientAction`, `shouldRevalidate`) | [`./data-and-actions.md`](./data-and-actions.md) |
104
+ | §4 Middleware / Route Protection, §5 Loading & Error States, §6 Navigation, §7 Metadata / Head, §8 API / Resource Routes, §9 Theme / Dark Mode | [`./component-migration.md`](./component-migration.md) |
105
+ | §10 Cloudflare Workers (streaming, dev tooling, deploy) | [`./cloudflare-workers.md`](./cloudflare-workers.md) |
106
+
107
+ ## 11. Key Conceptual Differences
744
108
 
745
109
  | Concept | React Router | Rango |
746
110
  | ------------------- | ----------------------------------- | -------------------------------------- |
@@ -769,3 +133,21 @@ See `/theme` for full API including system detection and cookie persistence.
769
133
  11. [ ] Update metadata to use `Meta` handle + `<MetaTags />`
770
134
  12. [ ] Replace custom theme provider with `theme: true` in createRouter (see `/theme`)
771
135
  13. [ ] Run `npx rango generate src/` to generate route types
136
+ 14. [ ] Verify no shims: `grep -rnE "from ['\"](react-router|@remix-run)" src/ app/`
137
+ returns nothing, no mock modules or aliases exist, and the packages are
138
+ out of `package.json`
139
+
140
+ **Cloudflare Workers (if migrating an RR7-on-Workers app):**
141
+
142
+ 14. [ ] Audit the custom worker entry — the `router.fetch()` response must pass
143
+ through as a **stream** (`new Response(response.body, response)`); remove
144
+ any `.text()`/`.arrayBuffer()`/`HTMLRewriter` buffering from the Rango
145
+ path (keep it only on legacy/proxy branches). See §10a.
146
+ 15. [ ] Switch local dev to `vite dev` / `vite preview` (they stream + load
147
+ `.dev.vars` + provide bindings); stop using `wrangler dev` for local
148
+ verification — it gzip-buffers and kills streaming. See §10b.
149
+ 16. [ ] Move build output paths from RR7's `build/` to vite's `dist/` in
150
+ `wrangler.toml` (`assets` → `./dist/client/`) and cleanup scripts. See §10d.
151
+ 17. [ ] Fix deploy to use the built config `dist/<env>/wrangler.json`
152
+ (`no_bundle: true`), not a root `wrangler deploy` against the source
153
+ worker entry (which can't bundle Rango's virtual modules). See §10e.