@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.30
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/AGENTS.md +5 -0
- package/README.md +883 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4655 -747
- package/package.json +78 -50
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +54 -25
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +23 -21
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +390 -63
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +133 -10
- package/skills/layout/SKILL.md +102 -5
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +366 -29
- package/skills/middleware/SKILL.md +173 -36
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +80 -3
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +86 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +227 -14
- package/skills/router-setup/SKILL.md +225 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +12 -11
- package/skills/typesafety/SKILL.md +401 -75
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +10 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +20 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +201 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +267 -317
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +173 -73
- package/src/browser/react/NavigationProvider.tsx +138 -27
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +49 -65
- package/src/browser/react/use-href.tsx +20 -188
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +27 -78
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +111 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +504 -584
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +92 -57
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +469 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +10 -15
- package/src/client.tsx +114 -135
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +34 -19
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +135 -49
- package/src/index.rsc.ts +182 -17
- package/src/index.ts +238 -24
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +27 -142
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +9 -11
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1388
- package/src/route-map-builder.ts +241 -112
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +70 -9
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +371 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +155 -32
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -29
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +321 -30
- package/src/router/prerender-match.ts +400 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1241 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +77 -3
- package/src/router.ts +688 -3656
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +786 -760
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +5 -25
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +40 -14
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +57 -61
- package/src/server/context.ts +202 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +422 -70
- package/src/server.ts +36 -120
- package/src/ssr/index.tsx +157 -26
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1577
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -726
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -782
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -3
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -357
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/skills/route/SKILL.md
CHANGED
|
@@ -9,7 +9,7 @@ argument-hint: [pattern]
|
|
|
9
9
|
## Basic Route
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
|
-
import { urls } from "@rangojs/router
|
|
12
|
+
import { urls } from "@rangojs/router";
|
|
13
13
|
|
|
14
14
|
export const urlpatterns = urls(({ path }) => [
|
|
15
15
|
path("/", HomePage, { name: "home" }),
|
|
@@ -74,8 +74,45 @@ path("/product/:slug", async (ctx) => {
|
|
|
74
74
|
|
|
75
75
|
```typescript
|
|
76
76
|
path("/product/:slug", ProductPage, {
|
|
77
|
-
name: "product",
|
|
78
|
-
})
|
|
77
|
+
name: "product", // Route name for href() and navigation
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Typed Search Params
|
|
82
|
+
|
|
83
|
+
Add a `search` schema to get typed `ctx.search`:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
path("/search", SearchPage, {
|
|
87
|
+
name: "search",
|
|
88
|
+
search: { q: "string", page: "number?", sort: "string?" },
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Use `Handler<"name">` for typed search params (resolves from the generated route map automatically):
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import type { Handler } from "@rangojs/router";
|
|
96
|
+
|
|
97
|
+
export const SearchPage: Handler<"search"> = (ctx) => {
|
|
98
|
+
// ctx.search is typed: { q: string; page?: number; sort?: string }
|
|
99
|
+
const { q, page, sort } = ctx.search;
|
|
100
|
+
// ctx.searchParams is always URLSearchParams
|
|
101
|
+
return <SearchResults q={q} page={page} sort={sort} />;
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
|
|
106
|
+
Missing params are `undefined` regardless of required/optional. The required/optional
|
|
107
|
+
distinction is a consumer-facing contract (for `href()` and `reverse()` autocomplete).
|
|
108
|
+
|
|
109
|
+
Use `RouteSearchParams<"name">` and `RouteParams<"name">` to extract types for props:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import type { RouteSearchParams, RouteParams } from "@rangojs/router";
|
|
113
|
+
|
|
114
|
+
type SP = RouteSearchParams<"search">; // { q: string; page?: number; sort?: string }
|
|
115
|
+
type P = RouteParams<"blogPost">; // { slug: string }
|
|
79
116
|
```
|
|
80
117
|
|
|
81
118
|
## Route Children
|
|
@@ -90,17 +127,193 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
|
90
127
|
])
|
|
91
128
|
```
|
|
92
129
|
|
|
130
|
+
## Handler Data Ownership
|
|
131
|
+
|
|
132
|
+
When a route has children (orphan layouts, parallels), the handler executes
|
|
133
|
+
first. Use `ctx.set(key, value)` to share data with children, who read it
|
|
134
|
+
via `ctx.get(key)`. Caching wraps all segments together, so either all run
|
|
135
|
+
or none do.
|
|
136
|
+
|
|
137
|
+
### Typed context variables with createVar
|
|
138
|
+
|
|
139
|
+
Use `createVar<T>()` to create a typed token for `ctx.set()`/`ctx.get()`.
|
|
140
|
+
The token is imported by both the handler (producer) and layout (consumer),
|
|
141
|
+
making the data contract explicit and compile-time verified:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { createVar } from "@rangojs/router";
|
|
145
|
+
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
146
|
+
|
|
147
|
+
// Typed token -- shared between handler and layout
|
|
148
|
+
interface DashboardData {
|
|
149
|
+
title: string;
|
|
150
|
+
stats: { views: number };
|
|
151
|
+
}
|
|
152
|
+
const Dashboard = createVar<DashboardData>();
|
|
153
|
+
|
|
154
|
+
path("/dashboard/:id", async (ctx) => {
|
|
155
|
+
const data = await fetchDashboard(ctx.params.id);
|
|
156
|
+
ctx.set(Dashboard, data); // type-checked
|
|
157
|
+
return <DashboardPage data={data} />;
|
|
158
|
+
}, { name: "dashboard" }, () => [
|
|
159
|
+
layout((ctx) => {
|
|
160
|
+
const data = ctx.get(Dashboard); // typed as DashboardData | undefined
|
|
161
|
+
return (
|
|
162
|
+
<div>
|
|
163
|
+
<h1>{data?.title}</h1>
|
|
164
|
+
<Outlet />
|
|
165
|
+
<ParallelOutlet name="@sidebar" />
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}),
|
|
169
|
+
parallel({
|
|
170
|
+
"@sidebar": (ctx) => {
|
|
171
|
+
const data = ctx.get(Dashboard);
|
|
172
|
+
return <Sidebar stats={data?.stats} />;
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
])
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
String keys still work (`ctx.set("key", value)` / `ctx.get("key")`), but
|
|
179
|
+
`createVar<T>()` is preferred for type safety.
|
|
180
|
+
|
|
181
|
+
Only route handlers and middleware can call `ctx.set()`. Layouts, parallels,
|
|
182
|
+
and intercepts can only read via `ctx.get()`.
|
|
183
|
+
|
|
184
|
+
### Revalidation Contracts for Handler Data
|
|
185
|
+
|
|
186
|
+
Handler-first guarantees apply within a single full render pass. For partial
|
|
187
|
+
action revalidation, define named revalidation contracts and reuse them on both
|
|
188
|
+
the producer route and the consumer child segments.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// revalidation-contracts.ts
|
|
192
|
+
export const revalidateCheckoutData = ({ actionId }) =>
|
|
193
|
+
actionId?.includes("src/actions/checkout.ts#") ?? false;
|
|
194
|
+
|
|
195
|
+
path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
196
|
+
revalidate(revalidateCheckoutData), // producer (route handler) reruns
|
|
197
|
+
layout(CheckoutLayout, () => [
|
|
198
|
+
revalidate(revalidateCheckoutData), // consumer reruns
|
|
199
|
+
parallel({ "@summary": CheckoutSummary }, () => [
|
|
200
|
+
revalidate(revalidateCheckoutData),
|
|
201
|
+
]),
|
|
202
|
+
]),
|
|
203
|
+
]);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
If children depend on multiple upstream domains, compose multiple contracts on
|
|
207
|
+
the same segment (`revalidateAuthData`, `revalidateCheckoutData`, and so on).
|
|
208
|
+
|
|
209
|
+
For cleaner route trees, expose contract helpers and spread them:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { revalidate } from "@rangojs/router";
|
|
213
|
+
|
|
214
|
+
export const revalidateCheckout = () => [revalidate(revalidateCheckoutData)];
|
|
215
|
+
|
|
216
|
+
path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
217
|
+
revalidateCheckout(),
|
|
218
|
+
layout(CheckoutLayout, () => [revalidateCheckout()]),
|
|
219
|
+
]);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
For scope/revalidation guarantees and non-guarantees, see:
|
|
223
|
+
[docs/execution-model.md](../../docs/internal/execution-model.md)
|
|
224
|
+
|
|
225
|
+
## Redirects
|
|
226
|
+
|
|
227
|
+
### Basic redirect
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { redirect } from "@rangojs/router";
|
|
231
|
+
|
|
232
|
+
path("/old-page", () => redirect("/new-page"), { name: "oldPage" });
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Redirect with custom status
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
path("/moved", () => redirect("/new-location", 301), { name: "moved" });
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Redirect with location state
|
|
242
|
+
|
|
243
|
+
Carry typed state through redirects (e.g. flash messages):
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { redirect, createLocationState } from "@rangojs/router";
|
|
247
|
+
|
|
248
|
+
export const FlashMessage = createLocationState<{ text: string }>({
|
|
249
|
+
flash: true,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
path(
|
|
253
|
+
"/save",
|
|
254
|
+
(ctx) => {
|
|
255
|
+
// ... save logic
|
|
256
|
+
return redirect("/dashboard", {
|
|
257
|
+
state: [FlashMessage({ text: "Item saved!" })],
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
{ name: "save" },
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// With custom status + state
|
|
264
|
+
path(
|
|
265
|
+
"/action",
|
|
266
|
+
(ctx) => {
|
|
267
|
+
return redirect("/target", {
|
|
268
|
+
status: 303,
|
|
269
|
+
state: [FlashMessage({ text: "Action complete" })],
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
{ name: "action" },
|
|
273
|
+
);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Read the state on the target page with `useLocationState(FlashMessage)`. The
|
|
277
|
+
`{ flash: true }` option makes it auto-clear. Without `{ flash: true }`,
|
|
278
|
+
state persists on back/forward. See `/hooks` for details.
|
|
279
|
+
|
|
280
|
+
### ctx.setLocationState()
|
|
281
|
+
|
|
282
|
+
Attach location state to any server response (not just redirects):
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
path("/dashboard", (ctx) => {
|
|
286
|
+
ctx.setLocationState(ServerInfo({ data: "welcome" }));
|
|
287
|
+
return <Dashboard />;
|
|
288
|
+
}, { name: "dashboard" })
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
State flows to the browser via the RSC payload and is merged into
|
|
292
|
+
`history.pushState()`. Only works for SPA (partial) navigations.
|
|
293
|
+
|
|
93
294
|
## Handler Context
|
|
94
295
|
|
|
95
296
|
Every handler receives a context object:
|
|
96
297
|
|
|
97
298
|
```typescript
|
|
98
|
-
interface HandlerContext<TParams =
|
|
99
|
-
params: TParams;
|
|
100
|
-
request: Request;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
299
|
+
interface HandlerContext<TParams = {}, TEnv = DefaultEnv, TSearch = {}> {
|
|
300
|
+
params: TParams; // URL parameters
|
|
301
|
+
request: Request; // Original request
|
|
302
|
+
searchParams: URLSearchParams; // Query params (always URLSearchParams)
|
|
303
|
+
search: {} | ResolveSearchSchema<TSearch>; // Typed search params (from search schema)
|
|
304
|
+
url: URL; // Parsed URL
|
|
305
|
+
env: TEnv; // Environment (bindings + variables)
|
|
306
|
+
set(key: string, value: any): void; // Set context variable (untyped string key)
|
|
307
|
+
set<T>(contextVar: ContextVar<T>, value: T): void; // Set typed context variable
|
|
308
|
+
get(key: string): any; // Read context variable (untyped string key)
|
|
309
|
+
get<T>(contextVar: ContextVar<T>): T | undefined; // Read typed context variable
|
|
310
|
+
use<T>(handle: Handle<T>): T; // Access handles
|
|
311
|
+
reverse(
|
|
312
|
+
name: string,
|
|
313
|
+
params?: Record<string, string>,
|
|
314
|
+
search?: Record<string, unknown>,
|
|
315
|
+
): string; // URL generation
|
|
316
|
+
setLocationState(entries: LocationStateEntry[]): void; // Attach state to response
|
|
104
317
|
}
|
|
105
318
|
```
|
|
106
319
|
|
|
@@ -111,11 +324,11 @@ path("/product/:slug", (ctx) => {
|
|
|
111
324
|
// Access URL params
|
|
112
325
|
const { slug } = ctx.params;
|
|
113
326
|
|
|
114
|
-
// Access query params
|
|
115
|
-
const tab = ctx.
|
|
327
|
+
// Access query params (untyped - use search schema for typed access)
|
|
328
|
+
const tab = ctx.searchParams.get("tab");
|
|
116
329
|
|
|
117
|
-
// Access
|
|
118
|
-
const db = ctx.env.
|
|
330
|
+
// Access platform bindings
|
|
331
|
+
const db = ctx.env.DB;
|
|
119
332
|
|
|
120
333
|
// Access handles
|
|
121
334
|
const breadcrumbs = ctx.use(Breadcrumbs);
|
|
@@ -142,7 +355,7 @@ urls(({ path, layout }) => [
|
|
|
142
355
|
## Complete Example
|
|
143
356
|
|
|
144
357
|
```typescript
|
|
145
|
-
import { urls } from "@rangojs/router
|
|
358
|
+
import { urls } from "@rangojs/router";
|
|
146
359
|
import { Breadcrumbs } from "./handles/breadcrumbs";
|
|
147
360
|
|
|
148
361
|
export const urlpatterns = urls(({ path, layout, loader, loading }) => [
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: router-setup
|
|
3
|
-
description: Create and configure the RSC router with
|
|
3
|
+
description: Create and configure the RSC router with createRouter
|
|
4
4
|
argument-hint: [option]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Router Setup with
|
|
7
|
+
# Router Setup with createRouter
|
|
8
8
|
|
|
9
9
|
## Basic Router Creation
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
12
|
// src/router.tsx
|
|
13
|
-
import {
|
|
13
|
+
import { createRouter } from "@rangojs/router";
|
|
14
14
|
import { Document } from "./document";
|
|
15
15
|
import { urlpatterns } from "./urls";
|
|
16
16
|
|
|
17
|
-
const router =
|
|
17
|
+
const router = createRouter({
|
|
18
18
|
document: Document,
|
|
19
19
|
urls: urlpatterns,
|
|
20
20
|
});
|
|
@@ -26,7 +26,7 @@ export default router;
|
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
28
|
// src/urls.tsx
|
|
29
|
-
import { urls } from "@rangojs/router
|
|
29
|
+
import { urls } from "@rangojs/router";
|
|
30
30
|
import { HomePage } from "./pages/home";
|
|
31
31
|
import { AboutPage } from "./pages/about";
|
|
32
32
|
import { ProductPage } from "./pages/product";
|
|
@@ -50,20 +50,22 @@ export const urlpatterns = urls(({ path, layout, loader, loading }) => [
|
|
|
50
50
|
The `urls()` function provides a callback with all available DSL functions:
|
|
51
51
|
|
|
52
52
|
```typescript
|
|
53
|
-
urls(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
53
|
+
urls(
|
|
54
|
+
({
|
|
55
|
+
path, // Define a route
|
|
56
|
+
layout, // Wrap routes in a layout
|
|
57
|
+
parallel, // Define parallel routes (slots)
|
|
58
|
+
loader, // Add data loader
|
|
59
|
+
loading, // Add loading skeleton
|
|
60
|
+
cache, // Configure caching
|
|
61
|
+
middleware, // Add middleware
|
|
62
|
+
revalidate, // Control revalidation
|
|
63
|
+
intercept, // Intercept routes for modals
|
|
64
|
+
when, // Conditional rendering
|
|
65
|
+
}) => [
|
|
66
|
+
// Route definitions here
|
|
67
|
+
],
|
|
68
|
+
);
|
|
67
69
|
```
|
|
68
70
|
|
|
69
71
|
## Router Options
|
|
@@ -76,7 +78,7 @@ interface RSCRouterOptions<TEnv> {
|
|
|
76
78
|
// Document component wrapping entire app
|
|
77
79
|
document?: ComponentType<{ children: ReactNode }>;
|
|
78
80
|
|
|
79
|
-
// Enable performance
|
|
81
|
+
// Enable per-request performance timeline (console waterfall + Server-Timing header)
|
|
80
82
|
debugPerformance?: boolean;
|
|
81
83
|
|
|
82
84
|
// Default error boundary
|
|
@@ -93,9 +95,57 @@ interface RSCRouterOptions<TEnv> {
|
|
|
93
95
|
|
|
94
96
|
// Global cache configuration
|
|
95
97
|
cache?: CacheConfig<TEnv>;
|
|
98
|
+
|
|
99
|
+
// Theme configuration
|
|
100
|
+
theme?: ThemeConfig | true;
|
|
101
|
+
|
|
102
|
+
// SSR options (streaming policy)
|
|
103
|
+
ssr?: SSROptions<TEnv>;
|
|
104
|
+
|
|
105
|
+
// Telemetry sink for structured lifecycle events
|
|
106
|
+
telemetry?: TelemetrySink;
|
|
107
|
+
|
|
108
|
+
// Connection warmup (default: true)
|
|
109
|
+
warmup?: boolean;
|
|
110
|
+
|
|
111
|
+
// Prefetch cache TTL in seconds (default: 300)
|
|
112
|
+
// Controls in-memory cache duration and Cache-Control max-age for prefetch responses.
|
|
113
|
+
// Set to false to disable prefetch caching.
|
|
114
|
+
prefetchCacheTTL?: number | false;
|
|
115
|
+
|
|
116
|
+
// CSP nonce provider (for router.fetch)
|
|
117
|
+
nonce?: (
|
|
118
|
+
request: Request,
|
|
119
|
+
env: TEnv,
|
|
120
|
+
) => string | true | Promise<string | true>;
|
|
121
|
+
|
|
122
|
+
// RSC version string (for router.fetch)
|
|
123
|
+
version?: string;
|
|
96
124
|
}
|
|
97
125
|
```
|
|
98
126
|
|
|
127
|
+
## Using the Request Handler
|
|
128
|
+
|
|
129
|
+
The router provides a `fetch` method to handle RSC requests:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// src/router.tsx
|
|
133
|
+
import { createRouter } from "@rangojs/router";
|
|
134
|
+
import { Document } from "./document";
|
|
135
|
+
import { urlpatterns } from "./urls";
|
|
136
|
+
|
|
137
|
+
export const router = createRouter({
|
|
138
|
+
document: Document,
|
|
139
|
+
urls: urlpatterns,
|
|
140
|
+
nonce: () => true, // Auto-generate nonce for CSP
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// src/worker.tsx (Cloudflare Workers)
|
|
144
|
+
import { router } from "./router";
|
|
145
|
+
|
|
146
|
+
export default { fetch: router.fetch };
|
|
147
|
+
```
|
|
148
|
+
|
|
99
149
|
## Document Component
|
|
100
150
|
|
|
101
151
|
```typescript
|
|
@@ -121,12 +171,50 @@ export function Document({ children }: { children: ReactNode }) {
|
|
|
121
171
|
## Using with Cloudflare Workers
|
|
122
172
|
|
|
123
173
|
```typescript
|
|
174
|
+
// src/router.tsx
|
|
175
|
+
import { createRouter } from "@rangojs/router";
|
|
176
|
+
import { Document } from "./document";
|
|
177
|
+
import { urlpatterns } from "./urls";
|
|
178
|
+
|
|
179
|
+
export const router = createRouter<AppBindings>({
|
|
180
|
+
document: Document,
|
|
181
|
+
urls: urlpatterns,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// src/worker.tsx
|
|
185
|
+
import { router } from "./router";
|
|
186
|
+
|
|
187
|
+
export default {
|
|
188
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
189
|
+
return router.fetch(request, { env, ctx });
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### With Dynamic Cache Configuration
|
|
195
|
+
|
|
196
|
+
For per-request cache configuration (e.g., Cloudflare Workers with ExecutionContext):
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// src/router.tsx
|
|
200
|
+
import { createRouter } from "@rangojs/router";
|
|
201
|
+
import { CFCacheStore } from "@rangojs/router/cache";
|
|
202
|
+
|
|
203
|
+
export const router = createRouter<AppBindings>({
|
|
204
|
+
document: Document,
|
|
205
|
+
urls: urlpatterns,
|
|
206
|
+
// Cache config receives (env, ctx) separately
|
|
207
|
+
cache: (_env, ctx) => ({
|
|
208
|
+
store: new CFCacheStore({ ctx: ctx!, defaults: { ttl: 60 } }),
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
|
|
124
212
|
// src/worker.tsx
|
|
125
|
-
import router from "./router";
|
|
213
|
+
import { router } from "./router";
|
|
126
214
|
|
|
127
215
|
export default {
|
|
128
216
|
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
129
|
-
return router.fetch(request, env, ctx);
|
|
217
|
+
return router.fetch(request, { env, ctx });
|
|
130
218
|
},
|
|
131
219
|
};
|
|
132
220
|
```
|
|
@@ -135,7 +223,7 @@ export default {
|
|
|
135
223
|
|
|
136
224
|
```typescript
|
|
137
225
|
// src/urls.tsx
|
|
138
|
-
import { urls } from "@rangojs/router
|
|
226
|
+
import { urls } from "@rangojs/router";
|
|
139
227
|
import { Outlet } from "@rangojs/router/client";
|
|
140
228
|
|
|
141
229
|
// Pages
|
|
@@ -176,11 +264,11 @@ export const urlpatterns = urls(({ path, layout, parallel, loader, loading, cach
|
|
|
176
264
|
|
|
177
265
|
```typescript
|
|
178
266
|
// src/router.tsx
|
|
179
|
-
import {
|
|
267
|
+
import { createRouter } from "@rangojs/router";
|
|
180
268
|
import { Document } from "./document";
|
|
181
269
|
import { urlpatterns } from "./urls";
|
|
182
270
|
|
|
183
|
-
const router =
|
|
271
|
+
const router = createRouter({
|
|
184
272
|
document: Document,
|
|
185
273
|
urls: urlpatterns,
|
|
186
274
|
|
|
@@ -206,7 +294,7 @@ export default router;
|
|
|
206
294
|
|
|
207
295
|
```typescript
|
|
208
296
|
// src/urls/shop.tsx
|
|
209
|
-
import { urls } from "@rangojs/router
|
|
297
|
+
import { urls } from "@rangojs/router";
|
|
210
298
|
|
|
211
299
|
export const shopPatterns = urls(({ path, layout }) => [
|
|
212
300
|
path("/", ShopIndex, { name: "index" }),
|
|
@@ -214,10 +302,10 @@ export const shopPatterns = urls(({ path, layout }) => [
|
|
|
214
302
|
]);
|
|
215
303
|
|
|
216
304
|
// src/urls.tsx
|
|
217
|
-
import { urls
|
|
305
|
+
import { urls } from "@rangojs/router";
|
|
218
306
|
import { shopPatterns } from "./urls/shop";
|
|
219
307
|
|
|
220
|
-
export const urlpatterns = urls(({ path }) => [
|
|
308
|
+
export const urlpatterns = urls(({ path, include }) => [
|
|
221
309
|
path("/", HomePage, { name: "home" }),
|
|
222
310
|
include("/shop", shopPatterns, { name: "shop" }),
|
|
223
311
|
]);
|
|
@@ -226,21 +314,126 @@ export const urlpatterns = urls(({ path }) => [
|
|
|
226
314
|
## Environment Types
|
|
227
315
|
|
|
228
316
|
```typescript
|
|
229
|
-
|
|
230
|
-
|
|
317
|
+
// Bindings passed as TEnv to createRouter<TEnv>()
|
|
231
318
|
interface AppBindings {
|
|
232
319
|
DB: D1Database;
|
|
233
320
|
KV: KVNamespace;
|
|
234
321
|
}
|
|
235
322
|
|
|
323
|
+
// Variables declared via module augmentation
|
|
236
324
|
interface AppVariables {
|
|
237
325
|
user?: { id: string; name: string };
|
|
238
326
|
}
|
|
239
327
|
|
|
240
|
-
|
|
328
|
+
const router = createRouter<AppBindings>({
|
|
329
|
+
document: Document,
|
|
330
|
+
urls: urlpatterns,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Register types globally for implicit typing
|
|
334
|
+
declare global {
|
|
335
|
+
namespace RSCRouter {
|
|
336
|
+
interface Env extends AppBindings {}
|
|
337
|
+
interface Vars extends AppVariables {}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Connection Warmup
|
|
343
|
+
|
|
344
|
+
Enabled by default. Keeps TCP+TLS connections alive so navigations after idle periods
|
|
345
|
+
don't pay handshake costs.
|
|
346
|
+
|
|
347
|
+
After 60s of no user interaction, the connection is marked cold. When the user returns
|
|
348
|
+
(tab becomes visible or first mouse/touch), a `HEAD ?_rsc_warmup` request re-establishes
|
|
349
|
+
the TLS connection before the next navigation. The server responds with 204 No Content
|
|
350
|
+
before any middleware or routing runs.
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// Enabled by default
|
|
354
|
+
const router = createRouter({
|
|
355
|
+
document: Document,
|
|
356
|
+
urls: urlpatterns,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Disable warmup
|
|
360
|
+
const router = createRouter({
|
|
361
|
+
document: Document,
|
|
362
|
+
urls: urlpatterns,
|
|
363
|
+
warmup: false,
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
The warmup request is relative to the current page path, so it works correctly
|
|
368
|
+
with subpath deployments (reverse proxy, base path).
|
|
369
|
+
|
|
370
|
+
## Telemetry
|
|
371
|
+
|
|
372
|
+
The router emits structured lifecycle events through a pluggable telemetry sink.
|
|
373
|
+
Zero overhead when not configured.
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// Console sink for development
|
|
377
|
+
import { createRouter, createConsoleSink } from "@rangojs/router";
|
|
241
378
|
|
|
242
|
-
const router =
|
|
379
|
+
const router = createRouter({
|
|
243
380
|
document: Document,
|
|
244
381
|
urls: urlpatterns,
|
|
382
|
+
telemetry: createConsoleSink(),
|
|
245
383
|
});
|
|
246
384
|
```
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
// OpenTelemetry for production
|
|
388
|
+
import { createRouter, createOTelSink } from "@rangojs/router";
|
|
389
|
+
import { trace } from "@opentelemetry/api";
|
|
390
|
+
|
|
391
|
+
const router = createRouter({
|
|
392
|
+
document: Document,
|
|
393
|
+
urls: urlpatterns,
|
|
394
|
+
telemetry: createOTelSink(trace.getTracer("my-app")),
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
// Custom sink
|
|
400
|
+
const router = createRouter({
|
|
401
|
+
telemetry: {
|
|
402
|
+
emit(event) {
|
|
403
|
+
// Send to any observability backend
|
|
404
|
+
myTracer.record(event);
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Events emitted: `request.start/end/error`, `loader.start/end/error`,
|
|
411
|
+
`handler.error`, `cache.decision`, `revalidation.decision`.
|
|
412
|
+
|
|
413
|
+
## SSR Streaming Policy
|
|
414
|
+
|
|
415
|
+
Control whether HTML SSR responses stream progressively or wait for all content:
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { createRouter, type SSRStreamMode } from "@rangojs/router";
|
|
419
|
+
|
|
420
|
+
const router = createRouter({
|
|
421
|
+
ssr: {
|
|
422
|
+
resolveStreaming: ({ request }) => {
|
|
423
|
+
const ua = request.headers.get("user-agent") ?? "";
|
|
424
|
+
// Bots that can't process streamed HTML get a fully resolved page
|
|
425
|
+
if (/Googlebot|bingbot/i.test(ua)) return "allReady";
|
|
426
|
+
return "stream";
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
`SSRStreamMode` is `"stream" | "allReady"`:
|
|
433
|
+
|
|
434
|
+
- `"stream"` (default) — flush HTML as React renders. Suspense fallbacks appear first, then resolved content streams in. Best for real users (fastest TTFB).
|
|
435
|
+
- `"allReady"` — await `stream.allReady` before flushing. The full page arrives in one shot. Use for bots that cannot execute JavaScript or process chunked HTML.
|
|
436
|
+
|
|
437
|
+
The resolver receives `{ request, env, url }` and may be sync or async. It only runs on HTML SSR paths — RSC partials, `__rsc` requests, and response routes are unaffected.
|
|
438
|
+
|
|
439
|
+
When `resolveStreaming` is not configured, the default is `"stream"`.
|