@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.
- package/README.md +255 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/history-state.d.ts +16 -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-key-utils.d.ts +17 -2
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +132 -19
- package/dist/types/cache/cache-tag.d.ts +92 -7
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +54 -13
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +110 -21
- package/dist/types/cache/tag-invalidation.d.ts +30 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +220 -44
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
- 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/prerender/build-shell-capture.d.ts +11 -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/instrument.d.ts +8 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +9 -3
- 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 +66 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
- package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
- 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/full-payload.d.ts +14 -0
- package/dist/types/rsc/handler.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-build-manifest.d.ts +8 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
- package/dist/types/rsc/shell-capture.d.ts +151 -19
- package/dist/types/rsc/shell-serve.d.ts +140 -17
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +17 -0
- package/dist/types/server/context.d.ts +139 -15
- package/dist/types/server/cookie-store.d.ts +21 -0
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +182 -53
- package/dist/types/ssr/index.d.ts +24 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- 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/run-loader.d.ts +18 -3
- package/dist/types/testing/serve-shell-request.d.ts +132 -0
- package/dist/types/testing/shell-status.d.ts +23 -10
- 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 +28 -1
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/pattern-types.d.ts +22 -18
- 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/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1420 -1348
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +75 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +306 -124
- package/skills/caching/SKILL.md +530 -98
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +65 -38
- package/skills/cloudflare/SKILL.md +87 -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 +75 -57
- 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 +52 -31
- package/skills/deployment-caching/SKILL.md +11 -3
- package/skills/document-cache/SKILL.md +98 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +55 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +34 -17
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +39 -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 +386 -128
- package/skills/middleware/SKILL.md +129 -26
- package/skills/migrate-nextjs/SKILL.md +156 -70
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +51 -39
- package/skills/migrate-react-router/component-migration.md +28 -7
- package/skills/migrate-react-router/data-and-actions.md +21 -6
- package/skills/migrate-react-router/route-mapping.md +19 -10
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +92 -14
- package/skills/parallel/SKILL.md +88 -47
- package/skills/ppr/SKILL.md +866 -379
- package/skills/prerender/SKILL.md +210 -108
- package/skills/rango/SKILL.md +115 -65
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +170 -60
- package/skills/router-setup/SKILL.md +420 -240
- package/skills/scripts/SKILL.md +39 -21
- package/skills/server-actions/SKILL.md +231 -59
- package/skills/shell-manifest/SKILL.md +28 -14
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +73 -33
- package/skills/testing/cache-prerender.md +60 -33
- 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 +26 -6
- 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/serve-shell-request.md +188 -0
- 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 +155 -30
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +137 -50
- 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 +224 -83
- package/skills/vercel/SKILL.md +35 -13
- package/skills/view-transitions/SKILL.md +42 -21
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/history-state.ts +23 -0
- 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 +6 -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 +23 -8
- package/src/cache/cache-policy.ts +22 -0
- package/src/cache/cache-runtime.ts +264 -68
- package/src/cache/cache-scope.ts +419 -43
- package/src/cache/cache-tag.ts +375 -11
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +990 -348
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +133 -28
- package/src/cache/index.ts +2 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +217 -27
- package/src/cache/tag-invalidation.ts +108 -22
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +225 -44
- package/src/cache/vercel/vercel-cache-store.ts +545 -61
- 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 +73 -22
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +121 -33
- 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 +71 -28
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +141 -43
- package/src/router/loader-resolution.ts +115 -39
- package/src/router/match-api.ts +40 -26
- package/src/router/match-handlers.ts +5 -0
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +110 -10
- package/src/router/match-middleware/cache-store.ts +48 -101
- package/src/router/middleware-types.ts +9 -6
- package/src/router/middleware.ts +75 -13
- package/src/router/prerender-match.ts +59 -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 +62 -21
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +407 -76
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +208 -56
- package/src/router/segment-resolution/mask-nested.ts +138 -44
- package/src/router/segment-resolution/revalidation.ts +61 -16
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/full-payload.ts +25 -1
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/progressive-enhancement.ts +3 -2
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-cache-serve.ts +9 -6
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +608 -225
- package/src/rsc/shell-build-manifest.ts +27 -9
- package/src/rsc/shell-capture-constants.ts +73 -6
- package/src/rsc/shell-capture.ts +1068 -296
- package/src/rsc/shell-serve.ts +316 -34
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +18 -0
- package/src/server/context.ts +300 -32
- package/src/server/cookie-store.ts +87 -61
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +297 -67
- package/src/ssr/index.tsx +136 -44
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +14 -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 +68 -6
- package/src/testing/serve-shell-request.ts +378 -0
- package/src/testing/shell-status.ts +34 -12
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +28 -1
- package/src/types/loader-types.ts +8 -6
- package/src/urls/pattern-types.ts +22 -18
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/discovery/shell-prerender-phase.ts +9 -0
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
- package/src/vite/router-discovery.ts +14 -4
|
@@ -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);
|
|
@@ -135,18 +154,20 @@ const HomePage: Handler<"home"> = (ctx) => {
|
|
|
135
154
|
RR's data-derived `meta({ data })` maps to the same push from the LOADER that
|
|
136
155
|
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
|
-
head. See `/loader` → "Writing Handles from Loaders".
|
|
157
|
+
head. See `/loader` → "Writing Handles from Loaders". The loader flag is
|
|
158
|
+
unrelated to RR7's `ssr: false` (SPA mode), which has no route-wide Rango
|
|
159
|
+
equivalent: it makes the server settle that loader before the first flush.
|
|
139
160
|
|
|
140
|
-
Add `<
|
|
161
|
+
Add `<Html.Meta />` in the Document component's `<head>`:
|
|
141
162
|
|
|
142
163
|
```typescript
|
|
143
|
-
import {
|
|
164
|
+
import { Html } from "@rangojs/router/client";
|
|
144
165
|
|
|
145
166
|
function Document({ children }: { children: ReactNode }) {
|
|
146
167
|
return (
|
|
147
168
|
<html>
|
|
148
169
|
<head>
|
|
149
|
-
<
|
|
170
|
+
<Html.Meta />
|
|
150
171
|
</head>
|
|
151
172
|
<body>{children}</body>
|
|
152
173
|
</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>;
|
|
@@ -46,7 +48,9 @@ Use it only when you need capabilities beyond what the handler provides:
|
|
|
46
48
|
document loads, never an HTTP 302 — pre-stream 302s move to middleware)
|
|
47
49
|
- **`meta({ data })` / `handle` exports** — data-derived page metadata becomes
|
|
48
50
|
a handle push from the loader body (`ctx.use(Meta)({ title: data.name })`),
|
|
49
|
-
with `loader(L, { ssr: false })` when it must be in the SSR'd head
|
|
51
|
+
with `loader(L, { ssr: false })` when it must be in the SSR'd head (not
|
|
52
|
+
RR7's `ssr: false` SPA mode, which has no route-wide Rango equivalent: the
|
|
53
|
+
loader flag makes the server settle that loader before the first flush)
|
|
50
54
|
|
|
51
55
|
If the React Router loader just fetches data for its page component AND the
|
|
52
56
|
component can become a server component, merge it into the handler. If the
|
|
@@ -76,6 +80,7 @@ function EditProfile() {
|
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
// Rango: "use server" action + native form or useActionState
|
|
83
|
+
// actions/profile.ts
|
|
79
84
|
"use server";
|
|
80
85
|
import { redirect } from "@rangojs/router";
|
|
81
86
|
|
|
@@ -84,7 +89,10 @@ export async function updateProfile(formData: FormData): Promise<void> {
|
|
|
84
89
|
throw redirect("/profile");
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
//
|
|
92
|
+
// EditProfile.tsx — a server or client component; passing the action itself
|
|
93
|
+
// (not a wrapper function) keeps the form working with JavaScript disabled.
|
|
94
|
+
import { updateProfile } from "./actions/profile";
|
|
95
|
+
|
|
88
96
|
function EditProfile() {
|
|
89
97
|
return (
|
|
90
98
|
<form action={updateProfile}>
|
|
@@ -122,9 +130,12 @@ function ProductPrice() {
|
|
|
122
130
|
}
|
|
123
131
|
```
|
|
124
132
|
|
|
125
|
-
`useLoader()` provides live data that stays fresh —
|
|
126
|
-
and after actions (controlled by `revalidate()`)
|
|
127
|
-
`
|
|
133
|
+
`useLoader()` provides live data that stays fresh — the loader re-runs on
|
|
134
|
+
navigation and after actions (controlled by `revalidate()`), and the result also
|
|
135
|
+
exposes `isLoading`, `error`, `load`, and `refetch`. Register the loader on the
|
|
136
|
+
route (`loader(PriceLoader)`) so its data is in the tree the component reads;
|
|
137
|
+
the read suspends until it arrives, so keep a `<Suspense>` or `loading()` above
|
|
138
|
+
it. See `/loader` and `/hooks`.
|
|
128
139
|
|
|
129
140
|
### useActionData
|
|
130
141
|
|
|
@@ -155,8 +166,12 @@ function EditForm() {
|
|
|
155
166
|
// Rango: useActionState (standard React hook)
|
|
156
167
|
"use client";
|
|
157
168
|
import { useActionState } from "react";
|
|
158
|
-
|
|
169
|
+
// "use server" function with the useActionState shape:
|
|
170
|
+
// export async function saveForm(prev: State, formData: FormData): Promise<State>
|
|
171
|
+
import { saveForm } from "../actions";
|
|
159
172
|
|
|
173
|
+
// Pass the imported action straight to useActionState — not an inline closure —
|
|
174
|
+
// so the form still posts and re-renders with JavaScript disabled.
|
|
160
175
|
function EditForm() {
|
|
161
176
|
const [state, action, pending] = useActionState(saveForm, null);
|
|
162
177
|
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,16 +134,21 @@ 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()`
|
|
136
142
|
for the missing product AND push the data-derived meta itself
|
|
137
143
|
(`ctx.use(Meta)({ title: product.name })`); register it with
|
|
138
144
|
`loader(ProductLoader, { ssr: false })` when the 404 status and
|
|
139
|
-
title must be in the document deterministically
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
title must be in the document deterministically (the flag makes the server
|
|
146
|
+
settle that loader before the first flush; it is unrelated to RR7's
|
|
147
|
+
`ssr: false` SPA mode, which has no route-wide Rango equivalent). See
|
|
148
|
+
`/loader` → "Loader Authority" and "Writing Handles from Loaders". (One RR
|
|
149
|
+
habit that does NOT carry over: a loader `throw redirect()` is a client-side
|
|
150
|
+
navigate on document loads, never an HTTP 302 — pre-stream 302s belong in
|
|
151
|
+
middleware.)
|
|
143
152
|
|
|
144
153
|
### RR7 file routing → urls() DSL
|
|
145
154
|
|
|
@@ -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,83 @@ 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
|
+
### Reading a PPR shell HIT
|
|
67
|
+
|
|
68
|
+
A `ppr` route serving from its shell cache commits the response as soon as the
|
|
69
|
+
stored prelude is read, so its waterfall ends at `ppr:shell-commit`; what runs
|
|
70
|
+
after the commit (the capture snapshot, the seeded tail, the resumed holes)
|
|
71
|
+
prints as one `shell tail` line when it finishes, and rides the NEXT request's
|
|
72
|
+
`Server-Timing` as `ppr-tail`. The store-read rows under `ppr:shell-read` come
|
|
73
|
+
from the built-in stores (`CFCacheStore`'s prelude-first read shown here;
|
|
74
|
+
`VercelCacheStore` reports `hit store`, one `ppr:shell-match (store)` row, and
|
|
75
|
+
a `serial` marker read); other stores show `ppr:shell-read`,
|
|
76
|
+
`ppr:shell-open`, and `ppr:shell-commit` only. From `vite preview` of
|
|
77
|
+
`tests/cloudflare-basic` `/ppr-large/holes` (614 KB prelude, 1.0 MB capture
|
|
78
|
+
snapshot), with the timeline column dropped:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
[RSC Perf] GET /ppr-large/holes (4.00ms)
|
|
82
|
+
start dur span
|
|
83
|
+
1.00ms 0.00ms middleware:*#2
|
|
84
|
+
1.00ms 3.00ms ppr:shell-read (hit l1)
|
|
85
|
+
1.00ms 1.00ms ppr:shell-match (l1)
|
|
86
|
+
1.00ms 3.00ms render:total:pprLargeHoles
|
|
87
|
+
2.00ms 0.00ms ppr:shell-head (bytes=27943)
|
|
88
|
+
2.00ms 2.00ms ppr:shell-prelude (bytes=628949)
|
|
89
|
+
2.00ms 0.00ms ppr:shell-marker (tags=0 parallel commit-wait=0.00ms)
|
|
90
|
+
4.00ms 0.00ms ppr:shell-open (cpu raw prelude=628949b)
|
|
91
|
+
4.00ms 0.00ms ppr:shell-commit (cpu chunks=20 prelude=628949b)
|
|
92
|
+
0.00ms 4.00ms handler:total
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
and, when the tail finishes, one console line (wrapped here):
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
[RSC Perf] GET /ppr-large/holes shell tail: complete snapshot=4ms
|
|
99
|
+
snapshot-read=2ms snapshot-bytes=1019145b snapshot-parse-cpu=2ms
|
|
100
|
+
records=segment:1 pruned=item:5 seed=4ms seed-cpu=0ms match=4ms handover=4ms
|
|
101
|
+
first-html=5ms complete=55ms prelude=628949b tail=1033762b
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- `ppr:shell-match` then `head` then `prelude`: the store read. Only the head
|
|
105
|
+
and the prelude are read before the first byte; the snapshot is read off the
|
|
106
|
+
commit path (`snapshot=` in the tail line). A KV hit after a Cache API miss
|
|
107
|
+
starts with a `ppr:shell-l1-miss` row (the L1 attempt and why it missed). A
|
|
108
|
+
`ppr:shell-memo (hit …)` row means the shell came from the isolate's memo:
|
|
109
|
+
no store read, only the marker check.
|
|
110
|
+
- `ppr:shell-marker` is the tag-marker check, on every `CFCacheStore` read; it
|
|
111
|
+
runs alongside `ppr:shell-prelude`, and `commit-wait` is how much it held the
|
|
112
|
+
commit back (with `tags=0` it resolves at once). For a tagged shell it adds
|
|
113
|
+
`memo=` (how the per-isolate marker memo answered: `fresh`, `stale` served
|
|
114
|
+
while it refreshes, `read` from the store, `bypass` for a request carrying
|
|
115
|
+
the fresh-reads cookie), `hint=<shell tags hinted>/<tags hinted>` with `lead=` (how long the
|
|
116
|
+
hinted marker reads ran before the entry named its tags), and `fresh-reads`
|
|
117
|
+
when the request carried the cookie.
|
|
118
|
+
- `records=` counts the snapshot records the tail was seeded with, by
|
|
119
|
+
family; `pruned=` counts the ones the capture dropped because no reader of
|
|
120
|
+
the entry consumes them. Here every HIT tail replays the handler layer from
|
|
121
|
+
the one segment record, so the five `"use cache"` item records that produced
|
|
122
|
+
it were not stored.
|
|
123
|
+
- `snapshot=`, `seed=`, `match=`, `first-html=` are offsets from the commit,
|
|
124
|
+
not durations; `snapshot-read=`, `snapshot-parse-cpu=` and `seed-cpu=` are
|
|
125
|
+
durations.
|
|
126
|
+
- On a deployed worker the clock only moves on I/O, so the CPU-only numbers
|
|
127
|
+
(`ppr:shell-open`, `ppr:shell-commit`, `snapshot-parse-cpu`, `seed-cpu`)
|
|
128
|
+
read 0 there; the byte counts are the cost to watch.
|
|
129
|
+
|
|
130
|
+
The console waterfall uses these labels as written. In the `Server-Timing`
|
|
131
|
+
header, colons become hyphens and other non-alphanumeric characters are
|
|
132
|
+
dropped (`ssr:render-html` → `ssr-render-html`, `handler:total` →
|
|
133
|
+
`handler-total`), and nested rows get a `d<depth>-` prefix. The full metric
|
|
134
|
+
list is in the repository's `packages/rangojs-router/docs/telemetry.md`.
|
|
60
135
|
|
|
61
136
|
**Deployed Cloudflare caveat**: on production Workers, timers are frozen
|
|
62
137
|
during request execution (Spectre mitigation), so `Server-Timing` durations
|
|
@@ -106,7 +181,9 @@ const router = createRouter({
|
|
|
106
181
|
On **Cloudflare Workers**, use `createCloudflareTracing` for the `tracing` slot
|
|
107
182
|
instead — it emits the same phases as native Cloudflare custom spans (in the
|
|
108
183
|
Workers trace waterfall, next to the automatic KV/D1/fetch spans), with no
|
|
109
|
-
`@opentelemetry/api` dependency
|
|
184
|
+
`@opentelemetry/api` dependency. It reads the tracer from the `ctx` your Worker
|
|
185
|
+
passes to `router.fetch(request, { env, ctx })`, and is a pass-through when
|
|
186
|
+
Workers tracing is not enabled for the Worker:
|
|
110
187
|
|
|
111
188
|
```typescript
|
|
112
189
|
import { createRouter } from "@rangojs/router";
|
|
@@ -226,6 +303,7 @@ Then inspect:
|
|
|
226
303
|
|
|
227
304
|
## Zero-overhead defaults
|
|
228
305
|
|
|
229
|
-
`debugPerformance` is off by default,
|
|
230
|
-
is configured
|
|
231
|
-
|
|
306
|
+
`debugPerformance` is off by default, `telemetry` emits nothing unless a sink
|
|
307
|
+
is configured, and with `tracing` unset every span call runs the work directly.
|
|
308
|
+
Per-request `ctx.debugPerformance()` lets you turn on the waterfall only for the
|
|
309
|
+
route, user, or query param you are investigating.
|