@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.
Files changed (172) hide show
  1. package/CLAUDE.md +43 -0
  2. package/README.md +19 -0
  3. package/dist/bin/rango.js +227 -0
  4. package/dist/vite/index.js +3039 -0
  5. package/package.json +171 -0
  6. package/skills/caching/SKILL.md +191 -0
  7. package/skills/debug-manifest/SKILL.md +108 -0
  8. package/skills/document-cache/SKILL.md +180 -0
  9. package/skills/fonts/SKILL.md +165 -0
  10. package/skills/hooks/SKILL.md +442 -0
  11. package/skills/intercept/SKILL.md +190 -0
  12. package/skills/layout/SKILL.md +213 -0
  13. package/skills/links/SKILL.md +180 -0
  14. package/skills/loader/SKILL.md +246 -0
  15. package/skills/middleware/SKILL.md +202 -0
  16. package/skills/mime-routes/SKILL.md +124 -0
  17. package/skills/parallel/SKILL.md +228 -0
  18. package/skills/prerender/SKILL.md +283 -0
  19. package/skills/rango/SKILL.md +54 -0
  20. package/skills/response-routes/SKILL.md +358 -0
  21. package/skills/route/SKILL.md +173 -0
  22. package/skills/router-setup/SKILL.md +346 -0
  23. package/skills/tailwind/SKILL.md +129 -0
  24. package/skills/theme/SKILL.md +78 -0
  25. package/skills/typesafety/SKILL.md +394 -0
  26. package/src/__internal.ts +175 -0
  27. package/src/bin/rango.ts +24 -0
  28. package/src/browser/event-controller.ts +876 -0
  29. package/src/browser/index.ts +18 -0
  30. package/src/browser/link-interceptor.ts +121 -0
  31. package/src/browser/lru-cache.ts +69 -0
  32. package/src/browser/merge-segment-loaders.ts +126 -0
  33. package/src/browser/navigation-bridge.ts +913 -0
  34. package/src/browser/navigation-client.ts +165 -0
  35. package/src/browser/navigation-store.ts +823 -0
  36. package/src/browser/partial-update.ts +600 -0
  37. package/src/browser/react/Link.tsx +248 -0
  38. package/src/browser/react/NavigationProvider.tsx +346 -0
  39. package/src/browser/react/ScrollRestoration.tsx +94 -0
  40. package/src/browser/react/context.ts +53 -0
  41. package/src/browser/react/index.ts +52 -0
  42. package/src/browser/react/location-state-shared.ts +120 -0
  43. package/src/browser/react/location-state.ts +62 -0
  44. package/src/browser/react/mount-context.ts +32 -0
  45. package/src/browser/react/use-action.ts +240 -0
  46. package/src/browser/react/use-client-cache.ts +56 -0
  47. package/src/browser/react/use-handle.ts +203 -0
  48. package/src/browser/react/use-href.tsx +40 -0
  49. package/src/browser/react/use-link-status.ts +134 -0
  50. package/src/browser/react/use-mount.ts +31 -0
  51. package/src/browser/react/use-navigation.ts +140 -0
  52. package/src/browser/react/use-segments.ts +188 -0
  53. package/src/browser/request-controller.ts +164 -0
  54. package/src/browser/rsc-router.tsx +352 -0
  55. package/src/browser/scroll-restoration.ts +324 -0
  56. package/src/browser/segment-structure-assert.ts +67 -0
  57. package/src/browser/server-action-bridge.ts +762 -0
  58. package/src/browser/shallow.ts +35 -0
  59. package/src/browser/types.ts +478 -0
  60. package/src/build/generate-manifest.ts +377 -0
  61. package/src/build/generate-route-types.ts +828 -0
  62. package/src/build/index.ts +36 -0
  63. package/src/build/route-trie.ts +239 -0
  64. package/src/cache/cache-scope.ts +563 -0
  65. package/src/cache/cf/cf-cache-store.ts +428 -0
  66. package/src/cache/cf/index.ts +19 -0
  67. package/src/cache/document-cache.ts +340 -0
  68. package/src/cache/index.ts +58 -0
  69. package/src/cache/memory-segment-store.ts +150 -0
  70. package/src/cache/memory-store.ts +253 -0
  71. package/src/cache/types.ts +392 -0
  72. package/src/client.rsc.tsx +83 -0
  73. package/src/client.tsx +643 -0
  74. package/src/component-utils.ts +76 -0
  75. package/src/components/DefaultDocument.tsx +23 -0
  76. package/src/debug.ts +233 -0
  77. package/src/default-error-boundary.tsx +88 -0
  78. package/src/deps/browser.ts +8 -0
  79. package/src/deps/html-stream-client.ts +2 -0
  80. package/src/deps/html-stream-server.ts +2 -0
  81. package/src/deps/rsc.ts +10 -0
  82. package/src/deps/ssr.ts +2 -0
  83. package/src/errors.ts +295 -0
  84. package/src/handle.ts +130 -0
  85. package/src/handles/MetaTags.tsx +193 -0
  86. package/src/handles/index.ts +6 -0
  87. package/src/handles/meta.ts +247 -0
  88. package/src/host/cookie-handler.ts +159 -0
  89. package/src/host/errors.ts +97 -0
  90. package/src/host/index.ts +56 -0
  91. package/src/host/pattern-matcher.ts +214 -0
  92. package/src/host/router.ts +330 -0
  93. package/src/host/testing.ts +79 -0
  94. package/src/host/types.ts +138 -0
  95. package/src/host/utils.ts +25 -0
  96. package/src/href-client.ts +202 -0
  97. package/src/href-context.ts +33 -0
  98. package/src/index.rsc.ts +121 -0
  99. package/src/index.ts +165 -0
  100. package/src/loader.rsc.ts +207 -0
  101. package/src/loader.ts +47 -0
  102. package/src/network-error-thrower.tsx +21 -0
  103. package/src/outlet-context.ts +15 -0
  104. package/src/prerender/param-hash.ts +35 -0
  105. package/src/prerender/store.ts +40 -0
  106. package/src/prerender.ts +156 -0
  107. package/src/reverse.ts +267 -0
  108. package/src/root-error-boundary.tsx +277 -0
  109. package/src/route-content-wrapper.tsx +193 -0
  110. package/src/route-definition.ts +1431 -0
  111. package/src/route-map-builder.ts +242 -0
  112. package/src/route-types.ts +220 -0
  113. package/src/router/error-handling.ts +287 -0
  114. package/src/router/handler-context.ts +158 -0
  115. package/src/router/intercept-resolution.ts +387 -0
  116. package/src/router/loader-resolution.ts +327 -0
  117. package/src/router/manifest.ts +216 -0
  118. package/src/router/match-api.ts +621 -0
  119. package/src/router/match-context.ts +264 -0
  120. package/src/router/match-middleware/background-revalidation.ts +236 -0
  121. package/src/router/match-middleware/cache-lookup.ts +382 -0
  122. package/src/router/match-middleware/cache-store.ts +276 -0
  123. package/src/router/match-middleware/index.ts +81 -0
  124. package/src/router/match-middleware/intercept-resolution.ts +281 -0
  125. package/src/router/match-middleware/segment-resolution.ts +184 -0
  126. package/src/router/match-pipelines.ts +214 -0
  127. package/src/router/match-result.ts +213 -0
  128. package/src/router/metrics.ts +62 -0
  129. package/src/router/middleware.ts +791 -0
  130. package/src/router/pattern-matching.ts +407 -0
  131. package/src/router/revalidation.ts +190 -0
  132. package/src/router/router-context.ts +301 -0
  133. package/src/router/segment-resolution.ts +1315 -0
  134. package/src/router/trie-matching.ts +172 -0
  135. package/src/router/types.ts +163 -0
  136. package/src/router.gen.ts +6 -0
  137. package/src/router.ts +2423 -0
  138. package/src/rsc/handler.ts +1443 -0
  139. package/src/rsc/helpers.ts +64 -0
  140. package/src/rsc/index.ts +56 -0
  141. package/src/rsc/nonce.ts +18 -0
  142. package/src/rsc/types.ts +236 -0
  143. package/src/segment-system.tsx +442 -0
  144. package/src/server/context.ts +466 -0
  145. package/src/server/handle-store.ts +229 -0
  146. package/src/server/loader-registry.ts +174 -0
  147. package/src/server/request-context.ts +554 -0
  148. package/src/server/root-layout.tsx +10 -0
  149. package/src/server/tsconfig.json +14 -0
  150. package/src/server.ts +171 -0
  151. package/src/ssr/index.tsx +296 -0
  152. package/src/theme/ThemeProvider.tsx +291 -0
  153. package/src/theme/ThemeScript.tsx +61 -0
  154. package/src/theme/constants.ts +59 -0
  155. package/src/theme/index.ts +58 -0
  156. package/src/theme/theme-context.ts +70 -0
  157. package/src/theme/theme-script.ts +152 -0
  158. package/src/theme/types.ts +182 -0
  159. package/src/theme/use-theme.ts +44 -0
  160. package/src/types.ts +1757 -0
  161. package/src/urls.gen.ts +8 -0
  162. package/src/urls.ts +1282 -0
  163. package/src/use-loader.tsx +346 -0
  164. package/src/vite/expose-action-id.ts +344 -0
  165. package/src/vite/expose-handle-id.ts +209 -0
  166. package/src/vite/expose-loader-id.ts +426 -0
  167. package/src/vite/expose-location-state-id.ts +177 -0
  168. package/src/vite/expose-prerender-handler-id.ts +429 -0
  169. package/src/vite/index.ts +2068 -0
  170. package/src/vite/package-resolution.ts +125 -0
  171. package/src/vite/version.d.ts +12 -0
  172. package/src/vite/virtual-entries.ts +114 -0
