@rangojs/router 0.0.0-experimental.10
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/CLAUDE.md +43 -0
- package/README.md +19 -0
- package/dist/bin/rango.js +227 -0
- package/dist/vite/index.js +3039 -0
- package/package.json +171 -0
- package/skills/caching/SKILL.md +191 -0
- package/skills/debug-manifest/SKILL.md +108 -0
- package/skills/document-cache/SKILL.md +180 -0
- package/skills/fonts/SKILL.md +165 -0
- package/skills/hooks/SKILL.md +442 -0
- package/skills/intercept/SKILL.md +190 -0
- package/skills/layout/SKILL.md +213 -0
- package/skills/links/SKILL.md +180 -0
- package/skills/loader/SKILL.md +246 -0
- package/skills/middleware/SKILL.md +202 -0
- package/skills/mime-routes/SKILL.md +124 -0
- package/skills/parallel/SKILL.md +228 -0
- package/skills/prerender/SKILL.md +283 -0
- package/skills/rango/SKILL.md +54 -0
- package/skills/response-routes/SKILL.md +358 -0
- package/skills/route/SKILL.md +173 -0
- package/skills/router-setup/SKILL.md +346 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +78 -0
- package/skills/typesafety/SKILL.md +394 -0
- package/src/__internal.ts +175 -0
- package/src/bin/rango.ts +24 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +913 -0
- package/src/browser/navigation-client.ts +165 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +600 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +346 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/mount-context.ts +32 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +203 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +140 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +352 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/segment-structure-assert.ts +67 -0
- package/src/browser/server-action-bridge.ts +762 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +478 -0
- package/src/build/generate-manifest.ts +377 -0
- package/src/build/generate-route-types.ts +828 -0
- package/src/build/index.ts +36 -0
- package/src/build/route-trie.ts +239 -0
- package/src/cache/cache-scope.ts +563 -0
- package/src/cache/cf/cf-cache-store.ts +428 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/document-cache.ts +340 -0
- package/src/cache/index.ts +58 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +392 -0
- package/src/client.rsc.tsx +83 -0
- package/src/client.tsx +643 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -0
- package/src/debug.ts +233 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +295 -0
- package/src/handle.ts +130 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/host/cookie-handler.ts +159 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +56 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +330 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +138 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +202 -0
- package/src/href-context.ts +33 -0
- package/src/index.rsc.ts +121 -0
- package/src/index.ts +165 -0
- package/src/loader.rsc.ts +207 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/prerender/param-hash.ts +35 -0
- package/src/prerender/store.ts +40 -0
- package/src/prerender.ts +156 -0
- package/src/reverse.ts +267 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +193 -0
- package/src/route-definition.ts +1431 -0
- package/src/route-map-builder.ts +242 -0
- package/src/route-types.ts +220 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/intercept-resolution.ts +387 -0
- package/src/router/loader-resolution.ts +327 -0
- package/src/router/manifest.ts +216 -0
- package/src/router/match-api.ts +621 -0
- package/src/router/match-context.ts +264 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +382 -0
- package/src/router/match-middleware/cache-store.ts +276 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +281 -0
- package/src/router/match-middleware/segment-resolution.ts +184 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +213 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.ts +791 -0
- package/src/router/pattern-matching.ts +407 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +301 -0
- package/src/router/segment-resolution.ts +1315 -0
- package/src/router/trie-matching.ts +172 -0
- package/src/router/types.ts +163 -0
- package/src/router.gen.ts +6 -0
- package/src/router.ts +2423 -0
- package/src/rsc/handler.ts +1443 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +236 -0
- package/src/segment-system.tsx +442 -0
- package/src/server/context.ts +466 -0
- package/src/server/handle-store.ts +229 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +171 -0
- package/src/ssr/index.tsx +296 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +59 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/theme-context.ts +70 -0
- package/src/theme/theme-script.ts +152 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types.ts +1757 -0
- package/src/urls.gen.ts +8 -0
- package/src/urls.ts +1282 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +426 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/expose-prerender-handler-id.ts +429 -0
- package/src/vite/index.ts +2068 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +114 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: parallel
|
|
3
|
+
description: Define parallel routes for multi-column layouts, sidebars, and modal slots in @rangojs/router
|
|
4
|
+
argument-hint: [@slot-name]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Parallel Routes
|
|
8
|
+
|
|
9
|
+
Parallel routes render multiple components simultaneously in named slots.
|
|
10
|
+
|
|
11
|
+
## Basic Parallel Routes
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { urls } from "@rangojs/router";
|
|
15
|
+
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
16
|
+
|
|
17
|
+
function DashboardLayout() {
|
|
18
|
+
return (
|
|
19
|
+
<div className="dashboard">
|
|
20
|
+
<aside>
|
|
21
|
+
<ParallelOutlet name="@sidebar" />
|
|
22
|
+
</aside>
|
|
23
|
+
<main>
|
|
24
|
+
<Outlet />
|
|
25
|
+
</main>
|
|
26
|
+
<div className="notifications">
|
|
27
|
+
<ParallelOutlet name="@notifications" />
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const urlpatterns = urls(({ path, layout, parallel }) => [
|
|
34
|
+
layout(<DashboardLayout />, () => [
|
|
35
|
+
parallel({
|
|
36
|
+
"@sidebar": () => <Sidebar />,
|
|
37
|
+
"@notifications": () => <NotificationPanel />,
|
|
38
|
+
}),
|
|
39
|
+
|
|
40
|
+
path("/dashboard", DashboardIndex, { name: "dashboard.index" }),
|
|
41
|
+
path("/dashboard/analytics", Analytics, { name: "dashboard.analytics" }),
|
|
42
|
+
]),
|
|
43
|
+
]);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Parallel Routes with Context
|
|
47
|
+
|
|
48
|
+
Access route params and loaders in parallel slots:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
parallel({
|
|
52
|
+
"@sidebar": (ctx) => <Sidebar userId={ctx.params.userId} />,
|
|
53
|
+
"@related": (ctx) => <RelatedProducts slug={ctx.params.slug} />,
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Parallel Routes with Loaders
|
|
58
|
+
|
|
59
|
+
Add loaders and loading states to parallel routes:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
parallel(
|
|
63
|
+
{
|
|
64
|
+
"@sidebar": () => <CategorySidebar />,
|
|
65
|
+
},
|
|
66
|
+
() => [
|
|
67
|
+
loader(CategoriesLoader),
|
|
68
|
+
loading(<SidebarSkeleton />),
|
|
69
|
+
revalidate(() => false), // Never revalidate sidebar
|
|
70
|
+
]
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Multiple Parallel Slots
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
layout(<ShopLayout />, () => [
|
|
78
|
+
parallel({
|
|
79
|
+
"@promoBanner": () => (
|
|
80
|
+
<div className="promo-banner">
|
|
81
|
+
Summer Sale! 50% off selected items
|
|
82
|
+
</div>
|
|
83
|
+
),
|
|
84
|
+
"@sidebar": () => <CategorySidebar />,
|
|
85
|
+
"@cartPreview": () => <CartPreview />,
|
|
86
|
+
"@notification": () => <CartNotification />,
|
|
87
|
+
}),
|
|
88
|
+
|
|
89
|
+
path("/shop", ShopIndex, { name: "shop" }),
|
|
90
|
+
])
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Conditional Parallel Content
|
|
94
|
+
|
|
95
|
+
Render different content based on context:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
parallel({
|
|
99
|
+
"@sidebar": (ctx) => {
|
|
100
|
+
const user = ctx.env.Variables.user;
|
|
101
|
+
return user ? <UserSidebar user={user} /> : <GuestSidebar />;
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Parallel Routes with Revalidation
|
|
107
|
+
|
|
108
|
+
Control when parallel routes revalidate:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
parallel(
|
|
112
|
+
{
|
|
113
|
+
"@cart": () => <CartSummary />,
|
|
114
|
+
},
|
|
115
|
+
() => [
|
|
116
|
+
loader(CartLoader),
|
|
117
|
+
// Revalidate when cart actions occur
|
|
118
|
+
revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
|
|
119
|
+
]
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Named Outlets
|
|
124
|
+
|
|
125
|
+
Use `ParallelOutlet` to render slots in layouts:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
129
|
+
|
|
130
|
+
function MyLayout() {
|
|
131
|
+
return (
|
|
132
|
+
<div>
|
|
133
|
+
<header>
|
|
134
|
+
<ParallelOutlet name="@header" />
|
|
135
|
+
</header>
|
|
136
|
+
|
|
137
|
+
<div className="content">
|
|
138
|
+
<aside>
|
|
139
|
+
<ParallelOutlet name="@sidebar" />
|
|
140
|
+
</aside>
|
|
141
|
+
|
|
142
|
+
<main>
|
|
143
|
+
<Outlet /> {/* Main route content */}
|
|
144
|
+
</main>
|
|
145
|
+
|
|
146
|
+
<aside>
|
|
147
|
+
<ParallelOutlet name="@rightPanel" />
|
|
148
|
+
</aside>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<footer>
|
|
152
|
+
<ParallelOutlet name="@footer" />
|
|
153
|
+
</footer>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Complete Example
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { urls } from "@rangojs/router";
|
|
163
|
+
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
164
|
+
|
|
165
|
+
function ShopLayout() {
|
|
166
|
+
return (
|
|
167
|
+
<div className="shop">
|
|
168
|
+
<ParallelOutlet name="@promoBanner" />
|
|
169
|
+
<div className="content">
|
|
170
|
+
<aside>
|
|
171
|
+
<ParallelOutlet name="@sidebar" />
|
|
172
|
+
</aside>
|
|
173
|
+
<main>
|
|
174
|
+
<Outlet />
|
|
175
|
+
</main>
|
|
176
|
+
<aside>
|
|
177
|
+
<ParallelOutlet name="@cartPreview" />
|
|
178
|
+
</aside>
|
|
179
|
+
</div>
|
|
180
|
+
<ParallelOutlet name="@notification" />
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export const shopPatterns = urls(({
|
|
186
|
+
path,
|
|
187
|
+
layout,
|
|
188
|
+
parallel,
|
|
189
|
+
loader,
|
|
190
|
+
loading,
|
|
191
|
+
revalidate,
|
|
192
|
+
}) => [
|
|
193
|
+
layout(<ShopLayout />, () => [
|
|
194
|
+
// Simple parallel slot
|
|
195
|
+
parallel({
|
|
196
|
+
"@promoBanner": () => <PromoBanner />,
|
|
197
|
+
}),
|
|
198
|
+
|
|
199
|
+
// Parallel slot with loader
|
|
200
|
+
parallel(
|
|
201
|
+
{ "@sidebar": () => <CategorySidebar /> },
|
|
202
|
+
() => [
|
|
203
|
+
loader(CategoriesLoader),
|
|
204
|
+
revalidate(() => false),
|
|
205
|
+
]
|
|
206
|
+
),
|
|
207
|
+
|
|
208
|
+
// Parallel slot with revalidation
|
|
209
|
+
parallel(
|
|
210
|
+
{ "@cartPreview": () => <CartPreview /> },
|
|
211
|
+
() => [
|
|
212
|
+
loader(CartLoader),
|
|
213
|
+
loading(<CartSkeleton />),
|
|
214
|
+
revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
|
|
215
|
+
]
|
|
216
|
+
),
|
|
217
|
+
|
|
218
|
+
// Notification slot
|
|
219
|
+
parallel({
|
|
220
|
+
"@notification": () => <CartNotification />,
|
|
221
|
+
}),
|
|
222
|
+
|
|
223
|
+
// Routes
|
|
224
|
+
path("/", ShopIndex, { name: "index" }),
|
|
225
|
+
path("/product/:slug", ProductPage, { name: "product" }),
|
|
226
|
+
]),
|
|
227
|
+
]);
|
|
228
|
+
```
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prerender
|
|
3
|
+
description: Pre-render route segments at build time with createPrerenderHandler and passthrough fallback
|
|
4
|
+
argument-hint: [passthrough]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Pre-rendering with createPrerenderHandler
|
|
8
|
+
|
|
9
|
+
Pre-rendering is **caching at build time**. Same serialization format, same
|
|
10
|
+
deserialization path, same segment system. The worker handles every request --
|
|
11
|
+
there are NO static .html or .rsc files served from assets. The worker reads
|
|
12
|
+
pre-computed Flight payloads instead of executing handler code.
|
|
13
|
+
|
|
14
|
+
## API: createPrerenderHandler
|
|
15
|
+
|
|
16
|
+
### Static Route (no params)
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { createPrerenderHandler } from "@rangojs/router";
|
|
20
|
+
|
|
21
|
+
export const AboutPage = createPrerenderHandler(async (ctx) => {
|
|
22
|
+
const content = await fs.readFile("content/about.md", "utf-8");
|
|
23
|
+
return <Page content={markdownToJsx(content)} />;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// urls.tsx
|
|
27
|
+
path("/about", AboutPage, { name: "about" })
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Dynamic Route (with params)
|
|
31
|
+
|
|
32
|
+
Params come first, handler second:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
export const BlogPost = createPrerenderHandler(
|
|
36
|
+
// 1. Params: which slugs to pre-render
|
|
37
|
+
async () => {
|
|
38
|
+
const files = await glob("content/blog/*.md");
|
|
39
|
+
return files.map(f => ({ slug: basename(f, ".md") }));
|
|
40
|
+
},
|
|
41
|
+
// 2. Handler: runs at build time with BuildContext
|
|
42
|
+
async (ctx) => {
|
|
43
|
+
const md = await fs.readFile(`content/${ctx.params.slug}.md`, "utf-8");
|
|
44
|
+
return <Article content={markdownToJsx(md)} />;
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// urls.tsx
|
|
49
|
+
path("/blog/:slug", BlogPost, { name: "blog.post" })
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### With Passthrough (live fallback for unknown params)
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
export const ProductPage = createPrerenderHandler(
|
|
56
|
+
async () => {
|
|
57
|
+
const top = await db.query("SELECT id FROM products WHERE featured");
|
|
58
|
+
return top.map(p => ({ id: p.id }));
|
|
59
|
+
},
|
|
60
|
+
async (ctx) => {
|
|
61
|
+
const product = await db.query("SELECT * FROM products WHERE id = ?", ctx.params.id);
|
|
62
|
+
return <Product data={product} />;
|
|
63
|
+
},
|
|
64
|
+
{ passthrough: true }
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Passthrough Mode
|
|
69
|
+
|
|
70
|
+
Controls whether the handler stays in the RSC server bundle after build:
|
|
71
|
+
|
|
72
|
+
| | `passthrough: false` (default) | `passthrough: true` |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| Known params | Served from pre-rendered Flight payload | Served from pre-rendered Flight payload |
|
|
75
|
+
| Unknown params | Handler evicted, no live fallback | Handler runs live at request time |
|
|
76
|
+
| Bundle size | Handler code + imports removed | Handler code kept in RSC bundle |
|
|
77
|
+
| `revalidate()` | Not allowed (handler gone) | Allowed (handler can re-render) |
|
|
78
|
+
| `loading()` | Ignored (segments fully resolved) | Works for live fallback renders |
|
|
79
|
+
|
|
80
|
+
### When to use passthrough
|
|
81
|
+
|
|
82
|
+
Use `passthrough: true` when:
|
|
83
|
+
- The route has a large or open-ended param space (e.g., user profiles, product pages)
|
|
84
|
+
- You want to pre-render popular/known params for speed but still serve unknown params live
|
|
85
|
+
- You need `revalidate()` on the route
|
|
86
|
+
|
|
87
|
+
Use `passthrough: false` (default) when:
|
|
88
|
+
- All possible params are known at build time (e.g., markdown files, config-driven pages)
|
|
89
|
+
- You want maximum bundle size reduction (handler code + node:fs imports removed)
|
|
90
|
+
- The route uses build-only APIs (node:fs, local files) not available at runtime
|
|
91
|
+
|
|
92
|
+
## BuildContext
|
|
93
|
+
|
|
94
|
+
Handlers receive `BuildContext` at build time, a subset of the runtime `HandlerContext`:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
interface BuildContext<TParams> {
|
|
98
|
+
params: TParams; // From getParams
|
|
99
|
+
use: <T>(handle: Handle<T>) => (data: T) => void; // Push handle data
|
|
100
|
+
url: URL; // Synthetic URL from pattern + params
|
|
101
|
+
pathname: string; // Pathname from synthetic URL
|
|
102
|
+
// NOT available: req, headers, cookies, env (throws descriptive errors)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
All items inside the path's use() callback (child layouts, parallels) also receive
|
|
107
|
+
`BuildContext` during pre-rendering. Loaders are the exception -- they run at
|
|
108
|
+
request time with full server context.
|
|
109
|
+
|
|
110
|
+
## Handler Eviction
|
|
111
|
+
|
|
112
|
+
In production builds, `createPrerenderHandler` exports are replaced with stubs:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Original
|
|
116
|
+
export const BlogPost = createPrerenderHandler(getParams, handler);
|
|
117
|
+
|
|
118
|
+
// Stubbed (ships to server bundle when passthrough: false)
|
|
119
|
+
export const BlogPost = { __brand: "prerenderHandler", $$id: "abc123#BlogPost" };
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The original module and its imports (node:fs, markdown libs) are excluded from
|
|
123
|
+
the bundle. With `passthrough: true`, the handler code stays in the RSC bundle.
|
|
124
|
+
|
|
125
|
+
In client and SSR environments, ALL prerender handlers are always stubbed
|
|
126
|
+
(passthrough only affects the RSC server bundle).
|
|
127
|
+
|
|
128
|
+
## Sub-use Semantics
|
|
129
|
+
|
|
130
|
+
Everything inside the path's use() callback is part of the B segment and gets
|
|
131
|
+
pre-rendered:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
path("/blog/:slug", BlogPost, { name: "blog.post" }, () => [
|
|
135
|
+
layout(<PostLayout />, () => [ // inside B -> pre-rendered
|
|
136
|
+
loader(PostMetaLoader), // live at runtime, bundled normally
|
|
137
|
+
]),
|
|
138
|
+
parallel({ "@sidebar": BlogSidebar }), // inside B -> pre-rendered
|
|
139
|
+
])
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
If a parallel or child layout uses node APIs, wrap it in `createPrerenderHandler`
|
|
143
|
+
(static, no getParams) so the Vite plugin can stub it:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// sidebar.tsx -- uses node:fs, must be a createPrerenderHandler
|
|
147
|
+
export const BlogSidebar = createPrerenderHandler(async (ctx) => {
|
|
148
|
+
const files = await fs.readdir("content/blog/");
|
|
149
|
+
return <Sidebar posts={files.map(f => basename(f, ".md"))} />;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// urls.tsx
|
|
153
|
+
path("/blog/:slug", BlogPost, { name: "blog.post" }, () => [
|
|
154
|
+
parallel({ "@sidebar": BlogSidebar }), // stubbable, node:fs excluded
|
|
155
|
+
])
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Interaction with DSL Items
|
|
159
|
+
|
|
160
|
+
| DSL item | Behavior with createPrerenderHandler |
|
|
161
|
+
|----------------|--------------------------------------|
|
|
162
|
+
| `loader()` | Live at runtime, bundled normally. Use `cache()` for caching. |
|
|
163
|
+
| `revalidate()` | Not allowed without passthrough. Allowed with passthrough. |
|
|
164
|
+
| `cache()` | Orthogonal -- use on parent layouts and loaders. |
|
|
165
|
+
| `layout()` | Child layouts inside path are pre-rendered. Parent layouts are live. |
|
|
166
|
+
| `parallel()` | Parallel slots inside path are pre-rendered. |
|
|
167
|
+
| `middleware()` | Skipped during pre-render (no request). Runs at request time for loaders. |
|
|
168
|
+
| `loading()` | Ignored without passthrough. Works for live fallback with passthrough. |
|
|
169
|
+
| `intercept()` | Not pre-rendered (intercepts are navigation-triggered). |
|
|
170
|
+
|
|
171
|
+
## Dev Mode
|
|
172
|
+
|
|
173
|
+
In dev mode, `createPrerenderHandler` is a normal handler. Routes render live
|
|
174
|
+
on every request. No stubbing, no build-time pre-rendering. The handler runs
|
|
175
|
+
with full runtime context (not BuildContext).
|
|
176
|
+
|
|
177
|
+
## Storage Layout
|
|
178
|
+
|
|
179
|
+
Pre-rendered Flight payloads are stored in the build output:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
dist/static/__<hash>/
|
|
183
|
+
routes.json
|
|
184
|
+
prefixes.json
|
|
185
|
+
prerender/
|
|
186
|
+
blog.post/
|
|
187
|
+
d4e5f6a7.flight # hash of { slug: "hello-world" }
|
|
188
|
+
b8c9d0e1.flight # hash of { slug: "getting-started" }
|
|
189
|
+
about/
|
|
190
|
+
_.flight # static route, no params
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Edge Cases and Constraints
|
|
194
|
+
|
|
195
|
+
### Loaders are always live
|
|
196
|
+
Loaders on pre-rendered routes run at request time. They are bundled normally
|
|
197
|
+
and need `cache()` for caching. Do not use build-only APIs in loaders.
|
|
198
|
+
|
|
199
|
+
### Handle data is frozen
|
|
200
|
+
Handle values pushed via `ctx.use()` during pre-rendering are baked into the
|
|
201
|
+
Flight payload. They do not update at request time.
|
|
202
|
+
|
|
203
|
+
### Server actions work normally
|
|
204
|
+
Actions do not re-render the B segment. The pre-rendered handler output stays
|
|
205
|
+
frozen. Loaders are live and can be revalidated by actions. With `passthrough: true`
|
|
206
|
+
and `revalidate()`, the handler itself can re-render live.
|
|
207
|
+
|
|
208
|
+
### Empty getParams
|
|
209
|
+
If `getParams` returns an empty array, no Flight payloads are written. No error.
|
|
210
|
+
|
|
211
|
+
### Route name is required
|
|
212
|
+
Routes using `createPrerenderHandler` must have a `name` in path options.
|
|
213
|
+
The name is used as the storage key for Flight payloads.
|
|
214
|
+
|
|
215
|
+
### No revalidate without passthrough
|
|
216
|
+
Using `revalidate()` with `passthrough: false` produces a build-time warning.
|
|
217
|
+
The handler is evicted -- there is nothing to re-render.
|
|
218
|
+
|
|
219
|
+
### loading() is ignored without passthrough
|
|
220
|
+
Pre-rendered segments are fully resolved at build time and never suspend.
|
|
221
|
+
With `passthrough: true`, `loading()` works for live fallback renders.
|
|
222
|
+
|
|
223
|
+
## Complete Example
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// pages/guides-handler.tsx
|
|
227
|
+
import { createPrerenderHandler } from "@rangojs/router";
|
|
228
|
+
import { Link } from "@rangojs/router/client";
|
|
229
|
+
import { href } from "../router.js";
|
|
230
|
+
|
|
231
|
+
const knownGuides: Record<string, string> = {
|
|
232
|
+
routing: "Routing Guide",
|
|
233
|
+
caching: "Caching Guide",
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const GuidesDetail = createPrerenderHandler<{ slug: string }>(
|
|
237
|
+
async () => Object.keys(knownGuides).map((slug) => ({ slug })),
|
|
238
|
+
async (ctx) => {
|
|
239
|
+
const title = knownGuides[ctx.params.slug] ?? `Guide: ${ctx.params.slug}`;
|
|
240
|
+
return (
|
|
241
|
+
<div>
|
|
242
|
+
<h1>{title}</h1>
|
|
243
|
+
<p>Slug: {ctx.params.slug}</p>
|
|
244
|
+
<nav>
|
|
245
|
+
<Link to={href("guides.detail", { slug: "routing" })}>Routing</Link>
|
|
246
|
+
{" | "}
|
|
247
|
+
<Link to={href("guides.detail", { slug: "dynamic-test" })}>Dynamic</Link>
|
|
248
|
+
</nav>
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
},
|
|
252
|
+
{ passthrough: true },
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// pages/guides.tsx
|
|
256
|
+
import { urls } from "@rangojs/router";
|
|
257
|
+
import { GuidesDetail } from "./guides-handler.js";
|
|
258
|
+
|
|
259
|
+
export const guidesPatterns = urls(({ path }) => [
|
|
260
|
+
path("/:slug", GuidesDetail, { name: "detail" }),
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
// urls.tsx
|
|
264
|
+
import { urls, include } from "@rangojs/router";
|
|
265
|
+
import { guidesPatterns } from "./pages/guides.js";
|
|
266
|
+
|
|
267
|
+
export const urlpatterns = urls(({ path }) => [
|
|
268
|
+
path("/", HomePage, { name: "home" }),
|
|
269
|
+
include("/guides", guidesPatterns, { name: "guides" }),
|
|
270
|
+
]);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Trie Flags
|
|
274
|
+
|
|
275
|
+
Pre-rendered routes set flags on the route trie leaf at build time:
|
|
276
|
+
|
|
277
|
+
- `pr: true` -- route has pre-rendered B segment data
|
|
278
|
+
- `pt: true` -- passthrough mode (handler available for live fallback)
|
|
279
|
+
|
|
280
|
+
At runtime, the cache-lookup middleware uses these flags:
|
|
281
|
+
- `pr + hit` -- serve pre-rendered Flight payload
|
|
282
|
+
- `pr + pt + miss` -- fall through to live handler (handler kept in bundle)
|
|
283
|
+
- `pr + miss` (no pt) -- fall through (handler stubbed, no live render)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rango
|
|
3
|
+
description: Overview of @rangojs/router and available skills
|
|
4
|
+
argument-hint:
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# @rangojs/router
|
|
8
|
+
|
|
9
|
+
Django-inspired RSC router with composable URL patterns, type-safe href, and server components.
|
|
10
|
+
|
|
11
|
+
## Skills
|
|
12
|
+
|
|
13
|
+
| Skill | Description |
|
|
14
|
+
|-------|-------------|
|
|
15
|
+
| `/router-setup` | Create and configure the RSC router |
|
|
16
|
+
| `/route` | Define routes with `urls()` and `path()` |
|
|
17
|
+
| `/layout` | Layouts that wrap child routes |
|
|
18
|
+
| `/loader` | Data loaders with `createLoader()` |
|
|
19
|
+
| `/middleware` | Request processing and authentication |
|
|
20
|
+
| `/intercept` | Modal/slide-over patterns for soft navigation |
|
|
21
|
+
| `/parallel` | Multi-column layouts and sidebars |
|
|
22
|
+
| `/caching` | Segment caching with memory or KV stores |
|
|
23
|
+
| `/document-cache` | Edge caching with Cache-Control headers |
|
|
24
|
+
| `/theme` | Light/dark mode with FOUC prevention |
|
|
25
|
+
| `/links` | URL generation: ctx.reverse, href, useHref, useMount, scopedReverse |
|
|
26
|
+
| `/hooks` | Client-side React hooks |
|
|
27
|
+
| `/typesafety` | Type-safe routes, params, href, and environment |
|
|
28
|
+
| `/host-router` | Multi-app host routing with domain/subdomain patterns |
|
|
29
|
+
| `/tailwind` | Set up Tailwind CSS v4 with `?url` imports |
|
|
30
|
+
| `/response-routes` | JSON/text/HTML/XML/stream endpoints with `path.json()`, `path.text()` |
|
|
31
|
+
| `/mime-routes` | Content negotiation — same URL, different response types via Accept header |
|
|
32
|
+
| `/fonts` | Load web fonts with preload hints |
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// urls.tsx
|
|
38
|
+
import { urls } from "@rangojs/router";
|
|
39
|
+
|
|
40
|
+
export const urlpatterns = urls(({ path, layout }) => [
|
|
41
|
+
layout(RootLayout, () => [
|
|
42
|
+
path("/", HomePage, { name: "home" }),
|
|
43
|
+
path("/about", AboutPage, { name: "about" }),
|
|
44
|
+
]),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
// router.tsx
|
|
48
|
+
import { createRouter } from "@rangojs/router";
|
|
49
|
+
import { urlpatterns } from "./urls";
|
|
50
|
+
|
|
51
|
+
export default createRouter({ document: Document }).urls(urlpatterns);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use `/typesafety` for type-safe href and environment setup.
|