@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,165 @@
1
+ ---
2
+ name: fonts
3
+ description: Load and configure web fonts with preload hints for optimal performance
4
+ argument-hint: [provider]
5
+ ---
6
+
7
+ # Fonts
8
+
9
+ Load web fonts in the Document component with `<link rel="preload">` for optimal performance. Fonts are declared in `<head>` alongside your stylesheet.
10
+
11
+ ## Google Fonts
12
+
13
+ ```tsx
14
+ // src/document.tsx
15
+ "use client";
16
+
17
+ import type { ReactNode } from "react";
18
+ import { MetaTags } from "@rangojs/router/client";
19
+ import styles from "./index.css?url";
20
+
21
+ export function Document({ children }: { children: ReactNode }) {
22
+ return (
23
+ <html lang="en">
24
+ <head>
25
+ {/* Preconnect to Google Fonts */}
26
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
27
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
28
+
29
+ {/* Load font stylesheet */}
30
+ <link
31
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
32
+ rel="stylesheet"
33
+ />
34
+
35
+ {/* App styles */}
36
+ <link rel="preload" href={styles} as="style" />
37
+ <link rel="stylesheet" href={styles} />
38
+ <MetaTags />
39
+ </head>
40
+ <body>
41
+ {children}
42
+ </body>
43
+ </html>
44
+ );
45
+ }
46
+ ```
47
+
48
+ Then reference the font in CSS:
49
+
50
+ ```css
51
+ /* src/index.css */
52
+ body {
53
+ font-family: "Inter", sans-serif;
54
+ }
55
+ ```
56
+
57
+ Or with Tailwind (see `/tailwind`):
58
+
59
+ ```css
60
+ /* src/index.css */
61
+ @theme {
62
+ --font-sans: "Inter", sans-serif;
63
+ }
64
+ ```
65
+
66
+ ## Self-Hosted Fonts
67
+
68
+ Place font files in `public/fonts/` and use `@font-face`:
69
+
70
+ ```css
71
+ /* src/index.css */
72
+ @font-face {
73
+ font-family: "CustomFont";
74
+ src: url("/fonts/custom-regular.woff2") format("woff2");
75
+ font-weight: 400;
76
+ font-style: normal;
77
+ font-display: swap;
78
+ }
79
+
80
+ @font-face {
81
+ font-family: "CustomFont";
82
+ src: url("/fonts/custom-bold.woff2") format("woff2");
83
+ font-weight: 700;
84
+ font-style: normal;
85
+ font-display: swap;
86
+ }
87
+
88
+ body {
89
+ font-family: "CustomFont", sans-serif;
90
+ }
91
+ ```
92
+
93
+ Preload the most critical weight in the Document:
94
+
95
+ ```tsx
96
+ export function Document({ children }: { children: ReactNode }) {
97
+ return (
98
+ <html lang="en">
99
+ <head>
100
+ <link
101
+ rel="preload"
102
+ href="/fonts/custom-regular.woff2"
103
+ as="font"
104
+ type="font/woff2"
105
+ crossOrigin="anonymous"
106
+ />
107
+ <link rel="preload" href={styles} as="style" />
108
+ <link rel="stylesheet" href={styles} />
109
+ <MetaTags />
110
+ </head>
111
+ <body>{children}</body>
112
+ </html>
113
+ );
114
+ }
115
+ ```
116
+
117
+ ## Fontsource (Recommended for Vite)
118
+
119
+ `@fontsource-variable` packages are the recommended approach with Vite. Fonts are installed as npm dependencies, bundled by Vite, and served from your own domain -- no external requests, no privacy concerns, no FOUT from slow CDNs.
120
+
121
+ ```bash
122
+ pnpm add @fontsource-variable/inter
123
+ ```
124
+
125
+ Import the font CSS in your stylesheet. Vite resolves the `@fontsource-variable` import from `node_modules` and bundles the woff2 files as hashed assets automatically:
126
+
127
+ ```css
128
+ /* src/index.css */
129
+ @import "@fontsource-variable/inter";
130
+
131
+ body {
132
+ font-family: "Inter Variable", sans-serif;
133
+ }
134
+ ```
135
+
136
+ With Tailwind:
137
+
138
+ ```css
139
+ /* src/index.css */
140
+ @import "@fontsource-variable/inter";
141
+ @import "tailwindcss";
142
+
143
+ @theme {
144
+ --font-sans: "Inter Variable", sans-serif;
145
+ }
146
+ ```
147
+
148
+ Why this is preferred over Google Fonts with Vite:
149
+
150
+ - No external network requests at runtime -- fonts are bundled into your build output
151
+ - No `<link rel="preconnect">` or extra stylesheet needed in the Document
152
+ - Variable font = single file covers all weights, smaller total download
153
+ - Vite handles cache-busting via content hashes
154
+ - Works offline and in edge deployments (Cloudflare Workers) without external dependencies
155
+
156
+ Browse available fonts at fontsource.org. Use `@fontsource-variable/*` for variable fonts and `@fontsource/*` for static fonts.
157
+
158
+ ## Performance Tips
159
+
160
+ - Prefer `@fontsource-variable` with Vite for self-hosted, zero-config font loading
161
+ - Use `font-display: swap` to prevent invisible text during font load
162
+ - Preload only the most critical font weight (usually regular 400)
163
+ - Prefer `woff2` format for smaller file sizes
164
+ - Use variable fonts when multiple weights are needed to reduce total file count
165
+ - `<link rel="preconnect">` eliminates DNS + TLS latency for external font providers
@@ -0,0 +1,442 @@
1
+ ---
2
+ name: hooks
3
+ description: Client-side React hooks for navigation, loaders, and state in @rangojs/router
4
+ argument-hint: [hook-name]
5
+ ---
6
+
7
+ # Client-Side React Hooks
8
+
9
+ All hooks are imported from `@rangojs/router` or `@rangojs/router/client`.
10
+
11
+ ## Navigation Hooks
12
+
13
+ ### useNavigation()
14
+
15
+ Track navigation state and control navigation:
16
+
17
+ ```tsx
18
+ "use client";
19
+ import { useNavigation } from "@rangojs/router";
20
+
21
+ function NavIndicator() {
22
+ const nav = useNavigation();
23
+
24
+ // Full state
25
+ nav.state; // 'idle' | 'loading' | 'streaming'
26
+ nav.isStreaming; // boolean
27
+ nav.location; // Current URL
28
+ nav.pendingUrl; // Target URL during navigation (or null)
29
+
30
+ // Methods
31
+ nav.navigate("/products"); // Navigate programmatically
32
+ nav.navigate("/products", { replace: true }); // Replace history
33
+ nav.refresh(); // Refresh current route
34
+
35
+ return nav.state === 'loading' ? <Spinner /> : null;
36
+ }
37
+
38
+ // With selector for performance
39
+ function IsLoading() {
40
+ const isLoading = useNavigation(nav => nav.state === 'loading');
41
+ return isLoading ? <Spinner /> : null;
42
+ }
43
+ ```
44
+
45
+ ### useSegments()
46
+
47
+ Access current URL path and matched route segments:
48
+
49
+ ```tsx
50
+ "use client";
51
+ import { useSegments } from "@rangojs/router";
52
+
53
+ function Breadcrumbs() {
54
+ const { path, segmentIds, location } = useSegments();
55
+
56
+ // path: ["/shop", "products", "123"]
57
+ // segmentIds: ["shop-layout", "products-route"]
58
+ // location: URL object
59
+
60
+ return <nav>{path.join(" > ")}</nav>;
61
+ }
62
+
63
+ // With selector
64
+ const isShopRoute = useSegments(s => s.path[0] === "shop");
65
+ ```
66
+
67
+ ### useLinkStatus()
68
+
69
+ Track pending state inside a Link component:
70
+
71
+ ```tsx
72
+ "use client";
73
+ import { Link, useLinkStatus } from "@rangojs/router/client";
74
+
75
+ function LoadingIndicator() {
76
+ const { pending } = useLinkStatus();
77
+ return pending ? <Spinner /> : null;
78
+ }
79
+
80
+ // Must be inside Link
81
+ <Link to="/dashboard">
82
+ Dashboard
83
+ <LoadingIndicator />
84
+ </Link>
85
+ ```
86
+
87
+ ## Data Hooks
88
+
89
+ ### useLoader()
90
+
91
+ Access loader data (strict - data guaranteed):
92
+
93
+ ```tsx
94
+ "use client";
95
+ import { useLoader } from "@rangojs/router";
96
+ import { ProductLoader } from "../loaders/product";
97
+
98
+ function ProductPrice() {
99
+ const { data, isLoading, error } = useLoader(ProductLoader);
100
+
101
+ // data: T (guaranteed - throws if not in context)
102
+ // isLoading: boolean
103
+ // error: Error | null
104
+
105
+ return <span>${data.price}</span>;
106
+ }
107
+ ```
108
+
109
+ **Precondition**: Loader must be registered on route via `loader()` helper.
110
+
111
+ Loaders can also be passed as props from server to client components:
112
+
113
+ ```tsx
114
+ "use client";
115
+ import { useLoader } from "@rangojs/router/client";
116
+ import type { ProductLoader } from "../loaders";
117
+
118
+ // typeof infers the full data type from the loader definition
119
+ function ProductCard({ loader }: { loader: typeof ProductLoader }) {
120
+ const { data } = useLoader(loader);
121
+ return <h2>{data.product.name}</h2>;
122
+ }
123
+ ```
124
+
125
+ ### useFetchLoader()
126
+
127
+ Access loader with on-demand fetching (flexible):
128
+
129
+ ```tsx
130
+ "use client";
131
+ import { useFetchLoader } from "@rangojs/router";
132
+ import { SearchLoader } from "../loaders/search";
133
+
134
+ function SearchResults() {
135
+ const { data, load, isLoading, error } = useFetchLoader(SearchLoader);
136
+
137
+ // data: T | undefined (may be undefined if not fetched)
138
+ // load: (options?) => Promise<T>
139
+ // refetch: alias for load
140
+
141
+ const handleSearch = async (query: string) => {
142
+ await load({ params: { query } });
143
+ };
144
+
145
+ return (
146
+ <div>
147
+ <input onChange={(e) => handleSearch(e.target.value)} />
148
+ {isLoading && <Spinner />}
149
+ {data?.results.map(r => <Result key={r.id} {...r} />)}
150
+ </div>
151
+ );
152
+ }
153
+ ```
154
+
155
+ **Load options**:
156
+ ```tsx
157
+ await load({
158
+ method: 'POST', // GET, POST, PUT, PATCH, DELETE
159
+ params: { query: 'test' }, // Query string (GET) or body (others)
160
+ body: { data: 'value' }, // For POST/PUT/PATCH/DELETE
161
+ });
162
+ ```
163
+
164
+ ### useLoaderData()
165
+
166
+ Get all loader data in current context:
167
+
168
+ ```tsx
169
+ "use client";
170
+ import { useLoaderData } from "@rangojs/router";
171
+
172
+ function DebugPanel() {
173
+ const allData = useLoaderData();
174
+ // Record<string, any> - Map of loader ID to data
175
+
176
+ return <pre>{JSON.stringify(allData, null, 2)}</pre>;
177
+ }
178
+ ```
179
+
180
+ ## Handle Hooks
181
+
182
+ ### useHandle()
183
+
184
+ Access accumulated handle data from route segments:
185
+
186
+ ```tsx
187
+ "use client";
188
+ import { useHandle } from "@rangojs/router";
189
+ import { Breadcrumbs } from "../handles/breadcrumbs";
190
+
191
+ function BreadcrumbNav() {
192
+ const crumbs = useHandle(Breadcrumbs);
193
+ // Array of { label, href } accumulated from layouts/routes
194
+
195
+ return (
196
+ <nav>
197
+ {crumbs.map((c, i) => (
198
+ <span key={i}>
199
+ <a href={c.href}>{c.label}</a>
200
+ {i < crumbs.length - 1 && " > "}
201
+ </span>
202
+ ))}
203
+ </nav>
204
+ );
205
+ }
206
+
207
+ // With selector
208
+ const lastCrumb = useHandle(Breadcrumbs, data => data.at(-1));
209
+ ```
210
+
211
+ Handles can be passed as props from server to client components:
212
+
213
+ ```tsx
214
+ // Server component
215
+ path("/dashboard", (ctx) => {
216
+ const push = ctx.use(Breadcrumbs);
217
+ push({ label: "Dashboard", href: "/dashboard" });
218
+ return <DashboardNav handle={Breadcrumbs} />;
219
+ })
220
+
221
+ // Client component — typeof infers the full Handle<T> type
222
+ "use client";
223
+ import { useHandle } from "@rangojs/router/client";
224
+ import type { Breadcrumbs } from "../handles";
225
+
226
+ function DashboardNav({ handle }: { handle: typeof Breadcrumbs }) {
227
+ const crumbs = useHandle(handle);
228
+ return <nav>{crumbs.map(c => <a href={c.href}>{c.label}</a>)}</nav>;
229
+ }
230
+ ```
231
+
232
+ RSC serialization strips the `collect` function via `toJSON()`. On the client,
233
+ `useHandle()` recovers it from the module-level registry (populated when
234
+ `createHandle()` runs during module initialization).
235
+
236
+ ## Action Hooks
237
+
238
+ ### useAction()
239
+
240
+ Track state of server action invocations:
241
+
242
+ ```tsx
243
+ "use client";
244
+ import { useAction } from "@rangojs/router";
245
+ import { addToCart } from "../actions/cart";
246
+
247
+ function AddToCartButton({ productId }: { productId: string }) {
248
+ const { state, error, result } = useAction(addToCart);
249
+
250
+ // state: 'idle' | 'loading' | 'streaming'
251
+ // actionId: string | null
252
+ // payload: unknown | null (input data)
253
+ // error: Error | null
254
+ // result: unknown | null (return value)
255
+
256
+ return (
257
+ <form action={addToCart}>
258
+ <input type="hidden" name="productId" value={productId} />
259
+ <button disabled={state === 'loading'}>
260
+ {state === 'loading' ? 'Adding...' : 'Add to Cart'}
261
+ </button>
262
+ {error && <p className="error">{error.message}</p>}
263
+ </form>
264
+ );
265
+ }
266
+
267
+ // Match by string suffix (convenient but may be ambiguous)
268
+ const isLoading = useAction('addToCart', s => s.state === 'loading');
269
+ ```
270
+
271
+ ## State Hooks
272
+
273
+ ### useLocationState()
274
+
275
+ Read type-safe state from history:
276
+
277
+ ```tsx
278
+ "use client";
279
+ import { useLocationState, createLocationState } from "@rangojs/router";
280
+
281
+ // Define typed state
282
+ export const ProductState = createLocationState<{
283
+ name: string;
284
+ price: number;
285
+ }>();
286
+
287
+ function ProductHeader() {
288
+ const state = useLocationState(ProductState);
289
+ // { name: string; price: number } | undefined
290
+
291
+ if (state) {
292
+ return <h1>{state.name} - ${state.price}</h1>;
293
+ }
294
+ return <h1>Loading...</h1>;
295
+ }
296
+ ```
297
+
298
+ Pass state through Link:
299
+
300
+ ```tsx
301
+ import { Link } from "@rangojs/router/client";
302
+ import { ProductState } from "./state";
303
+
304
+ <Link
305
+ to="/product/123"
306
+ state={[ProductState({ name: "Widget", price: 99 })]}
307
+ >
308
+ View Product
309
+ </Link>
310
+ ```
311
+
312
+ ## Cache Hooks
313
+
314
+ ### useClientCache()
315
+
316
+ Manually control client-side navigation cache:
317
+
318
+ ```tsx
319
+ "use client";
320
+ import { useClientCache } from "@rangojs/router";
321
+
322
+ function SaveButton() {
323
+ const { clear } = useClientCache();
324
+
325
+ const handleSave = async () => {
326
+ await fetch('/api/data', {
327
+ method: 'POST',
328
+ body: JSON.stringify(data)
329
+ });
330
+
331
+ // Invalidate cache after mutation
332
+ clear();
333
+ };
334
+
335
+ return <button onClick={handleSave}>Save</button>;
336
+ }
337
+ ```
338
+
339
+ **Use cases**: REST API mutations, WebSocket updates, non-RSC data changes.
340
+
341
+ ## Outlet Components
342
+
343
+ ### Outlet / ParallelOutlet
344
+
345
+ Render child content in layouts:
346
+
347
+ ```tsx
348
+ import { Outlet, ParallelOutlet } from "@rangojs/router";
349
+
350
+ function DashboardLayout({ children }: { children?: React.ReactNode }) {
351
+ return (
352
+ <div className="dashboard">
353
+ <aside>
354
+ <ParallelOutlet name="@sidebar" />
355
+ </aside>
356
+ <main>
357
+ {children ?? <Outlet />}
358
+ </main>
359
+ <ParallelOutlet name="@notifications" />
360
+ </div>
361
+ );
362
+ }
363
+ ```
364
+
365
+ ### useOutlet()
366
+
367
+ Access outlet content programmatically:
368
+
369
+ ```tsx
370
+ "use client";
371
+ import { useOutlet } from "@rangojs/router";
372
+
373
+ function ConditionalLayout() {
374
+ const outlet = useOutlet();
375
+ // ReactNode | null
376
+
377
+ return outlet ? (
378
+ <div className="with-content">{outlet}</div>
379
+ ) : (
380
+ <div className="empty">No content</div>
381
+ );
382
+ }
383
+ ```
384
+
385
+ ## URL Hooks
386
+
387
+ ### useHref()
388
+
389
+ Mount-aware href for client components inside `include()` scopes:
390
+
391
+ ```tsx
392
+ "use client";
393
+ import { useHref, href, Link } from "@rangojs/router/client";
394
+
395
+ // Inside include("/shop", shopPatterns)
396
+ function ShopNav() {
397
+ const href = useHref();
398
+
399
+ return (
400
+ <>
401
+ {/* Local paths - auto-prefixed with /shop */}
402
+ <Link to={href("/cart")}>Cart</Link>
403
+ <Link to={href("/product/widget")}>Widget</Link>
404
+ </>
405
+ );
406
+ }
407
+ ```
408
+
409
+ Use `useHref()` for local navigation. Use the bare `href()` function for absolute paths.
410
+
411
+ ### useMount()
412
+
413
+ Returns the current `include()` mount path:
414
+
415
+ ```tsx
416
+ "use client";
417
+ import { useMount } from "@rangojs/router/client";
418
+
419
+ function MountInfo() {
420
+ const mount = useMount(); // "/shop" inside include("/shop", ...)
421
+ return <span>Mounted at: {mount}</span>;
422
+ }
423
+ ```
424
+
425
+ See `/links` for full URL generation guide including server-side `ctx.reverse`.
426
+
427
+ ## Hook Summary
428
+
429
+ | Hook | Purpose | Returns |
430
+ |------|---------|---------|
431
+ | `useHref()` | Mount-aware href | `(path) => string` |
432
+ | `useMount()` | Current include() mount path | `string` |
433
+ | `useNavigation()` | Navigation state & control | state, navigate, refresh |
434
+ | `useSegments()` | URL path & segment IDs | path, segmentIds, location |
435
+ | `useLinkStatus()` | Link pending state | { pending } |
436
+ | `useLoader()` | Loader data (strict) | data, isLoading, error |
437
+ | `useFetchLoader()` | Loader with on-demand fetch | data, load, isLoading |
438
+ | `useLoaderData()` | All loader data | Record<string, any> |
439
+ | `useHandle()` | Accumulated handle data | T (handle type) |
440
+ | `useAction()` | Server action state | state, error, result |
441
+ | `useLocationState()` | History state | T \| undefined |
442
+ | `useClientCache()` | Cache control | { clear } |