@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
package/skills/route/SKILL.md
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: route
|
|
3
|
-
description: Define routes with path() in @rangojs/router. Use when creating a new page or route, or asking how to define a URL path and its handler.
|
|
3
|
+
description: Define routes with path() in @rangojs/router — URL params, catch-alls, typed search params, the handler context, handler-to-child data, and redirects. Use when creating a new page or route, or asking how to define a URL path and its handler.
|
|
4
4
|
argument-hint: [pattern]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Defining Routes with path()
|
|
8
8
|
|
|
9
|
+
`path(pattern, handler, options?, use?)` maps a URL pattern to a handler inside
|
|
10
|
+
`urls()`. This skill covers patterns and params, typed search params, the handler
|
|
11
|
+
context, passing handler data to child segments, and redirects.
|
|
12
|
+
|
|
13
|
+
Related skills: `/layout` (wrap routes in shared UI), `/response-routes`
|
|
14
|
+
(`path.json()`, `path.text()`, … endpoints), `/links` (URL generation),
|
|
15
|
+
`/composability` (`include()` and reusable config), `/loader` (data).
|
|
16
|
+
|
|
9
17
|
## Basic Route
|
|
10
18
|
|
|
11
19
|
```typescript
|
|
@@ -57,7 +65,8 @@ see `/i18n`.
|
|
|
57
65
|
|
|
58
66
|
A catch-all consumes the **rest of the path** and exposes it as a single
|
|
59
67
|
decoded string at `ctx.params.<name>`, with the internal `/` separators kept.
|
|
60
|
-
It
|
|
68
|
+
It only acts as a catch-all in the **last** segment of the pattern (see the
|
|
69
|
+
parsing rule at the end of this section).
|
|
61
70
|
|
|
62
71
|
- `:name+` — **one-or-more** segments (Next `[...name]`, React-Router splat).
|
|
63
72
|
`/docs/:slug+` matches `/docs/a` and `/docs/a/b/c`, but **not** the bare
|
|
@@ -81,9 +90,9 @@ urls(({ path }) => [
|
|
|
81
90
|
```
|
|
82
91
|
|
|
83
92
|
`ctx.params.<name>` is always a `string` for a catch-all (never `undefined`) —
|
|
84
|
-
`:name*` binds `""` for the empty case, so read it directly.
|
|
85
|
-
`ctx.reverse()`
|
|
86
|
-
`reverse("docs", { slug: "a/b" })` -> `/docs/a/b`.
|
|
93
|
+
`:name*` binds `""` for the empty case, so read it directly. URL generation
|
|
94
|
+
(`ctx.reverse()`, `href()`) rebuilds the URL with separators preserved:
|
|
95
|
+
`ctx.reverse("docs", { slug: "a/b" })` -> `/docs/a/b`.
|
|
87
96
|
|
|
88
97
|
The value is the URL-decoded remainder. `split("/")` recovers the segments in the
|
|
89
98
|
common case, but note that a segment containing an encoded slash (`%2F`) decodes
|
|
@@ -93,8 +102,27 @@ raw pathname instead.
|
|
|
93
102
|
|
|
94
103
|
The bare unnamed wildcard `path("/files/*", …)` still works and is read at
|
|
95
104
|
`ctx.params["*"]`; prefer a named catch-all when you want a typed param key.
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
|
|
106
|
+
Parsing rule: `+` / `*` is a catch-all modifier **only** when it is the bare
|
|
107
|
+
final character of the last segment. Anywhere else it is read as a literal
|
|
108
|
+
suffix character, not rejected: `/docs/:slug+/edit` matches `/docs/x+/edit`,
|
|
109
|
+
and `:slug*.html`, `:slug?*`, and `:slug(a|b)+` are ordinary params followed by
|
|
110
|
+
a literal suffix.
|
|
111
|
+
|
|
112
|
+
### Constrained and suffixed params
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
urls(({ path }) => [
|
|
116
|
+
// Only /en/about or /gb/about match; ctx.params.locale is typed "en" | "gb"
|
|
117
|
+
path("/:locale(en|gb)/about", AboutPage, { name: "about" }),
|
|
118
|
+
|
|
119
|
+
// Constrained + optional: /pricing, /en/pricing, /gb/pricing
|
|
120
|
+
path("/:locale(en|gb)?/pricing", PricingPage, { name: "pricing" }),
|
|
121
|
+
|
|
122
|
+
// Literal suffix: /files/report.pdf -> ctx.params.name === "report"
|
|
123
|
+
path("/files/:name.pdf", PdfViewer, { name: "pdf" }),
|
|
124
|
+
]);
|
|
125
|
+
```
|
|
98
126
|
|
|
99
127
|
## Route Handler Patterns
|
|
100
128
|
|
|
@@ -135,12 +163,25 @@ path("/product/:slug", async (ctx) => {
|
|
|
135
163
|
|
|
136
164
|
## Route Options
|
|
137
165
|
|
|
166
|
+
All options are optional; with none, pass the `use` callback as the third
|
|
167
|
+
argument: `path("/x", Page, () => [loader(L)])`.
|
|
168
|
+
|
|
138
169
|
```typescript
|
|
139
170
|
path("/product/:slug", ProductPage, {
|
|
140
|
-
name: "product", //
|
|
171
|
+
name: "product", // route name for href(), ctx.reverse(), Handler<"product">
|
|
172
|
+
search: { tab: "string?" }, // typed ctx.search (next section)
|
|
173
|
+
trailingSlash: "never", // "never" | "always" | "ignore"
|
|
174
|
+
ppr: true, // serve a cached HTML shell with live holes (see /ppr)
|
|
141
175
|
});
|
|
142
176
|
```
|
|
143
177
|
|
|
178
|
+
- `name` — unnamed routes still match but can't be reversed by name or typed
|
|
179
|
+
with `Handler<"name">`. Inside an `include()`, the include's `name` option
|
|
180
|
+
decides whether the name is visible globally (see "Nested Routes" below).
|
|
181
|
+
- `trailingSlash` — `"never"` redirects `/docs/` to `/docs`, `"always"` redirects `/docs`
|
|
182
|
+
to `/docs/`, `"ignore"` matches both. Unset, the pattern's own trailing slash
|
|
183
|
+
decides.
|
|
184
|
+
|
|
144
185
|
### Typed Search Params
|
|
145
186
|
|
|
146
187
|
Add a `search` schema to get typed `ctx.search`:
|
|
@@ -158,24 +199,31 @@ Use `Handler<"name">` for typed search params (resolves from the generated route
|
|
|
158
199
|
import type { Handler } from "@rangojs/router";
|
|
159
200
|
|
|
160
201
|
export const SearchPage: Handler<"search"> = (ctx) => {
|
|
161
|
-
// ctx.search is typed: { q: string; page?: number; sort?: string }
|
|
162
|
-
const { q, page, sort } = ctx.search;
|
|
202
|
+
// ctx.search is typed: { q: string | undefined; page?: number; sort?: string }
|
|
203
|
+
const { q = "", page = 1, sort } = ctx.search;
|
|
163
204
|
// ctx.searchParams is always URLSearchParams
|
|
164
205
|
return <SearchResults q={q} page={page} sort={sort} />;
|
|
165
206
|
};
|
|
166
207
|
```
|
|
167
208
|
|
|
168
209
|
Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
|
|
169
|
-
|
|
170
|
-
|
|
210
|
+
|
|
211
|
+
- **Missing params are `undefined` regardless of required/optional** — a client
|
|
212
|
+
can always omit them, so the handler must default or check. The
|
|
213
|
+
required/optional distinction is a consumer-facing contract that drives
|
|
214
|
+
`href()` / `ctx.reverse()` autocomplete.
|
|
215
|
+
- `"number"` accepts decimal numerals (`42`, `-3.5`, `1e3`). Empty values, hex
|
|
216
|
+
(`0x10`), and non-finite values are treated as missing, not coerced.
|
|
217
|
+
- `"boolean"` is `true` for `"true"` / `"1"` and `false` for any other present
|
|
218
|
+
value.
|
|
171
219
|
|
|
172
220
|
Use `RouteSearchParams<"name">` and `RouteParams<"name">` to extract types for props:
|
|
173
221
|
|
|
174
222
|
```typescript
|
|
175
223
|
import type { RouteSearchParams, RouteParams } from "@rangojs/router";
|
|
176
224
|
|
|
177
|
-
type SP = RouteSearchParams<"search">; // { q: string; page?: number; sort?: string }
|
|
178
|
-
type P = RouteParams<"blogPost">; // { slug: string }
|
|
225
|
+
type SP = RouteSearchParams<"search">; // { q: string | undefined; page?: number; sort?: string }
|
|
226
|
+
type P = RouteParams<"blogPost">; // { year: string; month: string; slug: string }
|
|
179
227
|
```
|
|
180
228
|
|
|
181
229
|
## Route Children
|
|
@@ -194,15 +242,13 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
|
194
242
|
|
|
195
243
|
When a route has children (orphan layouts, parallels), the handler executes
|
|
196
244
|
first. Use `ctx.set(key, value)` to share data with children, who read it
|
|
197
|
-
via `ctx.get(key)`.
|
|
198
|
-
|
|
245
|
+
via `ctx.get(key)`. A route-level `cache()` wraps all of the entry's segments
|
|
246
|
+
together, so on a cache hit none of them run and on a miss all of them do.
|
|
199
247
|
|
|
200
|
-
This
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
stays consistent with no configuration. See `/rango` → "Passing data down
|
|
205
|
-
the tree" for the safest-first ladder.
|
|
248
|
+
This stays consistent after actions with no configuration: on an action, the
|
|
249
|
+
route segment, its loaders, and the children declared inside the `path()`
|
|
250
|
+
(orphan layouts and their parallels) all re-run together, handler first. See
|
|
251
|
+
`/rango` → "Passing data down the tree" for the safest-first ladder.
|
|
206
252
|
|
|
207
253
|
### Typed context variables with createVar
|
|
208
254
|
|
|
@@ -248,8 +294,10 @@ path("/dashboard/:id", async (ctx) => {
|
|
|
248
294
|
String keys still work (`ctx.set("key", value)` / `ctx.get("key")`), but
|
|
249
295
|
`createVar<T>()` is preferred for type safety.
|
|
250
296
|
|
|
251
|
-
|
|
252
|
-
|
|
297
|
+
Write with `ctx.set()` from middleware, route handlers, and layout handlers;
|
|
298
|
+
read with `ctx.get()` in the segments they wrap. Handlers run before their
|
|
299
|
+
orphan layouts and parallels (handler-first), so those children see the value.
|
|
300
|
+
Parallels and intercepts render last, so treat them as readers.
|
|
253
301
|
|
|
254
302
|
#### Non-cacheable context variables
|
|
255
303
|
|
|
@@ -268,19 +316,25 @@ ctx.set(Theme, userTheme, { cache: false });
|
|
|
268
316
|
"Least cacheable wins" — if either the var definition or the write site says
|
|
269
317
|
`cache: false`, the value is non-cacheable.
|
|
270
318
|
|
|
271
|
-
Reading a non-cacheable var
|
|
272
|
-
runtime
|
|
319
|
+
Reading a non-cacheable var directly with `ctx.get()` inside a `cache()`
|
|
320
|
+
boundary throws at runtime, so request-specific data can't be baked into a
|
|
321
|
+
cached segment:
|
|
273
322
|
|
|
274
323
|
```typescript
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
324
|
+
cache({ ttl: 60 }, () => [
|
|
325
|
+
path("/account", (ctx) => {
|
|
326
|
+
const session = ctx.get(Session); // throws: non-cacheable read inside cache()
|
|
327
|
+
return <Account session={session} />;
|
|
328
|
+
}, { name: "account" }),
|
|
329
|
+
]);
|
|
281
330
|
```
|
|
282
331
|
|
|
283
|
-
|
|
332
|
+
The same read throws inside a `"use cache"` function body, where the value
|
|
333
|
+
would be stored without being part of the key — pass it in as an argument
|
|
334
|
+
instead. The guard does not follow derived values (a string copied out of
|
|
335
|
+
`Session` and cached elsewhere is not tracked). Loaders are exempt — they run
|
|
336
|
+
fresh on every request — but a `"use cache"` function a loader calls is not. Cacheable vars (the default) can be read freely
|
|
337
|
+
inside cache scopes. See `/cache-guide` → "Context Variable Cache Safety".
|
|
284
338
|
|
|
285
339
|
### Revalidation Contracts for Handler Data
|
|
286
340
|
|
|
@@ -295,7 +349,8 @@ the route handler and its children re-run together by default, so handler
|
|
|
295
349
|
data stays consistent on its own. Contracts matter in two cases:
|
|
296
350
|
|
|
297
351
|
1. **You narrow the entry's revalidation** with a predicate that can return a
|
|
298
|
-
hard `false` (e.g.
|
|
352
|
+
hard `false` (e.g. `ctx.isAction() ? ctx.isAction(X) : undefined`). A hard
|
|
353
|
+
`false` on one side of a
|
|
299
354
|
producer/consumer pair desyncs it — the child re-runs by default and reads
|
|
300
355
|
`undefined`, or vice versa. Put the same named contract on the route and
|
|
301
356
|
its dependent children so they narrow together.
|
|
@@ -305,17 +360,20 @@ data stays consistent on its own. Contracts matter in two cases:
|
|
|
305
360
|
|
|
306
361
|
```typescript
|
|
307
362
|
// revalidation-contracts.ts
|
|
363
|
+
import type { Revalidate } from "@rangojs/router";
|
|
308
364
|
import * as CheckoutActions from "./actions/checkout";
|
|
309
365
|
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
366
|
+
// The route and its children re-run after every action by default, so this
|
|
367
|
+
// contract narrows them together: after an action, re-run only for checkout
|
|
368
|
+
// actions; on navigation, undefined keeps the default. After an action it is a
|
|
369
|
+
// hard decision, so later revalidators on the same segment do not run.
|
|
370
|
+
export const revalidateCheckoutData: Revalidate = (ctx) =>
|
|
371
|
+
ctx.isAction() ? ctx.isAction(CheckoutActions) : undefined;
|
|
314
372
|
|
|
315
373
|
path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
316
|
-
revalidate(revalidateCheckoutData), // producer (route handler)
|
|
374
|
+
revalidate(revalidateCheckoutData), // producer (route handler)
|
|
317
375
|
layout(CheckoutLayout, () => [
|
|
318
|
-
revalidate(revalidateCheckoutData), // consumer
|
|
376
|
+
revalidate(revalidateCheckoutData), // consumer
|
|
319
377
|
parallel({ "@summary": CheckoutSummary }, () => [
|
|
320
378
|
revalidate(revalidateCheckoutData),
|
|
321
379
|
]),
|
|
@@ -323,8 +381,10 @@ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
|
323
381
|
]);
|
|
324
382
|
```
|
|
325
383
|
|
|
326
|
-
If children depend on multiple upstream domains,
|
|
327
|
-
|
|
384
|
+
If children depend on multiple upstream domains, match them in one narrowing
|
|
385
|
+
contract (`ctx.isAction(CheckoutActions, AuthActions)`): a second narrowing
|
|
386
|
+
contract on the same segment would never run, because the first one's hard
|
|
387
|
+
`false` ends the chain.
|
|
328
388
|
|
|
329
389
|
For cleaner route trees, expose contract helpers and spread them:
|
|
330
390
|
|
|
@@ -341,6 +401,9 @@ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
|
341
401
|
|
|
342
402
|
## Redirects
|
|
343
403
|
|
|
404
|
+
`redirect(url, statusOrOptions?)` returns a `Response` (default status `302`).
|
|
405
|
+
Return it from a handler or middleware, or throw it from a loader.
|
|
406
|
+
|
|
344
407
|
### Basic redirect
|
|
345
408
|
|
|
346
409
|
```typescript
|
|
@@ -355,6 +418,22 @@ path("/old-page", () => redirect("/new-page"), { name: "oldPage" });
|
|
|
355
418
|
path("/moved", () => redirect("/new-location", 301), { name: "moved" });
|
|
356
419
|
```
|
|
357
420
|
|
|
421
|
+
Root-relative targets are prefixed with the router's `basename` automatically
|
|
422
|
+
(an already-prefixed URL is left alone).
|
|
423
|
+
|
|
424
|
+
### Off-origin redirects
|
|
425
|
+
|
|
426
|
+
Cross-origin targets are blocked by a same-origin guard and replaced with the
|
|
427
|
+
app root. Opt in explicitly for an intended off-host redirect:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
path(
|
|
431
|
+
"/login",
|
|
432
|
+
() => redirect("https://accounts.example.com/oauth", { external: true }),
|
|
433
|
+
{ name: "login" },
|
|
434
|
+
);
|
|
435
|
+
```
|
|
436
|
+
|
|
358
437
|
> **Redirecting from a route with `loading()`:** an `async` handler that returns
|
|
359
438
|
> a `Response`/`redirect()` on a route that also declares `loading()` is streamed,
|
|
360
439
|
> so the redirect is rendered into the RSC stream instead of becoming an HTTP
|
|
@@ -399,7 +478,8 @@ path(
|
|
|
399
478
|
);
|
|
400
479
|
```
|
|
401
480
|
|
|
402
|
-
Read
|
|
481
|
+
`state` takes one entry or an array. Read it on the target page with
|
|
482
|
+
`useLocationState(FlashMessage)` (from `@rangojs/router/client`). The
|
|
403
483
|
`{ flash: true }` option makes it auto-clear. Without `{ flash: true }`,
|
|
404
484
|
state persists on back/forward. See `/hooks` for details.
|
|
405
485
|
|
|
@@ -426,24 +506,44 @@ State flows to the browser via the RSC payload and is merged into
|
|
|
426
506
|
Every handler receives a context object:
|
|
427
507
|
|
|
428
508
|
```typescript
|
|
509
|
+
// Simplified sketch. Import the real type with
|
|
510
|
+
// `import type { HandlerContext } from "@rangojs/router"`.
|
|
429
511
|
interface HandlerContext<TParams = {}, TEnv = DefaultEnv, TSearch = {}> {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
512
|
+
// Request
|
|
513
|
+
params: TParams; // URL params
|
|
514
|
+
request: Request; // original request (raw transport URL, headers, body)
|
|
515
|
+
url: URL; // request URL with internal _rsc* params stripped
|
|
516
|
+
pathname: string;
|
|
517
|
+
searchParams: URLSearchParams; // always URLSearchParams (_rsc* stripped)
|
|
518
|
+
search: ResolveSearchSchema<TSearch>; // typed from the search schema ({} without one)
|
|
519
|
+
env: TEnv; // platform bindings
|
|
520
|
+
routeName?: string; // matched route name (undefined for unnamed routes)
|
|
521
|
+
|
|
522
|
+
// Data flow
|
|
523
|
+
get<T>(contextVar: ContextVar<T>): T | undefined; // also accepts a string key
|
|
524
|
+
set<T>(
|
|
525
|
+
contextVar: ContextVar<T>,
|
|
526
|
+
value: T,
|
|
527
|
+
options?: { cache?: boolean },
|
|
528
|
+
): void;
|
|
529
|
+
use<T>(loader: LoaderDefinition<T>): Promise<T>; // loader data (memoized per request)
|
|
530
|
+
use<T>(handle: Handle<T>): HandlePush<T>; // push function for a handle
|
|
531
|
+
|
|
532
|
+
// Response
|
|
533
|
+
headers: Headers; // response headers, merged into the final response
|
|
534
|
+
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
441
535
|
reverse(
|
|
442
536
|
name: string,
|
|
443
537
|
params?: Record<string, string>,
|
|
444
538
|
search?: Record<string, unknown>,
|
|
445
|
-
): string;
|
|
446
|
-
|
|
539
|
+
): string;
|
|
540
|
+
waitUntil(fn: () => Promise<void>): void; // work that continues after the response
|
|
541
|
+
|
|
542
|
+
// Mode
|
|
543
|
+
build: boolean; // true while pre-rendering at build time
|
|
544
|
+
dev: boolean; // true under Vite dev
|
|
545
|
+
dynamic(): void; // opt this request out of PPR shell capture (see /ppr)
|
|
546
|
+
theme?: Theme; // plus setTheme?() — only when the router enables themes (/theme)
|
|
447
547
|
}
|
|
448
548
|
```
|
|
449
549
|
|
|
@@ -460,9 +560,12 @@ path("/product/:slug", (ctx) => {
|
|
|
460
560
|
// Access platform bindings
|
|
461
561
|
const db = ctx.env.DB;
|
|
462
562
|
|
|
463
|
-
//
|
|
464
|
-
const
|
|
465
|
-
|
|
563
|
+
// Push handle data: ctx.use(Handle) returns a push function
|
|
564
|
+
const pushCrumb = ctx.use(Breadcrumbs);
|
|
565
|
+
pushCrumb({ label: "Product", href: `/product/${slug}` });
|
|
566
|
+
|
|
567
|
+
// Set a response header
|
|
568
|
+
ctx.headers.set("Cache-Control", "private, max-age=60");
|
|
466
569
|
|
|
467
570
|
return <ProductPage slug={slug} tab={tab} />;
|
|
468
571
|
}, { name: "product" })
|
|
@@ -505,6 +608,8 @@ A route can configure its own `transition()` — the wrap goes around the route'
|
|
|
505
608
|
Page handlers can carry their own loader, middleware, error boundaries, parallels, and other defaults via a `.use` callback — so the page is self-contained and reusable across mount sites without re-wiring the same items.
|
|
506
609
|
|
|
507
610
|
```typescript
|
|
611
|
+
import { loader, loading, middleware, type Handler } from "@rangojs/router";
|
|
612
|
+
|
|
508
613
|
const ProductPage: Handler<"/product/:slug"> = async (ctx) => {
|
|
509
614
|
const product = await ctx.use(ProductLoader);
|
|
510
615
|
return <ProductView product={product} />;
|
|
@@ -514,7 +619,7 @@ ProductPage.use = () => [
|
|
|
514
619
|
loading(<ProductSkeleton />),
|
|
515
620
|
middleware(async (ctx, next) => {
|
|
516
621
|
await next();
|
|
517
|
-
ctx.
|
|
622
|
+
ctx.headers.set("Cache-Control", "private, max-age=60");
|
|
518
623
|
}),
|
|
519
624
|
];
|
|
520
625
|
|