@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.
Files changed (155) hide show
  1. package/CLAUDE.md +7 -0
  2. package/README.md +19 -0
  3. package/dist/vite/index.js +1298 -0
  4. package/package.json +140 -0
  5. package/skills/caching/SKILL.md +319 -0
  6. package/skills/document-cache/SKILL.md +152 -0
  7. package/skills/hooks/SKILL.md +359 -0
  8. package/skills/intercept/SKILL.md +292 -0
  9. package/skills/layout/SKILL.md +216 -0
  10. package/skills/loader/SKILL.md +365 -0
  11. package/skills/middleware/SKILL.md +442 -0
  12. package/skills/parallel/SKILL.md +255 -0
  13. package/skills/route/SKILL.md +141 -0
  14. package/skills/router-setup/SKILL.md +403 -0
  15. package/skills/theme/SKILL.md +54 -0
  16. package/skills/typesafety/SKILL.md +352 -0
  17. package/src/__mocks__/version.ts +6 -0
  18. package/src/__tests__/component-utils.test.ts +76 -0
  19. package/src/__tests__/route-definition.test.ts +63 -0
  20. package/src/__tests__/urls.test.tsx +436 -0
  21. package/src/browser/event-controller.ts +876 -0
  22. package/src/browser/index.ts +18 -0
  23. package/src/browser/link-interceptor.ts +121 -0
  24. package/src/browser/lru-cache.ts +69 -0
  25. package/src/browser/merge-segment-loaders.ts +126 -0
  26. package/src/browser/navigation-bridge.ts +893 -0
  27. package/src/browser/navigation-client.ts +162 -0
  28. package/src/browser/navigation-store.ts +823 -0
  29. package/src/browser/partial-update.ts +559 -0
  30. package/src/browser/react/Link.tsx +248 -0
  31. package/src/browser/react/NavigationProvider.tsx +275 -0
  32. package/src/browser/react/ScrollRestoration.tsx +94 -0
  33. package/src/browser/react/context.ts +53 -0
  34. package/src/browser/react/index.ts +52 -0
  35. package/src/browser/react/location-state-shared.ts +120 -0
  36. package/src/browser/react/location-state.ts +62 -0
  37. package/src/browser/react/use-action.ts +240 -0
  38. package/src/browser/react/use-client-cache.ts +56 -0
  39. package/src/browser/react/use-handle.ts +178 -0
  40. package/src/browser/react/use-href.tsx +208 -0
  41. package/src/browser/react/use-link-status.ts +134 -0
  42. package/src/browser/react/use-navigation.ts +150 -0
  43. package/src/browser/react/use-segments.ts +188 -0
  44. package/src/browser/request-controller.ts +164 -0
  45. package/src/browser/rsc-router.tsx +353 -0
  46. package/src/browser/scroll-restoration.ts +324 -0
  47. package/src/browser/server-action-bridge.ts +747 -0
  48. package/src/browser/shallow.ts +35 -0
  49. package/src/browser/types.ts +464 -0
  50. package/src/cache/__tests__/document-cache.test.ts +522 -0
  51. package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
  52. package/src/cache/__tests__/memory-store.test.ts +484 -0
  53. package/src/cache/cache-scope.ts +565 -0
  54. package/src/cache/cf/__tests__/cf-cache-store.test.ts +428 -0
  55. package/src/cache/cf/cf-cache-store.ts +428 -0
  56. package/src/cache/cf/index.ts +19 -0
  57. package/src/cache/document-cache.ts +340 -0
  58. package/src/cache/index.ts +58 -0
  59. package/src/cache/memory-segment-store.ts +150 -0
  60. package/src/cache/memory-store.ts +253 -0
  61. package/src/cache/types.ts +387 -0
  62. package/src/client.rsc.tsx +88 -0
  63. package/src/client.tsx +621 -0
  64. package/src/component-utils.ts +76 -0
  65. package/src/components/DefaultDocument.tsx +23 -0
  66. package/src/default-error-boundary.tsx +88 -0
  67. package/src/deps/browser.ts +8 -0
  68. package/src/deps/html-stream-client.ts +2 -0
  69. package/src/deps/html-stream-server.ts +2 -0
  70. package/src/deps/rsc.ts +10 -0
  71. package/src/deps/ssr.ts +2 -0
  72. package/src/errors.ts +259 -0
  73. package/src/handle.ts +120 -0
  74. package/src/handles/MetaTags.tsx +193 -0
  75. package/src/handles/index.ts +6 -0
  76. package/src/handles/meta.ts +247 -0
  77. package/src/href-client.ts +128 -0
  78. package/src/href-context.ts +33 -0
  79. package/src/href.ts +177 -0
  80. package/src/index.rsc.ts +79 -0
  81. package/src/index.ts +87 -0
  82. package/src/loader.rsc.ts +204 -0
  83. package/src/loader.ts +47 -0
  84. package/src/network-error-thrower.tsx +21 -0
  85. package/src/outlet-context.ts +15 -0
  86. package/src/root-error-boundary.tsx +277 -0
  87. package/src/route-content-wrapper.tsx +198 -0
  88. package/src/route-definition.ts +1371 -0
  89. package/src/route-map-builder.ts +146 -0
  90. package/src/route-types.ts +198 -0
  91. package/src/route-utils.ts +89 -0
  92. package/src/router/__tests__/match-context.test.ts +104 -0
  93. package/src/router/__tests__/match-pipelines.test.ts +537 -0
  94. package/src/router/__tests__/match-result.test.ts +566 -0
  95. package/src/router/__tests__/on-error.test.ts +935 -0
  96. package/src/router/__tests__/pattern-matching.test.ts +577 -0
  97. package/src/router/error-handling.ts +287 -0
  98. package/src/router/handler-context.ts +158 -0
  99. package/src/router/loader-resolution.ts +326 -0
  100. package/src/router/manifest.ts +138 -0
  101. package/src/router/match-context.ts +264 -0
  102. package/src/router/match-middleware/background-revalidation.ts +236 -0
  103. package/src/router/match-middleware/cache-lookup.ts +261 -0
  104. package/src/router/match-middleware/cache-store.ts +266 -0
  105. package/src/router/match-middleware/index.ts +81 -0
  106. package/src/router/match-middleware/intercept-resolution.ts +268 -0
  107. package/src/router/match-middleware/segment-resolution.ts +174 -0
  108. package/src/router/match-pipelines.ts +214 -0
  109. package/src/router/match-result.ts +214 -0
  110. package/src/router/metrics.ts +62 -0
  111. package/src/router/middleware.test.ts +1355 -0
  112. package/src/router/middleware.ts +748 -0
  113. package/src/router/pattern-matching.ts +272 -0
  114. package/src/router/revalidation.ts +190 -0
  115. package/src/router/router-context.ts +299 -0
  116. package/src/router/types.ts +96 -0
  117. package/src/router.ts +3876 -0
  118. package/src/rsc/__tests__/helpers.test.ts +175 -0
  119. package/src/rsc/handler.ts +1060 -0
  120. package/src/rsc/helpers.ts +64 -0
  121. package/src/rsc/index.ts +56 -0
  122. package/src/rsc/nonce.ts +18 -0
  123. package/src/rsc/types.ts +237 -0
  124. package/src/segment-system.tsx +456 -0
  125. package/src/server/__tests__/request-context.test.ts +171 -0
  126. package/src/server/context.ts +417 -0
  127. package/src/server/handle-store.ts +230 -0
  128. package/src/server/loader-registry.ts +174 -0
  129. package/src/server/request-context.ts +554 -0
  130. package/src/server/root-layout.tsx +10 -0
  131. package/src/server/tsconfig.json +14 -0
  132. package/src/server.ts +146 -0
  133. package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
  134. package/src/ssr/index.tsx +234 -0
  135. package/src/theme/ThemeProvider.tsx +291 -0
  136. package/src/theme/ThemeScript.tsx +61 -0
  137. package/src/theme/__tests__/theme.test.ts +120 -0
  138. package/src/theme/constants.ts +55 -0
  139. package/src/theme/index.ts +58 -0
  140. package/src/theme/theme-context.ts +70 -0
  141. package/src/theme/theme-script.ts +152 -0
  142. package/src/theme/types.ts +182 -0
  143. package/src/theme/use-theme.ts +44 -0
  144. package/src/types.ts +1561 -0
  145. package/src/urls.ts +726 -0
  146. package/src/use-loader.tsx +346 -0
  147. package/src/vite/__tests__/expose-loader-id.test.ts +117 -0
  148. package/src/vite/expose-action-id.ts +344 -0
  149. package/src/vite/expose-handle-id.ts +209 -0
  150. package/src/vite/expose-loader-id.ts +357 -0
  151. package/src/vite/expose-location-state-id.ts +177 -0
  152. package/src/vite/index.ts +787 -0
  153. package/src/vite/package-resolution.ts +125 -0
  154. package/src/vite/version.d.ts +12 -0
  155. 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)