@rangojs/router 0.0.0-experimental.2
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 +7 -0
- package/README.md +19 -0
- package/dist/vite/index.js +1298 -0
- package/package.json +140 -0
- package/skills/caching/SKILL.md +319 -0
- package/skills/document-cache/SKILL.md +152 -0
- package/skills/hooks/SKILL.md +359 -0
- package/skills/intercept/SKILL.md +292 -0
- package/skills/layout/SKILL.md +216 -0
- package/skills/loader/SKILL.md +365 -0
- package/skills/middleware/SKILL.md +442 -0
- package/skills/parallel/SKILL.md +255 -0
- package/skills/route/SKILL.md +141 -0
- package/skills/router-setup/SKILL.md +403 -0
- package/skills/theme/SKILL.md +54 -0
- package/skills/typesafety/SKILL.md +352 -0
- package/src/__mocks__/version.ts +6 -0
- package/src/__tests__/component-utils.test.ts +76 -0
- package/src/__tests__/route-definition.test.ts +63 -0
- package/src/__tests__/urls.test.tsx +436 -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 +893 -0
- package/src/browser/navigation-client.ts +162 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +559 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +275 -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/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +178 -0
- package/src/browser/react/use-href.tsx +208 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-navigation.ts +150 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +353 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/server-action-bridge.ts +747 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +464 -0
- package/src/cache/__tests__/document-cache.test.ts +522 -0
- package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
- package/src/cache/__tests__/memory-store.test.ts +484 -0
- package/src/cache/cache-scope.ts +565 -0
- package/src/cache/cf/__tests__/cf-cache-store.test.ts +428 -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 +387 -0
- package/src/client.rsc.tsx +88 -0
- package/src/client.tsx +621 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -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 +259 -0
- package/src/handle.ts +120 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/href-client.ts +128 -0
- package/src/href-context.ts +33 -0
- package/src/href.ts +177 -0
- package/src/index.rsc.ts +79 -0
- package/src/index.ts +87 -0
- package/src/loader.rsc.ts +204 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +198 -0
- package/src/route-definition.ts +1371 -0
- package/src/route-map-builder.ts +146 -0
- package/src/route-types.ts +198 -0
- package/src/route-utils.ts +89 -0
- package/src/router/__tests__/match-context.test.ts +104 -0
- package/src/router/__tests__/match-pipelines.test.ts +537 -0
- package/src/router/__tests__/match-result.test.ts +566 -0
- package/src/router/__tests__/on-error.test.ts +935 -0
- package/src/router/__tests__/pattern-matching.test.ts +577 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/loader-resolution.ts +326 -0
- package/src/router/manifest.ts +138 -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 +261 -0
- package/src/router/match-middleware/cache-store.ts +266 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +268 -0
- package/src/router/match-middleware/segment-resolution.ts +174 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +214 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.test.ts +1355 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +272 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +299 -0
- package/src/router/types.ts +96 -0
- package/src/router.ts +3876 -0
- package/src/rsc/__tests__/helpers.test.ts +175 -0
- package/src/rsc/handler.ts +1060 -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 +237 -0
- package/src/segment-system.tsx +456 -0
- package/src/server/__tests__/request-context.test.ts +171 -0
- package/src/server/context.ts +417 -0
- package/src/server/handle-store.ts +230 -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 +146 -0
- package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
- package/src/ssr/index.tsx +234 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/__tests__/theme.test.ts +120 -0
- package/src/theme/constants.ts +55 -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 +1561 -0
- package/src/urls.ts +726 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/__tests__/expose-loader-id.test.ts +117 -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 +357 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/index.ts +787 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +109 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: route
|
|
3
|
+
description: Define basic routes and route handlers in rsc-router
|
|
4
|
+
argument-hint: [route-name]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Basic Route Definition
|
|
8
|
+
|
|
9
|
+
## Route Definition (routes.ts)
|
|
10
|
+
|
|
11
|
+
Define type-safe routes using the `route()` function:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { route } from "rsc-router";
|
|
15
|
+
|
|
16
|
+
export const routes = route({
|
|
17
|
+
index: "/",
|
|
18
|
+
about: "/about",
|
|
19
|
+
// Dynamic params
|
|
20
|
+
product: "/products/:id",
|
|
21
|
+
// Nested routes
|
|
22
|
+
blog: {
|
|
23
|
+
index: "/blog",
|
|
24
|
+
post: "/blog/:slug",
|
|
25
|
+
category: "/blog/category/:category",
|
|
26
|
+
},
|
|
27
|
+
// Optional params
|
|
28
|
+
search: "/search/:query?",
|
|
29
|
+
// Constrained params
|
|
30
|
+
locale: "/:locale(en|de|fr)",
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Parameter Types
|
|
35
|
+
|
|
36
|
+
| Pattern | Example | Description |
|
|
37
|
+
|---------|---------|-------------|
|
|
38
|
+
| `:id` | `/products/:id` | Required parameter |
|
|
39
|
+
| `:page?` | `/list/:page?` | Optional parameter |
|
|
40
|
+
| `:locale(en\|de)` | `/:locale(en\|de)` | Constrained values |
|
|
41
|
+
| `:lang(en\|de)?` | `/:lang(en\|de)?` | Optional + constrained |
|
|
42
|
+
|
|
43
|
+
## Route Handler (handlers/*.tsx)
|
|
44
|
+
|
|
45
|
+
Use the `map()` function to define handlers:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { map } from "rsc-router/server";
|
|
49
|
+
import type { routes } from "../routes.js";
|
|
50
|
+
|
|
51
|
+
export default map<typeof routes>(({ route, loader, loading, revalidate }) => [
|
|
52
|
+
// Simple route
|
|
53
|
+
route("index", () => <HomePage />),
|
|
54
|
+
|
|
55
|
+
// Route with params
|
|
56
|
+
route("product", (ctx) => {
|
|
57
|
+
return <ProductPage id={ctx.params.id} />;
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
// Route with loader
|
|
61
|
+
route("blog.post", async (ctx) => {
|
|
62
|
+
const post = await ctx.use(PostLoader);
|
|
63
|
+
return <BlogPost post={post} />;
|
|
64
|
+
}, () => [
|
|
65
|
+
loader(PostLoader),
|
|
66
|
+
loading(<PostSkeleton />),
|
|
67
|
+
]),
|
|
68
|
+
]);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Handler Context
|
|
72
|
+
|
|
73
|
+
The `ctx` object provides:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
route("product", (ctx) => {
|
|
77
|
+
ctx.params // Type-safe route parameters { id: string }
|
|
78
|
+
ctx.query // Query string parameters
|
|
79
|
+
ctx.url // Full URL object
|
|
80
|
+
ctx.pathname // Current pathname
|
|
81
|
+
ctx.method // HTTP method (GET, POST, etc.)
|
|
82
|
+
ctx.request // Raw Request object
|
|
83
|
+
|
|
84
|
+
// Data fetching
|
|
85
|
+
const data = await ctx.use(MyLoader);
|
|
86
|
+
|
|
87
|
+
// Handle accumulation
|
|
88
|
+
const push = ctx.use(BreadcrumbHandle);
|
|
89
|
+
push({ label: "Product", href: ctx.pathname });
|
|
90
|
+
|
|
91
|
+
// Context variables (set by middleware)
|
|
92
|
+
const user = ctx.get("user");
|
|
93
|
+
|
|
94
|
+
return <ProductPage />;
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Router Registration (router.tsx)
|
|
99
|
+
|
|
100
|
+
Register routes with the router:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { createRSCRouter } from "rsc-router/server";
|
|
104
|
+
import { routes } from "./routes.js";
|
|
105
|
+
|
|
106
|
+
const router = createRSCRouter<AppEnv>({
|
|
107
|
+
document: RootLayout,
|
|
108
|
+
})
|
|
109
|
+
.routes("/", routes)
|
|
110
|
+
.map(() => import("./handlers/main.js"));
|
|
111
|
+
|
|
112
|
+
export default router;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Type-Safe Links
|
|
116
|
+
|
|
117
|
+
Use the generated `href` function:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { href } from "./router.js";
|
|
121
|
+
|
|
122
|
+
// Type-safe href generation
|
|
123
|
+
<Link href={href("product", { id: "123" })} />
|
|
124
|
+
<Link href={href("blog.post", { slug: "hello-world" })} />
|
|
125
|
+
|
|
126
|
+
// With query params
|
|
127
|
+
<Link href={href("search", { query: "test" }, { page: "2" })} />
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Route Configuration Options
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
route("product", ProductHandler, () => [
|
|
134
|
+
loader(ProductLoader), // Attach data loader
|
|
135
|
+
loading(<Skeleton />), // Loading UI
|
|
136
|
+
revalidate(revalidateFn), // Cache control
|
|
137
|
+
errorBoundary(<ErrorFallback />), // Error handling
|
|
138
|
+
notFoundBoundary(<NotFound />), // 404 handling
|
|
139
|
+
parallel({ "@sidebar": Sidebar }), // Parallel slots
|
|
140
|
+
])
|
|
141
|
+
```
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-setup
|
|
3
|
+
description: Create and configure the RSC router with createRSCRouter
|
|
4
|
+
argument-hint: [option]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Router Setup with createRSCRouter
|
|
8
|
+
|
|
9
|
+
## Basic Router Creation
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { createRSCRouter } from "rsc-router/server";
|
|
13
|
+
import { Document } from "./document";
|
|
14
|
+
|
|
15
|
+
const router = createRSCRouter({
|
|
16
|
+
document: Document,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export default router;
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Router Options
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
interface RSCRouterOptions<TEnv> {
|
|
26
|
+
// Document component wrapping entire app
|
|
27
|
+
document?: ComponentType<{ children: ReactNode }>;
|
|
28
|
+
|
|
29
|
+
// Enable performance metrics (console + Server-Timing header)
|
|
30
|
+
debugPerformance?: boolean;
|
|
31
|
+
|
|
32
|
+
// Default error boundary fallback
|
|
33
|
+
defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler;
|
|
34
|
+
|
|
35
|
+
// Default not-found boundary (for notFound() calls)
|
|
36
|
+
defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler;
|
|
37
|
+
|
|
38
|
+
// Component for routes with no match (404)
|
|
39
|
+
notFound?: ReactNode | ((props: { pathname: string }) => ReactNode);
|
|
40
|
+
|
|
41
|
+
// Error logging callback
|
|
42
|
+
onError?: OnErrorCallback<TEnv>;
|
|
43
|
+
|
|
44
|
+
// Cache configuration
|
|
45
|
+
cache?:
|
|
46
|
+
| { store: SegmentCacheStore; enabled?: boolean }
|
|
47
|
+
| ((env: TEnv) => { store: SegmentCacheStore; enabled?: boolean });
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Full Configuration Example
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { createRSCRouter } from "rsc-router/server";
|
|
55
|
+
import { createMemorySegmentStore } from "rsc-router/cache";
|
|
56
|
+
import { Document } from "./document";
|
|
57
|
+
|
|
58
|
+
const cacheStore = createMemorySegmentStore();
|
|
59
|
+
|
|
60
|
+
const router = createRSCRouter<AppEnv>({
|
|
61
|
+
document: Document,
|
|
62
|
+
debugPerformance: process.env.NODE_ENV === "development",
|
|
63
|
+
|
|
64
|
+
cache: {
|
|
65
|
+
store: cacheStore,
|
|
66
|
+
enabled: true,
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
defaultErrorBoundary: ({ error, reset }) => (
|
|
70
|
+
<div className="error">
|
|
71
|
+
<h2>Something went wrong</h2>
|
|
72
|
+
<p>{error.message}</p>
|
|
73
|
+
<button onClick={reset}>Try again</button>
|
|
74
|
+
</div>
|
|
75
|
+
),
|
|
76
|
+
|
|
77
|
+
defaultNotFoundBoundary: ({ notFound }) => (
|
|
78
|
+
<div className="not-found">
|
|
79
|
+
<h2>Not Found</h2>
|
|
80
|
+
<p>{notFound.message}</p>
|
|
81
|
+
</div>
|
|
82
|
+
),
|
|
83
|
+
|
|
84
|
+
notFound: ({ pathname }) => (
|
|
85
|
+
<div className="404">
|
|
86
|
+
<h1>404</h1>
|
|
87
|
+
<p>Page not found: {pathname}</p>
|
|
88
|
+
</div>
|
|
89
|
+
),
|
|
90
|
+
|
|
91
|
+
onError: (error, ctx) => {
|
|
92
|
+
console.error("Router error:", error);
|
|
93
|
+
// Send to error tracking service
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Environment Types
|
|
99
|
+
|
|
100
|
+
Define your app's environment type:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import type { RouterEnv } from "rsc-router/server";
|
|
104
|
+
|
|
105
|
+
interface AppBindings {
|
|
106
|
+
DB: D1Database;
|
|
107
|
+
KV: KVNamespace;
|
|
108
|
+
RATE_LIMITER: RateLimiter;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface AppVariables {
|
|
112
|
+
user?: { id: string; name: string };
|
|
113
|
+
permissions?: string[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
type AppEnv = RouterEnv<AppBindings, AppVariables>;
|
|
117
|
+
|
|
118
|
+
const router = createRSCRouter<AppEnv>({
|
|
119
|
+
document: Document,
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Registering Routes
|
|
124
|
+
|
|
125
|
+
### routes() - Register route definitions
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { homeRoutes } from "./routes/home";
|
|
129
|
+
import { shopRoutes } from "./routes/shop";
|
|
130
|
+
import { adminRoutes } from "./routes/admin";
|
|
131
|
+
|
|
132
|
+
const router = createRSCRouter<AppEnv>({ document: Document })
|
|
133
|
+
// Without prefix
|
|
134
|
+
.routes(homeRoutes)
|
|
135
|
+
.map(() => import("./handlers/home"))
|
|
136
|
+
|
|
137
|
+
// With prefix
|
|
138
|
+
.routes("/shop", shopRoutes)
|
|
139
|
+
.map(() => import("./handlers/shop"))
|
|
140
|
+
|
|
141
|
+
.routes("/admin", adminRoutes)
|
|
142
|
+
.map(() => import("./handlers/admin"));
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### map() - Register handlers
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Async import (code splitting)
|
|
149
|
+
.routes(shopRoutes)
|
|
150
|
+
.map(() => import("./handlers/shop"))
|
|
151
|
+
|
|
152
|
+
// Inline definition (no separate handler file)
|
|
153
|
+
.routes({ index: "/", about: "/about" })
|
|
154
|
+
.map(({ route }) => [
|
|
155
|
+
route("index", () => <HomePage />),
|
|
156
|
+
route("about", () => <AboutPage />),
|
|
157
|
+
])
|
|
158
|
+
|
|
159
|
+
// With the map helper function
|
|
160
|
+
import { map } from "rsc-router/server";
|
|
161
|
+
|
|
162
|
+
// handlers/shop.ts
|
|
163
|
+
export default map<typeof shopRoutes>(({ route, layout, loader }) => [
|
|
164
|
+
layout(<ShopLayout />, () => [
|
|
165
|
+
route("index", ShopIndex),
|
|
166
|
+
route("products", ProductList),
|
|
167
|
+
]),
|
|
168
|
+
]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Global Middleware
|
|
172
|
+
|
|
173
|
+
### use() - Add middleware
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const router = createRSCRouter<AppEnv>({ document: Document })
|
|
177
|
+
// Global middleware (all routes)
|
|
178
|
+
.use(loggerMiddleware)
|
|
179
|
+
.use(corsMiddleware)
|
|
180
|
+
|
|
181
|
+
// Pattern-based middleware
|
|
182
|
+
.use("/api/*", rateLimiter)
|
|
183
|
+
.use("/admin/*", adminAuthMiddleware)
|
|
184
|
+
|
|
185
|
+
// Routes with scoped middleware
|
|
186
|
+
.routes("/shop", shopRoutes)
|
|
187
|
+
.use(shopMiddleware) // Only applies to shop routes
|
|
188
|
+
.map(() => import("./handlers/shop"));
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Middleware patterns
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
"/admin/*" // All paths under /admin
|
|
195
|
+
"/api/*/protected" // Wildcard in middle
|
|
196
|
+
"/checkout.*" // Routes starting with /checkout
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Type-Safe Links with href()
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// routes/shop.ts - use dot notation for namespaced keys
|
|
203
|
+
export const shopRoutes = route({
|
|
204
|
+
"shop.index": "/",
|
|
205
|
+
"shop.products": "/products",
|
|
206
|
+
"shop.product": "/products/:slug",
|
|
207
|
+
"shop.cart": "/cart",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// router.tsx
|
|
211
|
+
const router = createRSCRouter<AppEnv>({ document: Document })
|
|
212
|
+
.routes(homeRoutes)
|
|
213
|
+
.map(() => import("./handlers/home"))
|
|
214
|
+
.routes("/shop", shopRoutes) // Keys stay unchanged, only URLs get prefixed
|
|
215
|
+
.map(() => import("./handlers/shop"));
|
|
216
|
+
|
|
217
|
+
// Export for use in components
|
|
218
|
+
export const href = router.href;
|
|
219
|
+
|
|
220
|
+
// Usage with full autocomplete
|
|
221
|
+
href("index"); // "/"
|
|
222
|
+
href("about"); // "/about"
|
|
223
|
+
href("shop.products"); // "/shop/products"
|
|
224
|
+
href("shop.product", { slug: "widget" }); // "/shop/products/widget"
|
|
225
|
+
|
|
226
|
+
// In components
|
|
227
|
+
import { href } from "./router";
|
|
228
|
+
<Link to={href("shop.cart")}>Cart</Link>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Note**: Route keys stay unchanged when mounted with a prefix. Only URL patterns get the prefix applied. Use dot notation (e.g., `shop.products`) for namespaced keys.
|
|
232
|
+
|
|
233
|
+
## Route Type Registration
|
|
234
|
+
|
|
235
|
+
For global type inference:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// router.ts
|
|
239
|
+
const _router = createRSCRouter<AppEnv>({ document: Document })
|
|
240
|
+
.routes(homeRoutes)
|
|
241
|
+
.map(() => import("./handlers/home"))
|
|
242
|
+
.routes("/shop", shopRoutes)
|
|
243
|
+
.map(() => import("./handlers/shop"));
|
|
244
|
+
|
|
245
|
+
// Extract route types
|
|
246
|
+
type AppRoutes = typeof _router.routeMap;
|
|
247
|
+
|
|
248
|
+
// Augment global types
|
|
249
|
+
declare global {
|
|
250
|
+
namespace RSCRouter {
|
|
251
|
+
interface RegisteredRoutes extends AppRoutes {}
|
|
252
|
+
interface Env extends AppEnv {}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export default _router;
|
|
257
|
+
export const href = _router.href;
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Now handlers have type-safe context without imports.
|
|
261
|
+
|
|
262
|
+
## Cache Configuration
|
|
263
|
+
|
|
264
|
+
### Static cache
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { createMemorySegmentStore } from "rsc-router/cache";
|
|
268
|
+
|
|
269
|
+
const cacheStore = createMemorySegmentStore();
|
|
270
|
+
|
|
271
|
+
const router = createRSCRouter<AppEnv>({
|
|
272
|
+
document: Document,
|
|
273
|
+
cache: {
|
|
274
|
+
store: cacheStore,
|
|
275
|
+
enabled: true,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Dynamic cache (per-environment)
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const router = createRSCRouter<AppEnv>({
|
|
284
|
+
document: Document,
|
|
285
|
+
cache: (env) => ({
|
|
286
|
+
store: env.Bindings.CACHE_KV,
|
|
287
|
+
enabled: env.Bindings.CACHE_ENABLED === "true",
|
|
288
|
+
}),
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Cloudflare KV cache
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { createCFKVSegmentStore } from "rsc-router/cache/cf";
|
|
296
|
+
|
|
297
|
+
const router = createRSCRouter<AppEnv>({
|
|
298
|
+
document: Document,
|
|
299
|
+
cache: (env) => ({
|
|
300
|
+
store: createCFKVSegmentStore(env.Bindings.CACHE_KV),
|
|
301
|
+
enabled: true,
|
|
302
|
+
}),
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Document Component
|
|
307
|
+
|
|
308
|
+
The document wraps the entire app and persists during errors:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// document.tsx
|
|
312
|
+
"use client";
|
|
313
|
+
|
|
314
|
+
export function Document({ children }: { children: React.ReactNode }) {
|
|
315
|
+
return (
|
|
316
|
+
<html lang="en">
|
|
317
|
+
<head>
|
|
318
|
+
<meta charSet="utf-8" />
|
|
319
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
320
|
+
<title>My App</title>
|
|
321
|
+
</head>
|
|
322
|
+
<body>
|
|
323
|
+
{children}
|
|
324
|
+
</body>
|
|
325
|
+
</html>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Router Methods
|
|
331
|
+
|
|
332
|
+
### match() - Full render
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
const result = await router.match(request, env);
|
|
336
|
+
// Returns full render tree with segments, loaders, metadata
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### matchPartial() - Partial navigation
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
const result = await router.matchPartial(request, env, {
|
|
343
|
+
actionId: "addToCart",
|
|
344
|
+
actionResult: { success: true },
|
|
345
|
+
});
|
|
346
|
+
// Only re-renders changed segments
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### matchError() - Error handling
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const result = await router.matchError(request, env, error);
|
|
353
|
+
// Finds nearest error boundary
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### previewMatch() - Middleware validation
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
const result = await router.previewMatch(request, env);
|
|
360
|
+
// Executes middleware without full segment resolution
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Complete Router Example
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// router.ts
|
|
367
|
+
import { createRSCRouter } from "rsc-router/server";
|
|
368
|
+
import { createMemorySegmentStore } from "rsc-router/cache";
|
|
369
|
+
import { Document } from "./document";
|
|
370
|
+
import { homeRoutes } from "./routes/home";
|
|
371
|
+
import { shopRoutes } from "./routes/shop";
|
|
372
|
+
import { adminRoutes } from "./routes/admin";
|
|
373
|
+
import { loggerMiddleware, authMiddleware, adminMiddleware } from "./middleware";
|
|
374
|
+
|
|
375
|
+
const cacheStore = createMemorySegmentStore();
|
|
376
|
+
|
|
377
|
+
const router = createRSCRouter<AppEnv>({
|
|
378
|
+
document: Document,
|
|
379
|
+
debugPerformance: true,
|
|
380
|
+
cache: { store: cacheStore, enabled: true },
|
|
381
|
+
defaultErrorBoundary: <DefaultError />,
|
|
382
|
+
notFound: ({ pathname }) => <NotFoundPage pathname={pathname} />,
|
|
383
|
+
})
|
|
384
|
+
// Global middleware
|
|
385
|
+
.use(loggerMiddleware)
|
|
386
|
+
|
|
387
|
+
// Public routes
|
|
388
|
+
.routes(homeRoutes)
|
|
389
|
+
.map(() => import("./handlers/home"))
|
|
390
|
+
|
|
391
|
+
// Shop routes (with auth)
|
|
392
|
+
.routes("/shop", shopRoutes)
|
|
393
|
+
.use(authMiddleware)
|
|
394
|
+
.map(() => import("./handlers/shop"))
|
|
395
|
+
|
|
396
|
+
// Admin routes (with admin auth)
|
|
397
|
+
.routes("/admin", adminRoutes)
|
|
398
|
+
.use(adminMiddleware)
|
|
399
|
+
.map(() => import("./handlers/admin"));
|
|
400
|
+
|
|
401
|
+
export default router;
|
|
402
|
+
export const href = router.href;
|
|
403
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: theme
|
|
3
|
+
description: Opt-in theme system with FOUC prevention for light/dark mode
|
|
4
|
+
argument-hint: [setup]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Theme Support
|
|
8
|
+
|
|
9
|
+
Opt-in theme system with FOUC prevention.
|
|
10
|
+
|
|
11
|
+
## Enable
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// Simple - all defaults
|
|
15
|
+
const router = createRSCRouter<Env>({ theme: true });
|
|
16
|
+
|
|
17
|
+
// Custom config
|
|
18
|
+
const router = createRSCRouter<Env>({
|
|
19
|
+
theme: {
|
|
20
|
+
defaultTheme: "system", // "light" | "dark" | "system"
|
|
21
|
+
themes: ["light", "dark"],
|
|
22
|
+
attribute: "class", // or "data-theme"
|
|
23
|
+
storageKey: "theme",
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Server (route handlers)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
route("settings", (ctx) => {
|
|
32
|
+
const current = ctx.theme; // read from cookie
|
|
33
|
+
ctx.setTheme("dark"); // set cookie
|
|
34
|
+
return <Settings />;
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Client
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
"use client";
|
|
42
|
+
import { useTheme } from "@rangojs/router/theme";
|
|
43
|
+
|
|
44
|
+
function ThemeToggle() {
|
|
45
|
+
const { theme, setTheme, resolvedTheme, systemTheme, themes } = useTheme();
|
|
46
|
+
return <button onClick={() => setTheme("dark")}>Dark</button>;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Notes
|
|
51
|
+
|
|
52
|
+
- `<MetaTags />` auto-renders inline script for FOUC prevention
|
|
53
|
+
- Add `suppressHydrationWarning` to `<html>`
|
|
54
|
+
- Theme persists in localStorage + cookie (for SSR)
|