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