@@ -0,0 +1,346 @@
1
+ ---
2
+ name: router-setup
3
+ description: Create and configure the RSC router with createRouter
4
+ argument-hint: [option]
5
+ ---
6
+
7
+ # Router Setup with createRouter
8
+
9
+ ## Basic Router Creation
10
+
11
+ ```typescript
12
+ // src/router.tsx
13
+ import { createRouter } from "@rangojs/router";
14
+ import { Document } from "./document";
15
+ import { urlpatterns } from "./urls";
16
+
17
+ const router = createRouter({
18
+ document: Document,
19
+ urls: urlpatterns,
20
+ });
21
+
22
+ export default router;
23
+ ```
24
+
25
+ ## URL Patterns (Django-style)
26
+
27
+ ```typescript
28
+ // src/urls.tsx
29
+ import { urls } from "@rangojs/router";
30
+ import { HomePage } from "./pages/home";
31
+ import { AboutPage } from "./pages/about";
32
+ import { ProductPage } from "./pages/product";
33
+ import { RootLayout } from "./layouts/RootLayout";
34
+
35
+ export const urlpatterns = urls(({ path, layout, loader, loading }) => [
36
+ path("/", HomePage, { name: "home" }),
37
+ path("/about", AboutPage, { name: "about" }),
38
+
39
+ layout(<RootLayout />, () => [
40
+ path("/product/:slug", ProductPage, { name: "product" }, () => [
41
+ loader(ProductLoader),
42
+ loading(<ProductSkeleton />),
43
+ ]),
44
+ ]),
45
+ ]);
46
+ ```
47
+
48
+ ## The urls() DSL
49
+
50
+ The `urls()` function provides a callback with all available DSL functions:
51
+
52
+ ```typescript
53
+ urls(({
54
+ path, // Define a route
55
+ layout, // Wrap routes in a layout
56
+ parallel, // Define parallel routes (slots)
57
+ loader, // Add data loader
58
+ loading, // Add loading skeleton
59
+ cache, // Configure caching
60
+ middleware, // Add middleware
61
+ revalidate, // Control revalidation
62
+ intercept, // Intercept routes for modals
63
+ when, // Conditional rendering
64
+ }) => [
65
+ // Route definitions here
66
+ ]);
67
+ ```
68
+
69
+ ## Router Options
70
+
71
+ ```typescript
72
+ interface RSCRouterOptions<TEnv> {
73
+ // URL patterns from urls() function
74
+ urls: UrlPatterns;
75
+
76
+ // Document component wrapping entire app
77
+ document?: ComponentType<{ children: ReactNode }>;
78
+
79
+ // Enable performance metrics
80
+ debugPerformance?: boolean;
81
+
82
+ // Default error boundary
83
+ defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler;
84
+
85
+ // Default not-found boundary
86
+ defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler;
87
+
88
+ // Component for 404 routes
89
+ notFound?: ReactNode | ((props: { pathname: string }) => ReactNode);
90
+
91
+ // Error logging callback
92
+ onError?: OnErrorCallback<TEnv>;
93
+
94
+ // Global cache configuration
95
+ cache?: CacheConfig<TEnv>;
96
+
97
+ // Theme configuration
98
+ theme?: ThemeConfig | true;
99
+
100
+ // Connection warmup (default: true)
101
+ warmup?: boolean;
102
+
103
+ // CSP nonce provider (for router.fetch)
104
+ nonce?: (request: Request, env: TEnv) => string | true | Promise<string | true>;
105
+
106
+ // RSC version string (for router.fetch)
107
+ version?: string;
108
+ }
109
+ ```
110
+
111
+ ## Using the Request Handler
112
+
113
+ The router provides a `fetch` method to handle RSC requests:
114
+
115
+ ```typescript
116
+ // src/router.tsx
117
+ import { createRouter } from "@rangojs/router";
118
+ import { Document } from "./document";
119
+ import { urlpatterns } from "./urls";
120
+
121
+ export const router = createRouter({
122
+ document: Document,
123
+ urls: urlpatterns,
124
+ nonce: () => true, // Auto-generate nonce for CSP
125
+ });
126
+
127
+ // src/worker.tsx (Cloudflare Workers)
128
+ import { router } from "./router";
129
+
130
+ export default { fetch: router.fetch };
131
+ ```
132
+
133
+ ## Document Component
134
+
135
+ ```typescript
136
+ // src/document.tsx
137
+ import type { ReactNode } from "react";
138
+
139
+ export function Document({ children }: { children: ReactNode }) {
140
+ return (
141
+ <html lang="en">
142
+ <head>
143
+ <meta charSet="utf-8" />
144
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
145
+ <title>My App</title>
146
+ </head>
147
+ <body>
148
+ <div id="root">{children}</div>
149
+ </body>
150
+ </html>
151
+ );
152
+ }
153
+ ```
154
+
155
+ ## Using with Cloudflare Workers
156
+
157
+ ```typescript
158
+ // src/router.tsx
159
+ import { createRouter } from "@rangojs/router";
160
+ import { Document } from "./document";
161
+ import { urlpatterns } from "./urls";
162
+
163
+ export const router = createRouter<AppEnv>({
164
+ document: Document,
165
+ urls: urlpatterns,
166
+ });
167
+
168
+ // src/worker.tsx
169
+ import { router } from "./router";
170
+
171
+ export default {
172
+ async fetch(request: Request, env: Env, ctx: ExecutionContext) {
173
+ return router.fetch(request, { Bindings: env, Variables: {}, ctx });
174
+ },
175
+ };
176
+ ```
177
+
178
+ ### With Dynamic Cache Configuration
179
+
180
+ For per-request cache configuration (e.g., Cloudflare Workers with ExecutionContext):
181
+
182
+ ```typescript
183
+ // src/router.tsx
184
+ import { createRouter } from "@rangojs/router";
185
+ import { CFCacheStore } from "@rangojs/router/cache";
186
+
187
+ export const router = createRouter<AppEnv>({
188
+ document: Document,
189
+ urls: urlpatterns,
190
+ // Cache config receives env with ctx for ExecutionContext access
191
+ cache: (env) => ({
192
+ store: new CFCacheStore({ ctx: env.ctx, defaults: { ttl: 60 } }),
193
+ }),
194
+ });
195
+
196
+ // src/worker.tsx
197
+ import { router } from "./router";
198
+
199
+ export default {
200
+ async fetch(request: Request, env: Env, ctx: ExecutionContext) {
201
+ return router.fetch(request, { Bindings: env, Variables: {}, ctx });
202
+ },
203
+ };
204
+ ```
205
+
206
+ ## Complete Example
207
+
208
+ ```typescript
209
+ // src/urls.tsx
210
+ import { urls } from "@rangojs/router";
211
+ import { Outlet } from "@rangojs/router/client";
212
+
213
+ // Pages
214
+ import { HomePage } from "./pages/home";
215
+ import { AboutPage } from "./pages/about";
216
+ import { BlogIndexPage, BlogPostPage } from "./pages/blog";
217
+
218
+ // Layouts
219
+ import { RootLayout } from "./layouts/RootLayout";
220
+ import { BlogLayout } from "./layouts/BlogLayout";
221
+
222
+ // Loaders
223
+ import { BlogPostLoader, BlogSidebarLoader } from "./loaders/blog";
224
+
225
+ export const urlpatterns = urls(({ path, layout, parallel, loader, loading, cache }) => [
226
+ // Simple routes
227
+ path("/", HomePage, { name: "home" }),
228
+ path("/about", AboutPage, { name: "about" }),
229
+
230
+ // Blog with layout and loaders
231
+ layout(<BlogLayout />, () => [
232
+ // Sidebar as parallel route
233
+ parallel({ "@sidebar": () => <BlogSidebar /> }, () => [
234
+ loader(BlogSidebarLoader),
235
+ ]),
236
+
237
+ // Cached blog routes
238
+ cache({ ttl: 60 }, () => [
239
+ path("/blog", BlogIndexPage, { name: "blog" }),
240
+ path("/blog/:slug", BlogPostPage, { name: "blogPost" }, () => [
241
+ loader(BlogPostLoader),
242
+ loading(<BlogPostSkeleton />),
243
+ ]),
244
+ ]),
245
+ ]),
246
+ ]);
247
+ ```
248
+
249
+ ```typescript
250
+ // src/router.tsx
251
+ import { createRouter } from "@rangojs/router";
252
+ import { Document } from "./document";
253
+ import { urlpatterns } from "./urls";
254
+
255
+ const router = createRouter({
256
+ document: Document,
257
+ urls: urlpatterns,
258
+
259
+ defaultErrorBoundary: ({ error, reset }) => (
260
+ <div>
261
+ <h1>Something went wrong</h1>
262
+ <button onClick={reset}>Try again</button>
263
+ </div>
264
+ ),
265
+
266
+ notFound: ({ pathname }) => (
267
+ <div>
268
+ <h1>404</h1>
269
+ <p>Page not found: {pathname}</p>
270
+ </div>
271
+ ),
272
+ });
273
+
274
+ export default router;
275
+ ```
276
+
277
+ ## Including Sub-patterns
278
+
279
+ ```typescript
280
+ // src/urls/shop.tsx
281
+ import { urls } from "@rangojs/router";
282
+
283
+ export const shopPatterns = urls(({ path, layout }) => [
284
+ path("/", ShopIndex, { name: "index" }),
285
+ path("/product/:slug", ProductPage, { name: "product" }),
286
+ ]);
287
+
288
+ // src/urls.tsx
289
+ import { urls, include } from "@rangojs/router";
290
+ import { shopPatterns } from "./urls/shop";
291
+
292
+ export const urlpatterns = urls(({ path }) => [
293
+ path("/", HomePage, { name: "home" }),
294
+ include("/shop", shopPatterns, { name: "shop" }),
295
+ ]);
296
+ ```
297
+
298
+ ## Environment Types
299
+
300
+ ```typescript
301
+ import type { RouterEnv } from "@rangojs/router";
302
+
303
+ interface AppBindings {
304
+ DB: D1Database;
305
+ KV: KVNamespace;
306
+ }
307
+
308
+ interface AppVariables {
309
+ user?: { id: string; name: string };
310
+ }
311
+
312
+ type AppEnv = RouterEnv<AppBindings, AppVariables>;
313
+
314
+ const router = createRouter<AppEnv>({
315
+ document: Document,
316
+ urls: urlpatterns,
317
+ });
318
+ ```
319
+
320
+ ## Connection Warmup
321
+
322
+ Enabled by default. Keeps TCP+TLS connections alive so navigations after idle periods
323
+ don't pay handshake costs.
324
+
325
+ After 60s of no user interaction, the connection is marked cold. When the user returns
326
+ (tab becomes visible or first mouse/touch), a `HEAD ?_rsc_warmup` request re-establishes
327
+ the TLS connection before the next navigation. The server responds with 204 No Content
328
+ before any middleware or routing runs.
329
+
330
+ ```typescript
331
+ // Enabled by default
332
+ const router = createRouter({
333
+ document: Document,
334
+ urls: urlpatterns,
335
+ });
336
+
337
+ // Disable warmup
338
+ const router = createRouter({
339
+ document: Document,
340
+ urls: urlpatterns,
341
+ warmup: false,
342
+ });
343
+ ```
344
+
345
+ The warmup request is relative to the current page path, so it works correctly
346
+ with subpath deployments (reverse proxy, base path).
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: tailwind
3
+ description: Set up Tailwind CSS v4 with the Document component and CSS imports
4
+ argument-hint: [setup]
5
+ ---
6
+
7
+ # Tailwind CSS
8
+
9
+ Set up Tailwind CSS v4 with the Rango router. Styles are loaded through the Document component using Vite's `?url` CSS import.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pnpm add -D tailwindcss @tailwindcss/vite
15
+ ```
16
+
17
+ ## Vite Plugin
18
+
19
+ ```typescript
20
+ // vite.config.ts
21
+ import tailwindcss from "@tailwindcss/vite";
22
+
23
+ export default defineConfig({
24
+ plugins: [
25
+ tailwindcss(),
26
+ // ... other plugins
27
+ ],
28
+ });
29
+ ```
30
+
31
+ ## CSS Entry Point
32
+
33
+ ```css
34
+ /* src/index.css */
35
+ @import "tailwindcss";
36
+ ```
37
+
38
+ ## Document Component
39
+
40
+ Import the CSS file with `?url` to get a hashed URL, then preload and link it in `<head>`:
41
+
42
+ ```tsx
43
+ // src/document.tsx
44
+ "use client";
45
+
46
+ import type { ReactNode } from "react";
47
+ import { MetaTags } from "@rangojs/router/client";
48
+ import styles from "./index.css?url";
49
+
50
+ export function Document({ children }: { children: ReactNode }) {
51
+ return (
52
+ <html lang="en">
53
+ <head>
54
+ <link rel="preload" href={styles} as="style" />
55
+ <link rel="stylesheet" href={styles} />
56
+ <MetaTags />
57
+ </head>
58
+ <body className="font-sans antialiased text-slate-900 bg-slate-50">
59
+ {children}
60
+ </body>
61
+ </html>
62
+ );
63
+ }
64
+ ```
65
+
66
+ The `?url` suffix tells Vite to return the processed CSS file's URL instead of injecting it as a side effect. This gives you a stable, hashed asset path that works in both development and production.
67
+
68
+ ## Customizing the Theme
69
+
70
+ Tailwind v4 uses CSS `@theme` for customization:
71
+
72
+ ```css
73
+ /* src/index.css */
74
+ @import "tailwindcss";
75
+
76
+ @theme {
77
+ --font-sans: "Inter", system-ui, sans-serif;
78
+ --color-primary: #3b82f6;
79
+ --color-secondary: #64748b;
80
+ --breakpoint-3xl: 1920px;
81
+ }
82
+ ```
83
+
84
+ ## Dark Mode
85
+
86
+ Combine with the Rango theme system (see `/theme`):
87
+
88
+ ```typescript
89
+ const router = createRouter({
90
+ document: Document,
91
+ urls: urlpatterns,
92
+ theme: { attribute: "class" },
93
+ });
94
+ ```
95
+
96
+ Then use Tailwind's `dark:` variant which reads the `class` attribute:
97
+
98
+ ```tsx
99
+ <div className="bg-white dark:bg-slate-900 text-slate-900 dark:text-white">
100
+ Content
101
+ </div>
102
+ ```
103
+
104
+ ## With Custom Fonts
105
+
106
+ Use `@fontsource-variable` for self-hosted fonts bundled by Vite (see `/fonts` for all options):
107
+
108
+ ```bash
109
+ pnpm add @fontsource-variable/inter
110
+ ```
111
+
112
+ ```css
113
+ /* src/index.css */
114
+ @import "@fontsource-variable/inter";
115
+ @import "tailwindcss";
116
+
117
+ @theme {
118
+ --font-sans: "Inter Variable", system-ui, sans-serif;
119
+ }
120
+ ```
121
+
122
+ No extra `<link>` tags needed in the Document -- Vite bundles the font files from `node_modules` automatically.
123
+
124
+ ## Notes
125
+
126
+ - `?url` import is required -- bare CSS imports inject styles as a side effect and do not work with SSR streaming
127
+ - `<link rel="preload" as="style">` eliminates render-blocking by starting the download early
128
+ - Tailwind v4 does not need a `tailwind.config.js` -- use `@theme` in CSS instead
129
+ - The `@tailwindcss/vite` plugin handles content detection automatically
@@ -0,0 +1,78 @@
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
+ import { createRouter } from "@rangojs/router";
15
+
16
+ // Simple - all defaults
17
+ const router = createRouter<Env>({
18
+ document: Document,
19
+ urls: urlpatterns,
20
+ theme: true,
21
+ });
22
+
23
+ // Custom config
24
+ const router = createRouter<Env>({
25
+ document: Document,
26
+ urls: urlpatterns,
27
+ theme: {
28
+ defaultTheme: "system", // "light" | "dark" | "system"
29
+ themes: ["light", "dark"],
30
+ attribute: "class", // or "data-theme"
31
+ storageKey: "theme",
32
+ }
33
+ });
34
+ ```
35
+
36
+ ## Server (in loaders/middleware)
37
+
38
+ ```typescript
39
+ import { createLoader, createMiddleware } from "@rangojs/router";
40
+
41
+ // In a loader
42
+ export const SettingsLoader = createLoader("settings", async (ctx) => {
43
+ const currentTheme = ctx.theme; // read from cookie
44
+ return { theme: currentTheme };
45
+ });
46
+
47
+ // In middleware
48
+ export const themeMiddleware = createMiddleware(async (ctx, next) => {
49
+ // Set theme based on user preference
50
+ ctx.setTheme("dark");
51
+ await next();
52
+ });
53
+ ```
54
+
55
+ ## Client
56
+
57
+ ```tsx
58
+ "use client";
59
+ import { useTheme } from "@rangojs/router/theme";
60
+
61
+ function ThemeToggle() {
62
+ const { theme, setTheme, resolvedTheme, systemTheme, themes } = useTheme();
63
+
64
+ return (
65
+ <select value={theme} onChange={(e) => setTheme(e.target.value)}>
66
+ <option value="system">System</option>
67
+ <option value="light">Light</option>
68
+ <option value="dark">Dark</option>
69
+ </select>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## Notes
75
+
76
+ - `<MetaTags />` auto-renders inline script for FOUC prevention
77
+ - Add `suppressHydrationWarning` to `<html>`
78
+ - Theme persists in localStorage + cookie (for SSR)