@rangojs/router 0.0.0-experimental.e9c0b2f2 → 0.0.0-experimental.ea9f40f2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -10
- package/README.md +289 -938
- package/dist/bin/rango.js +271 -46
- package/dist/vite/index.js +673 -193
- package/package.json +10 -8
- package/skills/api-client/SKILL.md +1 -1
- package/skills/breadcrumbs/SKILL.md +31 -14
- package/skills/cache-guide/SKILL.md +5 -2
- package/skills/caching/SKILL.md +59 -4
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/debug-manifest/SKILL.md +1 -1
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +9 -1
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +8 -8
- package/skills/hooks/SKILL.md +54 -892
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +4 -4
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +46 -14
- package/skills/layout/SKILL.md +27 -10
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +23 -1
- package/skills/middleware/SKILL.md +7 -3
- package/skills/migrate-nextjs/SKILL.md +167 -6
- package/skills/migrate-react-router/SKILL.md +59 -677
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +1 -1
- package/skills/observability/SKILL.md +9 -1
- package/skills/parallel/SKILL.md +23 -4
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +28 -18
- package/skills/rango/SKILL.md +84 -25
- package/skills/response-routes/SKILL.md +15 -1
- package/skills/route/SKILL.md +71 -4
- package/skills/router-setup/SKILL.md +14 -3
- package/skills/scripts/SKILL.md +1 -1
- package/skills/server-actions/SKILL.md +3 -2
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +1 -1
- package/skills/testing/SKILL.md +2 -1
- package/skills/testing/handles.md +4 -2
- package/skills/testing/render-handler.md +15 -14
- package/skills/testing/reverse-and-types.md +8 -7
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -919
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +30 -3
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/browser/event-controller.ts +62 -10
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +65 -16
- package/src/browser/navigation-client.ts +32 -2
- package/src/browser/navigation-store.ts +128 -14
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +76 -17
- package/src/browser/prefetch/cache.ts +51 -11
- package/src/browser/prefetch/fetch.ts +59 -21
- package/src/browser/prefetch/queue.ts +19 -4
- package/src/browser/react/Link.tsx +13 -3
- package/src/browser/react/NavigationProvider.tsx +108 -4
- package/src/browser/response-adapter.ts +38 -9
- package/src/browser/rsc-router.tsx +54 -4
- package/src/browser/scroll-restoration.ts +7 -5
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +22 -10
- package/src/browser/types.ts +54 -1
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/index.ts +3 -1
- package/src/build/route-trie.ts +35 -7
- package/src/build/route-types/include-resolution.ts +347 -47
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-key-utils.ts +29 -0
- package/src/cache/cache-runtime.ts +262 -71
- package/src/cache/cache-scope.ts +2 -17
- package/src/cache/cache-tag.ts +60 -14
- package/src/cache/cf/cf-cache-store.ts +243 -20
- package/src/cache/document-cache.ts +54 -21
- package/src/cache/index.ts +1 -0
- package/src/cache/memory-segment-store.ts +110 -3
- package/src/cache/profile-registry.ts +15 -0
- package/src/cache/read-through-swr.ts +15 -1
- package/src/cache/segment-codec.ts +4 -4
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/types.ts +158 -0
- package/src/cache/vercel/vercel-cache-store.ts +401 -124
- package/src/client.rsc.tsx +0 -3
- package/src/client.tsx +0 -3
- package/src/cloudflare/tracing.ts +7 -8
- package/src/defer.ts +11 -22
- package/src/handle.ts +37 -15
- package/src/handles/MetaTags.tsx +16 -82
- package/src/handles/breadcrumbs.ts +12 -14
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +7 -8
- package/src/handles/meta.ts +7 -44
- package/src/host/errors.ts +15 -0
- package/src/host/index.ts +1 -0
- package/src/index.rsc.ts +8 -2
- package/src/index.ts +19 -13
- package/src/internal-debug.ts +11 -8
- package/src/prerender.ts +17 -4
- package/src/redirect-origin.ts +14 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/route-content-wrapper.tsx +12 -5
- package/src/route-definition/dsl-helpers.ts +21 -32
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +43 -43
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/resolve-handler-use.ts +0 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -4
- package/src/route-types.ts +0 -5
- package/src/router/find-match.ts +86 -8
- package/src/router/instrument.ts +9 -4
- package/src/router/lazy-includes.ts +72 -12
- package/src/router/loader-resolution.ts +14 -2
- package/src/router/manifest.ts +56 -11
- package/src/router/match-api.ts +76 -32
- package/src/router/match-handlers.ts +181 -135
- package/src/router/match-middleware/background-revalidation.ts +40 -23
- package/src/router/match-middleware/cache-store.ts +39 -24
- package/src/router/match-result.ts +35 -15
- package/src/router/middleware.ts +64 -38
- package/src/router/navigation-snapshot.ts +7 -5
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +53 -64
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +11 -5
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +23 -8
- package/src/router/route-snapshot.ts +14 -2
- package/src/router/router-context.ts +3 -1
- package/src/router/router-interfaces.ts +32 -1
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +39 -3
- package/src/router/segment-resolution/loader-cache.ts +93 -2
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +3 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +6 -8
- package/src/router/telemetry.ts +9 -1
- package/src/router/tracing.ts +14 -5
- package/src/router/trie-matching.ts +19 -11
- package/src/router/url-params.ts +13 -0
- package/src/router.ts +47 -16
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler.ts +60 -33
- package/src/rsc/manifest-init.ts +1 -1
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/progressive-enhancement.ts +61 -4
- package/src/rsc/redirect-guard.ts +2 -1
- package/src/rsc/rsc-rendering.ts +429 -37
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +30 -0
- package/src/segment-loader-promise.ts +18 -0
- package/src/segment-system.tsx +149 -14
- package/src/server/context.ts +67 -9
- package/src/server/cookie-store.ts +73 -1
- package/src/server/loader-registry.ts +13 -1
- package/src/server/request-context.ts +169 -10
- package/src/ssr/index.tsx +462 -178
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/testing/collect-handle.ts +14 -8
- package/src/testing/dispatch.ts +152 -40
- package/src/testing/generated-routes.ts +27 -11
- package/src/testing/index.ts +6 -0
- package/src/testing/render-handler.ts +14 -0
- package/src/testing/render-route.tsx +13 -10
- package/src/testing/run-transition-when.ts +164 -0
- package/src/theme/ThemeProvider.tsx +36 -26
- package/src/types/handler-context.ts +1 -1
- package/src/types/index.ts +2 -0
- package/src/types/route-config.ts +19 -7
- package/src/types/segments.ts +100 -0
- package/src/urls/include-helper.ts +10 -8
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +44 -12
- package/src/urls/path-helper.ts +5 -0
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +43 -18
- package/src/urls/urls-function.ts +0 -1
- package/src/vercel/tracing.ts +7 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/index.ts +7 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugins/vercel-output.ts +114 -25
- package/src/vite/plugins/version-injector.ts +22 -7
- package/src/vite/plugins/virtual-entries.ts +80 -22
- package/src/vite/rango.ts +29 -19
- package/src/vite/router-discovery.ts +171 -43
- package/src/vite/utils/prerender-utils.ts +17 -4
- package/src/vite/utils/shared-utils.ts +47 -0
- package/src/network-error-thrower.tsx +0 -18
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Project Setup and Route Mapping
|
|
2
|
+
|
|
3
|
+
## 1. Project Setup
|
|
4
|
+
|
|
5
|
+
Replace React Router tooling with Vite + Rango:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Framework mode:
|
|
9
|
+
npm remove react-router @react-router/dev @react-router/node @react-router/serve
|
|
10
|
+
# Library mode:
|
|
11
|
+
npm remove react-router react-router-dom
|
|
12
|
+
|
|
13
|
+
npm install @rangojs/router
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Replace the `@react-router/dev` Vite plugin with `rango()`:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// vite.config.ts
|
|
20
|
+
// Before: import { reactRouter } from "@react-router/dev/vite";
|
|
21
|
+
import { defineConfig } from "vite";
|
|
22
|
+
import { rango } from "@rangojs/router/vite";
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
plugins: [rango()],
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Delete `react-router.config.ts` — route configuration moves to the `urls()` DSL.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// src/router.tsx
|
|
33
|
+
import { createRouter } from "@rangojs/router";
|
|
34
|
+
import { Document } from "./document";
|
|
35
|
+
import { urlpatterns } from "./urls";
|
|
36
|
+
|
|
37
|
+
export default createRouter({
|
|
38
|
+
document: Document,
|
|
39
|
+
}).routes(urlpatterns);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 2. Route Mapping
|
|
43
|
+
|
|
44
|
+
### RR7 framework mode: route modules → urls() DSL
|
|
45
|
+
|
|
46
|
+
In framework mode, each route is a file with conventional exports (`loader`,
|
|
47
|
+
`action`, `default`, `meta`, `headers`, `shouldRevalidate`, `handle`,
|
|
48
|
+
`ErrorBoundary`, `HydrateFallback`). In Rango, all of these become part of the
|
|
49
|
+
`urls()` DSL or move into the server component handler:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
RR7 route module export → Rango equivalent
|
|
53
|
+
─────────────────────────────────────────────────────
|
|
54
|
+
default (Component) → handler in path()
|
|
55
|
+
loader → fetch in handler, or createLoader()
|
|
56
|
+
action → "use server" function
|
|
57
|
+
meta → ctx.use(Meta) in handler
|
|
58
|
+
headers → ctx.header() in handler or middleware
|
|
59
|
+
shouldRevalidate → revalidate() DSL
|
|
60
|
+
ErrorBoundary → errorBoundary() DSL
|
|
61
|
+
HydrateFallback → loading() DSL
|
|
62
|
+
handle → createHandle() for cross-segment data (breadcrumbs, etc.)
|
|
63
|
+
clientLoader / clientAction → "use client" component with React hooks
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Example: full route module migration
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// RR7 framework mode: app/routes/product.$slug.tsx
|
|
70
|
+
import type { Route } from "./+types/product.$slug";
|
|
71
|
+
|
|
72
|
+
export async function loader({ params }: Route.LoaderArgs) {
|
|
73
|
+
const product = await getProduct(params.slug);
|
|
74
|
+
if (!product) throw new Response("Not Found", { status: 404 });
|
|
75
|
+
return { product };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function action({ request }: Route.ActionArgs) {
|
|
79
|
+
const formData = await request.formData();
|
|
80
|
+
await addToCart(formData.get("productId") as string);
|
|
81
|
+
return { ok: true };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function meta({ data }: Route.MetaArgs) {
|
|
85
|
+
return [{ title: data.product.name }];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function headers() {
|
|
89
|
+
return { "Cache-Control": "max-age=300" };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function shouldRevalidate({ actionResult }) {
|
|
93
|
+
return !!actionResult;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default function ProductPage({ loaderData }: Route.ComponentProps) {
|
|
97
|
+
return <div>{loaderData.product.name}</div>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function ErrorBoundary() {
|
|
101
|
+
return <div>Product error</div>;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Rango: urls.tsx + handler
|
|
107
|
+
import { notFound } from "@rangojs/router";
|
|
108
|
+
|
|
109
|
+
const ProductPage: Handler<"product"> = async (ctx) => {
|
|
110
|
+
const product = await getProduct(ctx.params.slug);
|
|
111
|
+
if (!product) notFound("Product not found");
|
|
112
|
+
|
|
113
|
+
const meta = ctx.use(Meta);
|
|
114
|
+
meta({ title: product.name });
|
|
115
|
+
ctx.header("Cache-Control", "max-age=300");
|
|
116
|
+
|
|
117
|
+
return <div>{product.name}</div>;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// In urls.tsx:
|
|
121
|
+
path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
122
|
+
revalidate(({ actionId }) => !!actionId),
|
|
123
|
+
errorBoundary(() => <div>Product error</div>),
|
|
124
|
+
loading(<ProductSkeleton />),
|
|
125
|
+
])
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Key shift: the route module's scattered exports consolidate into the handler
|
|
129
|
+
(data fetching, meta, headers) and the DSL (revalidation, error boundary, loading).
|
|
130
|
+
|
|
131
|
+
### RR7 file routing → urls() DSL
|
|
132
|
+
|
|
133
|
+
| RR7 file path | Rango |
|
|
134
|
+
| ---------------------------------------- | ------------------------------------------------------------- |
|
|
135
|
+
| `app/routes/_index.tsx` | `path("/", HomePage, { name: "home" })` |
|
|
136
|
+
| `app/routes/about.tsx` | `path("/about", AboutPage, { name: "about" })` |
|
|
137
|
+
| `app/routes/blog.$slug.tsx` | `path("/blog/:slug", BlogPost, { name: "blogPost" })` |
|
|
138
|
+
| `app/routes/files.$.tsx` (splat) | `path("/files/:path*", FileBrowser, { name: "files" })` |
|
|
139
|
+
| `app/routes/dashboard.tsx` (layout) | `layout(<DashboardLayout />, () => [...])` |
|
|
140
|
+
| `app/routes/dashboard._index.tsx` | `path("/dashboard", DashboardIndex, { name: "dashboard" })` |
|
|
141
|
+
| `app/routes/dashboard.settings.tsx` | `path("/dashboard/settings", Settings, { name: "settings" })` |
|
|
142
|
+
| `app/routes/_auth.tsx` (pathless layout) | `layout(<AuthLayout />, () => [...])` |
|
|
143
|
+
| `app/routes/_auth.login.tsx` | `path("/login", LoginPage, { name: "login" })` |
|
|
144
|
+
|
|
145
|
+
### Library mode: config routes → urls() DSL
|
|
146
|
+
|
|
147
|
+
| React Router | Rango |
|
|
148
|
+
| -------------------------------------- | ------------------------------------------------------- |
|
|
149
|
+
| `path: "/"` | `path("/", HomePage, { name: "home" })` |
|
|
150
|
+
| `path: "about"` | `path("/about", AboutPage, { name: "about" })` |
|
|
151
|
+
| `path: "blog/:slug"` | `path("/blog/:slug", BlogPost, { name: "blogPost" })` |
|
|
152
|
+
| `path: "files/*"` (splat) | `path("/files/:path*", FileBrowser, { name: "files" })` |
|
|
153
|
+
| `path: "docs/:lang?"` (optional param) | `path("/docs/:lang?", Docs, { name: "docs" })` |
|
|
154
|
+
|
|
155
|
+
The RR splat (`$` / `*`) matches the bare parent too (`/files` binds `""`), so
|
|
156
|
+
it maps to `:path*` (zero-or-more). Use `:path+` only when you require at least
|
|
157
|
+
one trailing segment. RR reads the splat at `params["*"]`; Rango exposes it as a
|
|
158
|
+
named string at `ctx.params.path` with the `/` separators preserved (split to
|
|
159
|
+
recover RR's array):
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
path("/files/:path*", (ctx) => {
|
|
163
|
+
const parts = ctx.params.path === "" ? [] : ctx.params.path.split("/");
|
|
164
|
+
return <FileBrowser path={parts} />;
|
|
165
|
+
}, { name: "files" });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Layouts
|
|
169
|
+
|
|
170
|
+
React Router layouts use `<Outlet />` — same concept in Rango:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// React Router:
|
|
174
|
+
function DashboardLayout() {
|
|
175
|
+
return (
|
|
176
|
+
<div className="dashboard">
|
|
177
|
+
<Outlet />
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// route config:
|
|
183
|
+
{ path: "dashboard", element: <DashboardLayout />, children: [...] }
|
|
184
|
+
|
|
185
|
+
// Rango: same <Outlet />, from @rangojs/router/client
|
|
186
|
+
import { Outlet } from "@rangojs/router/client";
|
|
187
|
+
|
|
188
|
+
layout(<DashboardLayout />, () => [
|
|
189
|
+
path("/dashboard", DashboardIndex, { name: "dashboard" }),
|
|
190
|
+
path("/dashboard/settings", Settings, { name: "settings" }),
|
|
191
|
+
])
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Dynamic layouts (with data)
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// React Router: useLoaderData() in layout component
|
|
198
|
+
function DashboardLayout() {
|
|
199
|
+
const { user } = useLoaderData();
|
|
200
|
+
return <Shell user={user}><Outlet /></Shell>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Rango: handler function layout (server component)
|
|
204
|
+
layout(async (ctx) => {
|
|
205
|
+
const user = ctx.get("user");
|
|
206
|
+
return (
|
|
207
|
+
<Shell user={user}>
|
|
208
|
+
<Outlet />
|
|
209
|
+
</Shell>
|
|
210
|
+
);
|
|
211
|
+
}, () => [
|
|
212
|
+
path("/dashboard", DashboardIndex, { name: "dashboard" }),
|
|
213
|
+
])
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Nested routes
|
|
217
|
+
|
|
218
|
+
React Router's nested route tree maps directly to Rango's `layout()` nesting:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// React Router:
|
|
222
|
+
createBrowserRouter([{
|
|
223
|
+
path: "/",
|
|
224
|
+
element: <RootLayout />,
|
|
225
|
+
children: [
|
|
226
|
+
{ path: "dashboard",
|
|
227
|
+
element: <DashboardLayout />,
|
|
228
|
+
children: [
|
|
229
|
+
{ index: true, element: <DashboardIndex /> },
|
|
230
|
+
{ path: "settings", element: <Settings /> },
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
]
|
|
234
|
+
}])
|
|
235
|
+
|
|
236
|
+
// Rango:
|
|
237
|
+
urls(({ path, layout }) => [
|
|
238
|
+
layout(<RootLayout />, () => [
|
|
239
|
+
layout(<DashboardLayout />, () => [
|
|
240
|
+
path("/dashboard", DashboardIndex, { name: "dashboard" }),
|
|
241
|
+
path("/dashboard/settings", Settings, { name: "settings" }),
|
|
242
|
+
]),
|
|
243
|
+
]),
|
|
244
|
+
])
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Route groups / pathless layouts
|
|
248
|
+
|
|
249
|
+
React Router's pathless routes (layout routes without a path) are Rango's
|
|
250
|
+
layouts without a URL prefix:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// React Router: { element: <AuthLayout />, children: [...] }
|
|
254
|
+
|
|
255
|
+
// Rango: layout with no URL segment
|
|
256
|
+
layout(<AuthLayout />, () => [
|
|
257
|
+
path("/login", LoginPage, { name: "login" }),
|
|
258
|
+
path("/register", RegisterPage, { name: "register" }),
|
|
259
|
+
])
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Index routes
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// React Router: { index: true, element: <Home /> }
|
|
266
|
+
|
|
267
|
+
// Rango: path with "/" inside a layout
|
|
268
|
+
layout(<RootLayout />, () => [
|
|
269
|
+
path("/", HomePage, { name: "home" }),
|
|
270
|
+
])
|
|
271
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mime-routes
|
|
3
|
-
description: Content negotiation — serve different response types (RSC, JSON, text, XML) from the same URL based on Accept header
|
|
3
|
+
description: Content negotiation — serve different response types (RSC, JSON, text, XML) from the same URL based on Accept header. Use when the same URL needs to return JSON for API clients and HTML/RSC for browsers, or branching a handler on the Accept header.
|
|
4
4
|
argument-hint: [negotiate|vary|accept]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: observability
|
|
3
|
-
description: Debug Rango request performance with debugPerformance, Server-Timing, structured telemetry, and tracing
|
|
3
|
+
description: Debug Rango request performance with debugPerformance, Server-Timing, structured telemetry, and tracing. Use when a request feels slow and you need to see where time is spent, or wiring up tracing/telemetry for production requests.
|
|
4
4
|
argument-hint:
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -58,6 +58,14 @@ Read the timeline as intervals:
|
|
|
58
58
|
- Cache, route matching, middleware pre/post, RSC serialization, and SSR phases
|
|
59
59
|
appear as separate spans, so the slow phase is visible without guessing.
|
|
60
60
|
|
|
61
|
+
**Deployed Cloudflare caveat**: on production Workers, timers are frozen
|
|
62
|
+
during request execution (Spectre mitigation), so `Server-Timing` durations
|
|
63
|
+
read as ~0 on the deployed edge — they only advance across genuine awaited
|
|
64
|
+
I/O. The waterfall is a LOCAL diagnostic (dev, `vite preview`,
|
|
65
|
+
`wrangler dev`); for deployed workers, measure from the client
|
|
66
|
+
(`PerformanceResourceTiming`, TTFB) and use structured telemetry below for
|
|
67
|
+
server-side events.
|
|
68
|
+
|
|
61
69
|
## Structured telemetry
|
|
62
70
|
|
|
63
71
|
Use telemetry when you want durable production events rather than a one-request
|
package/skills/parallel/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: parallel
|
|
3
|
-
description: Define parallel routes for multi-column layouts, sidebars, and modal slots in @rangojs/router
|
|
3
|
+
description: Define parallel routes for multi-column layouts, sidebars, and modal slots in @rangojs/router. Use when a layout needs multiple independently-loading regions (e.g. a sidebar and main panel), or rendering more than one route segment at the same URL.
|
|
4
4
|
argument-hint: [@slot-name]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,6 +8,13 @@ argument-hint: [@slot-name]
|
|
|
8
8
|
|
|
9
9
|
Parallel routes render multiple components simultaneously in named slots.
|
|
10
10
|
|
|
11
|
+
## Not this skill if…
|
|
12
|
+
|
|
13
|
+
- You want a modal or slide-over that appears only on soft navigation and shows
|
|
14
|
+
the full page on hard navigation — that is `intercept()`: see `/intercept`.
|
|
15
|
+
- You want a slot rendered conditionally on HOW the user navigated — parallel
|
|
16
|
+
slots ALWAYS render alongside the page; see `/intercept`.
|
|
17
|
+
|
|
11
18
|
## Basic Parallel Routes
|
|
12
19
|
|
|
13
20
|
```typescript
|
|
@@ -232,6 +239,8 @@ layout(<AccountLayout />, () => [
|
|
|
232
239
|
|
|
233
240
|
A slot's `loading()` (whether from `handler.use` or explicit) makes that slot an independent streaming unit, exactly as in the **Streaming Behavior** section above.
|
|
234
241
|
|
|
242
|
+
Under a shared artifact (`cache()`, `"use cache"`, a PPR shell), the server-side `await ctx.use(CartLoader)` above is the BAKED lane — the capture-time value (identity reads included) freezes into the artifact; consume the loader client-side (`useLoader` in a `"use client"` component) to keep the slot live per request. One rule, stated once: `/rango` → Invariants ("the consumption-lane rule").
|
|
243
|
+
|
|
235
244
|
The `parallel` mount site has the narrowest allow-list for `handler.use` items — slots cannot bring their own middleware or layout, only `revalidate`, `loader`, `loading`, `errorBoundary`, `notFoundBoundary`, and `transition`. See [skills/handler-use](../handler-use/SKILL.md) for the full table and merge rules.
|
|
236
245
|
|
|
237
246
|
`transition` is allowed in the slot allow-list, but slot-level rendering does **not** currently apply a `<ViewTransition>` wrapper — only the layout/route wraps take effect at render time. For a modal-only morph today, use an element-level React `<ViewTransition>` inside the slot's component. The reverse direction is the useful guarantee: a layout-level `transition()` fires when the layout's default outlet content changes but **not** when a `<ParallelOutlet />` mounts new content (modal opens are not subtree updates of the layout VT). See [skills/view-transitions](../view-transitions/SKILL.md) for the wrap rules and the intercept caveat.
|
|
@@ -264,6 +273,8 @@ parallel(
|
|
|
264
273
|
|
|
265
274
|
Per-slot merge order is **handler.use → shared use → slot-local use**. Slot-local is the narrowest scope, so it wins for last-write-wins items. See [skills/handler-use § `loading()` is a single-assignment item — scope it correctly](../handler-use/SKILL.md#loading-is-a-single-assignment-item--scope-it-correctly) for the full reasoning.
|
|
266
275
|
|
|
276
|
+
Typing note: a BARE arrow slot handler infers its ctx (`"@cart": (ctx) => ...`), but an arrow inside a DESCRIPTOR needs an explicit annotation — `handler: (ctx: HandlerContext) => ...` — because `StaticHandlerDefinition` in the slot union contributes a second callable to the contextual type and TS declines to pick a signature.
|
|
277
|
+
|
|
267
278
|
## Slot Override Semantics
|
|
268
279
|
|
|
269
280
|
When multiple `parallel()` calls define the same slot name, **the last
|
|
@@ -344,9 +355,17 @@ parallel(
|
|
|
344
355
|
)
|
|
345
356
|
```
|
|
346
357
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
358
|
+
Where the slot sits decides its action default. A parallel under a
|
|
359
|
+
`path()` (or one of its orphan layouts) belongs to the route entry and
|
|
360
|
+
revalidates together with it on every action — handler-set data stays
|
|
361
|
+
consistent with no configuration. A parallel under a standalone
|
|
362
|
+
`layout()` entry follows the parent-chain default instead: skipped on
|
|
363
|
+
actions unless a `revalidate()` opts it in.
|
|
364
|
+
|
|
365
|
+
In either position, revalidating only the parallel does not re-run outer
|
|
366
|
+
handlers/layouts. If the slot reads `ctx.get()` data established above
|
|
367
|
+
it, opt the outer segment into revalidation as well (see `/rango` →
|
|
368
|
+
"Passing data down the tree").
|
|
350
369
|
|
|
351
370
|
A `revalidate()` callback may return a hard `boolean`, a soft
|
|
352
371
|
`{ defaultShouldRevalidate }` object, or nothing (`void` / `null` /
|