@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1b930379

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 (84) hide show
  1. package/README.md +46 -12
  2. package/dist/bin/rango.js +109 -15
  3. package/dist/vite/index.js +323 -121
  4. package/package.json +15 -16
  5. package/skills/breadcrumbs/SKILL.md +250 -0
  6. package/skills/caching/SKILL.md +4 -4
  7. package/skills/document-cache/SKILL.md +2 -2
  8. package/skills/hooks/SKILL.md +33 -31
  9. package/skills/host-router/SKILL.md +218 -0
  10. package/skills/loader/SKILL.md +55 -15
  11. package/skills/prerender/SKILL.md +2 -2
  12. package/skills/rango/SKILL.md +0 -1
  13. package/skills/route/SKILL.md +3 -4
  14. package/skills/router-setup/SKILL.md +8 -3
  15. package/skills/typesafety/SKILL.md +25 -23
  16. package/src/__internal.ts +92 -0
  17. package/src/bin/rango.ts +18 -0
  18. package/src/browser/link-interceptor.ts +4 -0
  19. package/src/browser/navigation-bridge.ts +95 -5
  20. package/src/browser/navigation-client.ts +97 -72
  21. package/src/browser/prefetch/cache.ts +112 -25
  22. package/src/browser/prefetch/fetch.ts +28 -30
  23. package/src/browser/prefetch/policy.ts +6 -0
  24. package/src/browser/react/Link.tsx +19 -7
  25. package/src/browser/rsc-router.tsx +11 -2
  26. package/src/browser/server-action-bridge.ts +448 -432
  27. package/src/browser/types.ts +24 -0
  28. package/src/build/generate-route-types.ts +2 -0
  29. package/src/build/route-trie.ts +19 -3
  30. package/src/build/route-types/router-processing.ts +125 -15
  31. package/src/client.rsc.tsx +2 -1
  32. package/src/client.tsx +1 -46
  33. package/src/handles/breadcrumbs.ts +66 -0
  34. package/src/handles/index.ts +1 -0
  35. package/src/host/index.ts +0 -3
  36. package/src/index.rsc.ts +5 -36
  37. package/src/index.ts +32 -66
  38. package/src/prerender/store.ts +56 -15
  39. package/src/route-definition/index.ts +0 -3
  40. package/src/router/handler-context.ts +30 -3
  41. package/src/router/loader-resolution.ts +1 -1
  42. package/src/router/match-api.ts +1 -1
  43. package/src/router/match-result.ts +0 -9
  44. package/src/router/metrics.ts +233 -13
  45. package/src/router/middleware-types.ts +53 -10
  46. package/src/router/middleware.ts +170 -81
  47. package/src/router/pattern-matching.ts +20 -5
  48. package/src/router/prerender-match.ts +4 -0
  49. package/src/router/revalidation.ts +27 -7
  50. package/src/router/router-interfaces.ts +14 -1
  51. package/src/router/router-options.ts +13 -8
  52. package/src/router/segment-resolution/fresh.ts +18 -0
  53. package/src/router/segment-resolution/helpers.ts +1 -1
  54. package/src/router/segment-resolution/revalidation.ts +22 -9
  55. package/src/router/trie-matching.ts +20 -2
  56. package/src/router.ts +29 -9
  57. package/src/rsc/handler.ts +106 -11
  58. package/src/rsc/index.ts +0 -20
  59. package/src/rsc/progressive-enhancement.ts +21 -8
  60. package/src/rsc/rsc-rendering.ts +30 -43
  61. package/src/rsc/server-action.ts +14 -10
  62. package/src/rsc/ssr-setup.ts +128 -0
  63. package/src/rsc/types.ts +2 -0
  64. package/src/search-params.ts +16 -13
  65. package/src/server/context.ts +8 -2
  66. package/src/server/request-context.ts +38 -16
  67. package/src/server.ts +6 -0
  68. package/src/theme/index.ts +4 -13
  69. package/src/types/handler-context.ts +12 -16
  70. package/src/types/route-config.ts +17 -8
  71. package/src/types/segments.ts +0 -5
  72. package/src/vite/discovery/bundle-postprocess.ts +31 -56
  73. package/src/vite/discovery/discover-routers.ts +18 -4
  74. package/src/vite/discovery/prerender-collection.ts +34 -14
  75. package/src/vite/discovery/state.ts +4 -7
  76. package/src/vite/index.ts +4 -3
  77. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  78. package/src/vite/plugins/refresh-cmd.ts +65 -0
  79. package/src/vite/rango.ts +11 -0
  80. package/src/vite/router-discovery.ts +16 -0
  81. package/src/vite/utils/prerender-utils.ts +60 -0
  82. package/skills/testing/SKILL.md +0 -226
  83. package/src/route-definition/route-function.ts +0 -119
  84. /package/{CLAUDE.md → AGENTS.md} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.0.0-experimental.19",
3
+ "version": "0.0.0-experimental.1b930379",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -31,7 +31,7 @@
31
31
  "!src/**/*.test.tsx",
32
32
  "dist",
33
33
  "skills",
34
- "CLAUDE.md",
34
+ "AGENTS.md",
35
35
  "README.md"
36
36
  ],
37
37
  "type": "module",
@@ -132,15 +132,6 @@
132
132
  "access": "public",
133
133
  "tag": "experimental"
134
134
  },
135
- "scripts": {
136
- "build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
137
- "prepublishOnly": "pnpm build",
138
- "typecheck": "tsc --noEmit",
139
- "test": "playwright test",
140
- "test:ui": "playwright test --ui",
141
- "test:unit": "vitest run",
142
- "test:unit:watch": "vitest"
143
- },
144
135
  "dependencies": {
145
136
  "@vitejs/plugin-rsc": "^0.5.14",
146
137
  "magic-string": "^0.30.17",
@@ -150,12 +141,12 @@
150
141
  "devDependencies": {
151
142
  "@playwright/test": "^1.49.1",
152
143
  "@types/node": "^24.10.1",
153
- "@types/react": "catalog:",
154
- "@types/react-dom": "catalog:",
144
+ "@types/react": "^19.2.7",
145
+ "@types/react-dom": "^19.2.3",
155
146
  "esbuild": "^0.27.0",
156
147
  "jiti": "^2.6.1",
157
- "react": "catalog:",
158
- "react-dom": "catalog:",
148
+ "react": "^19.2.4",
149
+ "react-dom": "^19.2.4",
159
150
  "tinyexec": "^0.3.2",
160
151
  "typescript": "^5.3.0",
161
152
  "vitest": "^4.0.0"
@@ -173,5 +164,13 @@
173
164
  "vite": {
174
165
  "optional": true
175
166
  }
167
+ },
168
+ "scripts": {
169
+ "build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
170
+ "typecheck": "tsc --noEmit",
171
+ "test": "playwright test",
172
+ "test:ui": "playwright test --ui",
173
+ "test:unit": "vitest run",
174
+ "test:unit:watch": "vitest"
176
175
  }
177
- }
176
+ }
@@ -0,0 +1,250 @@
1
+ ---
2
+ name: breadcrumbs
3
+ description: Built-in Breadcrumbs handle for accumulating breadcrumb navigation across route segments
4
+ argument-hint: [setup]
5
+ ---
6
+
7
+ # Breadcrumbs
8
+
9
+ Built-in handle for accumulating breadcrumb items across route segments.
10
+ Each layout/route pushes items via `ctx.use(Breadcrumbs)`, and they are
11
+ collected in parent-to-child order with automatic deduplication by `href`.
12
+
13
+ ## BreadcrumbItem Type
14
+
15
+ ```typescript
16
+ interface BreadcrumbItem {
17
+ label: string; // Display text
18
+ href: string; // URL the breadcrumb links to
19
+ content?: ReactNode | Promise<ReactNode>; // Optional extra content (sync or async)
20
+ }
21
+ ```
22
+
23
+ ## Pushing Breadcrumbs (Server)
24
+
25
+ Import `Breadcrumbs` from `@rangojs/router` in RSC/server context:
26
+
27
+ ```typescript
28
+ import { urls, Breadcrumbs } from "@rangojs/router";
29
+ import { Outlet } from "@rangojs/router/client";
30
+
31
+ export const urlpatterns = urls(({ path, layout }) => [
32
+ // Root layout pushes "Home"
33
+ layout((ctx) => {
34
+ const breadcrumb = ctx.use(Breadcrumbs);
35
+ breadcrumb({ label: "Home", href: "/" });
36
+ return <RootLayout />;
37
+ }, () => [
38
+ path("/", HomePage, { name: "home" }),
39
+
40
+ // Nested layout pushes "Blog"
41
+ layout((ctx) => {
42
+ const breadcrumb = ctx.use(Breadcrumbs);
43
+ breadcrumb({ label: "Blog", href: "/blog" });
44
+ return <BlogLayout />;
45
+ }, () => [
46
+ path("/blog", BlogIndex, { name: "blog.index" }),
47
+
48
+ // Route handler pushes post title
49
+ path("/blog/:slug", (ctx) => {
50
+ const breadcrumb = ctx.use(Breadcrumbs);
51
+ breadcrumb({ label: ctx.params.slug, href: `/blog/${ctx.params.slug}` });
52
+ return <BlogPost slug={ctx.params.slug} />;
53
+ }, { name: "blog.post" }),
54
+ ]),
55
+ ]),
56
+ ]);
57
+ ```
58
+
59
+ On `/blog/my-post`, breadcrumbs accumulate: `Home > Blog > my-post`.
60
+
61
+ ## Async Content
62
+
63
+ The `content` field supports `Promise<ReactNode>` for streaming:
64
+
65
+ ```typescript
66
+ path("/product/:id", async (ctx) => {
67
+ const breadcrumb = ctx.use(Breadcrumbs);
68
+ const productPromise = fetchProduct(ctx.params.id);
69
+
70
+ breadcrumb({
71
+ label: "Product",
72
+ href: `/product/${ctx.params.id}`,
73
+ content: productPromise.then((p) => <span>({p.category})</span>),
74
+ });
75
+
76
+ const product = await productPromise;
77
+ return <ProductPage product={product} />;
78
+ }, { name: "product" })
79
+ ```
80
+
81
+ Async content is a `Promise<ReactNode>`. Resolve it in your component
82
+ with React's `use()` hook wrapped in `<Suspense>`.
83
+
84
+ ## Consuming Breadcrumbs (Client)
85
+
86
+ Use `useHandle(Breadcrumbs)` in a client component to read the accumulated items:
87
+
88
+ ```tsx
89
+ "use client";
90
+ import { useHandle, Breadcrumbs, Link } from "@rangojs/router/client";
91
+
92
+ function BreadcrumbNav() {
93
+ const breadcrumbs = useHandle(Breadcrumbs);
94
+
95
+ if (!breadcrumbs.length) return null;
96
+
97
+ return (
98
+ <nav aria-label="Breadcrumb">
99
+ <ol>
100
+ {breadcrumbs.map((crumb, i) => (
101
+ <li key={crumb.href}>
102
+ {i === breadcrumbs.length - 1 ? (
103
+ <span aria-current="page">{crumb.label}</span>
104
+ ) : (
105
+ <Link to={crumb.href}>{crumb.label}</Link>
106
+ )}
107
+ </li>
108
+ ))}
109
+ </ol>
110
+ </nav>
111
+ );
112
+ }
113
+ ```
114
+
115
+ ### With Selector
116
+
117
+ Re-render only when the selected value changes:
118
+
119
+ ```tsx
120
+ // Only the last breadcrumb
121
+ const current = useHandle(Breadcrumbs, (data) => data.at(-1));
122
+
123
+ // Breadcrumb count
124
+ const count = useHandle(Breadcrumbs, (data) => data.length);
125
+ ```
126
+
127
+ ## Deduplication
128
+
129
+ The built-in collect function deduplicates by `href`. If multiple segments
130
+ push the same `href`, the last one wins. This prevents duplicates when
131
+ navigating between sibling routes that share a common breadcrumb.
132
+
133
+ ## Passing as Props
134
+
135
+ Breadcrumbs handle can be passed from server to client components:
136
+
137
+ ```tsx
138
+ // Server component
139
+ path("/dashboard", (ctx) => {
140
+ const breadcrumb = ctx.use(Breadcrumbs);
141
+ breadcrumb({ label: "Dashboard", href: "/dashboard" });
142
+ return <DashboardNav handle={Breadcrumbs} />;
143
+ });
144
+
145
+ // Client component
146
+ ("use client");
147
+ import { useHandle, type Breadcrumbs } from "@rangojs/router/client";
148
+
149
+ function DashboardNav({ handle }: { handle: typeof Breadcrumbs }) {
150
+ const crumbs = useHandle(handle);
151
+ return (
152
+ <nav>
153
+ {crumbs.map((c) => (
154
+ <a href={c.href}>{c.label}</a>
155
+ ))}
156
+ </nav>
157
+ );
158
+ }
159
+ ```
160
+
161
+ ## Complete Example
162
+
163
+ ```typescript
164
+ // urls.tsx
165
+ import { urls, Breadcrumbs, Meta } from "@rangojs/router";
166
+ import { Outlet, MetaTags } from "@rangojs/router/client";
167
+ import { BreadcrumbNav } from "./components/BreadcrumbNav";
168
+
169
+ function RootLayout() {
170
+ return (
171
+ <html lang="en">
172
+ <head><MetaTags /></head>
173
+ <body>
174
+ <BreadcrumbNav />
175
+ <main><Outlet /></main>
176
+ </body>
177
+ </html>
178
+ );
179
+ }
180
+
181
+ export const urlpatterns = urls(({ path, layout }) => [
182
+ layout((ctx) => {
183
+ ctx.use(Breadcrumbs)({ label: "Home", href: "/" });
184
+ ctx.use(Meta)({ title: "My App" });
185
+ return <RootLayout />;
186
+ }, () => [
187
+ path("/", () => <h1>Welcome</h1>, { name: "home" }),
188
+
189
+ layout((ctx) => {
190
+ ctx.use(Breadcrumbs)({ label: "Shop", href: "/shop" });
191
+ return <Outlet />;
192
+ }, () => [
193
+ path("/shop", () => <h1>Shop</h1>, { name: "shop" }),
194
+ path("/shop/:slug", (ctx) => {
195
+ ctx.use(Breadcrumbs)({
196
+ label: ctx.params.slug,
197
+ href: `/shop/${ctx.params.slug}`,
198
+ });
199
+ return <h1>Product: {ctx.params.slug}</h1>;
200
+ }, { name: "shop.product" }),
201
+ ]),
202
+ ]),
203
+ ]);
204
+ ```
205
+
206
+ Navigating to `/shop/widget` produces: `Home / Shop / widget`
207
+
208
+ ## Custom Handles
209
+
210
+ Create your own handle with `createHandle()`:
211
+
212
+ ```typescript
213
+ import { createHandle } from "@rangojs/router";
214
+
215
+ // Default: flatten into array
216
+ export const PageTitle = createHandle<string, string>(
217
+ (segments) => segments.flat().at(-1) ?? "Default Title",
218
+ );
219
+
220
+ // No collect function: default flattens into T[]
221
+ export const Warnings = createHandle<string>();
222
+ ```
223
+
224
+ The Vite `exposeInternalIds` plugin auto-injects a stable `$$id` based on
225
+ file path and export name. No manual naming required for project-local code.
226
+
227
+ ### Handles in 3rd-party packages
228
+
229
+ The `exposeInternalIds` plugin skips `node_modules/`, so handles defined in
230
+ published packages won't get auto-injected IDs. Pass a manual tag as the
231
+ second argument to `createHandle()`:
232
+
233
+ ```typescript
234
+ import { createHandle } from "@rangojs/router";
235
+
236
+ // With a collect function (reducer): collect is first arg, tag is second
237
+ export const Breadcrumbs = createHandle<BreadcrumbItem, BreadcrumbItem[]>(
238
+ collectBreadcrumbs,
239
+ "__my_package_breadcrumbs__",
240
+ );
241
+
242
+ // Without a collect function: pass undefined, then the tag
243
+ export const Warnings = createHandle<string>(
244
+ undefined,
245
+ "__my_package_warnings__",
246
+ );
247
+ ```
248
+
249
+ The tag must be globally unique and stable across builds. Without it,
250
+ `createHandle` throws in development mode.
@@ -89,7 +89,7 @@ Configure a cache store in the router:
89
89
 
90
90
  ```typescript
91
91
  import { createRouter } from "@rangojs/router";
92
- import { MemorySegmentCacheStore } from "@rangojs/router/rsc";
92
+ import { MemorySegmentCacheStore } from "@rangojs/router/cache";
93
93
 
94
94
  const store = new MemorySegmentCacheStore({
95
95
  defaults: { ttl: 60, swr: 300 },
@@ -112,7 +112,7 @@ const router = createRouter({
112
112
  For single-instance deployments:
113
113
 
114
114
  ```typescript
115
- import { MemorySegmentCacheStore } from "@rangojs/router/rsc";
115
+ import { MemorySegmentCacheStore } from "@rangojs/router/cache";
116
116
 
117
117
  const store = new MemorySegmentCacheStore({
118
118
  defaults: { ttl: 60, swr: 300 },
@@ -125,7 +125,7 @@ const store = new MemorySegmentCacheStore({
125
125
  For distributed caching on Cloudflare Workers:
126
126
 
127
127
  ```typescript
128
- import { CFCacheStore } from "@rangojs/router/cache/cf";
128
+ import { CFCacheStore } from "@rangojs/router/cache";
129
129
 
130
130
  const router = createRouter<AppBindings>({
131
131
  document: Document,
@@ -175,7 +175,7 @@ cache({ store: checkoutCache }, () => [
175
175
 
176
176
  ```typescript
177
177
  import { urls } from "@rangojs/router";
178
- import { MemorySegmentCacheStore } from "@rangojs/router/rsc";
178
+ import { MemorySegmentCacheStore } from "@rangojs/router/cache";
179
179
 
180
180
  // Custom store for checkout (short TTL)
181
181
  const checkoutCache = new MemorySegmentCacheStore({
@@ -14,7 +14,7 @@ Configure document cache in router:
14
14
 
15
15
  ```typescript
16
16
  import { createRouter } from "@rangojs/router";
17
- import { CFCacheStore } from "@rangojs/router/cache/cf";
17
+ import { CFCacheStore } from "@rangojs/router/cache";
18
18
  import { urlpatterns } from "./urls";
19
19
 
20
20
  const router = createRouter<AppBindings>({
@@ -134,7 +134,7 @@ Segment hash ensures different cached responses for navigations from different s
134
134
  ```typescript
135
135
  // router.tsx
136
136
  import { createRouter } from "@rangojs/router";
137
- import { CFCacheStore } from "@rangojs/router/cache/cf";
137
+ import { CFCacheStore } from "@rangojs/router/cache";
138
138
  import { urlpatterns } from "./urls";
139
139
 
140
140
  const router = createRouter<AppBindings>({
@@ -6,7 +6,8 @@ argument-hint: [hook-name]
6
6
 
7
7
  # Client-Side React Hooks
8
8
 
9
- All hooks are imported from `@rangojs/router` or `@rangojs/router/client`.
9
+ Import the hooks and components in this skill from `@rangojs/router/client`.
10
+ The root `@rangojs/router` entrypoint is for server/RSC APIs and shared types.
10
11
 
11
12
  ## Navigation Hooks
12
13
 
@@ -57,13 +58,33 @@ function NavigationControls() {
57
58
  }
58
59
  ```
59
60
 
61
+ #### Skipping revalidation
62
+
63
+ Pass `revalidate: false` to skip the RSC server fetch for same-pathname navigations (search param or hash changes). The URL updates and all hooks re-render, but server components stay as-is.
64
+
65
+ ```tsx
66
+ // Update search params without server round-trip
67
+ router.push("/products?color=blue", { revalidate: false });
68
+ router.replace("/products?page=3", { revalidate: false });
69
+ ```
70
+
71
+ If the pathname changes, `revalidate: false` is silently ignored and a full navigation occurs. This also works on `<Link>`:
72
+
73
+ ```tsx
74
+ <Link to="/products?color=blue" revalidate={false}>
75
+ Blue
76
+ </Link>
77
+ ```
78
+
79
+ Plain `<a>` tags can opt in via `data-revalidate="false"`.
80
+
60
81
  ### useSegments()
61
82
 
62
83
  Access current URL path and matched route segments:
63
84
 
64
85
  ```tsx
65
86
  "use client";
66
- import { useSegments } from "@rangojs/router";
87
+ import { useSegments } from "@rangojs/router/client";
67
88
 
68
89
  function Breadcrumbs() {
69
90
  const { path, segmentIds, location } = useSegments();
@@ -107,7 +128,7 @@ Access loader data (strict - data guaranteed):
107
128
 
108
129
  ```tsx
109
130
  "use client";
110
- import { useLoader } from "@rangojs/router";
131
+ import { useLoader } from "@rangojs/router/client";
111
132
  import { ProductLoader } from "../loaders/product";
112
133
 
113
134
  function ProductPrice() {
@@ -143,7 +164,7 @@ Access loader with on-demand fetching (flexible):
143
164
 
144
165
  ```tsx
145
166
  "use client";
146
- import { useFetchLoader } from "@rangojs/router";
167
+ import { useFetchLoader } from "@rangojs/router/client";
147
168
  import { SearchLoader } from "../loaders/search";
148
169
 
149
170
  function SearchResults() {
@@ -197,7 +218,7 @@ server, JSON bodies are available via `ctx.body` and FormData bodies via `ctx.fo
197
218
 
198
219
  ```tsx
199
220
  "use client";
200
- import { useFetchLoader } from "@rangojs/router";
221
+ import { useFetchLoader } from "@rangojs/router/client";
201
222
  import { FileUploadLoader } from "../loaders/upload";
202
223
 
203
224
  function FileUploader() {
@@ -238,22 +259,6 @@ export const FileUploadLoader = createLoader(async (ctx) => {
238
259
  }, true); // true = fetchable (can be called from the client via load())
239
260
  ```
240
261
 
241
- ### useLoaderData()
242
-
243
- Get all loader data in current context:
244
-
245
- ```tsx
246
- "use client";
247
- import { useLoaderData } from "@rangojs/router";
248
-
249
- function DebugPanel() {
250
- const allData = useLoaderData();
251
- // Record<string, any> - Map of loader ID to data
252
-
253
- return <pre>{JSON.stringify(allData, null, 2)}</pre>;
254
- }
255
- ```
256
-
257
262
  ## Handle Hooks
258
263
 
259
264
  ### useHandle()
@@ -262,8 +267,7 @@ Access accumulated handle data from route segments:
262
267
 
263
268
  ```tsx
264
269
  "use client";
265
- import { useHandle } from "@rangojs/router";
266
- import { Breadcrumbs } from "../handles/breadcrumbs";
270
+ import { useHandle, Breadcrumbs } from "@rangojs/router/client";
267
271
 
268
272
  function BreadcrumbNav() {
269
273
  const crumbs = useHandle(Breadcrumbs);
@@ -297,8 +301,7 @@ path("/dashboard", (ctx) => {
297
301
 
298
302
  // Client component — typeof infers the full Handle<T> type
299
303
  ("use client");
300
- import { useHandle } from "@rangojs/router/client";
301
- import type { Breadcrumbs } from "../handles";
304
+ import { useHandle, type Breadcrumbs } from "@rangojs/router/client";
302
305
 
303
306
  function DashboardNav({ handle }: { handle: typeof Breadcrumbs }) {
304
307
  const crumbs = useHandle(handle);
@@ -324,7 +327,7 @@ Track state of server action invocations:
324
327
 
325
328
  ```tsx
326
329
  "use client";
327
- import { useAction } from "@rangojs/router";
330
+ import { useAction } from "@rangojs/router/client";
328
331
  import { addToCart } from "../actions/cart";
329
332
 
330
333
  function AddToCartButton({ productId }: { productId: string }) {
@@ -359,7 +362,7 @@ Read type-safe state from history:
359
362
 
360
363
  ```tsx
361
364
  "use client";
362
- import { useLocationState, createLocationState } from "@rangojs/router";
365
+ import { useLocationState, createLocationState } from "@rangojs/router/client";
363
366
 
364
367
  // Define typed state (all export patterns supported)
365
368
  // Keys are auto-injected by the Vite plugin -- no manual key needed.
@@ -509,7 +512,7 @@ Manually control client-side navigation cache:
509
512
 
510
513
  ```tsx
511
514
  "use client";
512
- import { useClientCache } from "@rangojs/router";
515
+ import { useClientCache } from "@rangojs/router/client";
513
516
 
514
517
  function SaveButton() {
515
518
  const { clear } = useClientCache();
@@ -537,7 +540,7 @@ function SaveButton() {
537
540
  Render child content in layouts:
538
541
 
539
542
  ```tsx
540
- import { Outlet, ParallelOutlet } from "@rangojs/router";
543
+ import { Outlet, ParallelOutlet } from "@rangojs/router/client";
541
544
 
542
545
  function DashboardLayout({ children }: { children?: React.ReactNode }) {
543
546
  return (
@@ -558,7 +561,7 @@ Access outlet content programmatically:
558
561
 
559
562
  ```tsx
560
563
  "use client";
561
- import { useOutlet } from "@rangojs/router";
564
+ import { useOutlet } from "@rangojs/router/client";
562
565
 
563
566
  function ConditionalLayout() {
564
567
  const outlet = useOutlet();
@@ -695,7 +698,6 @@ See `/links` for full URL generation guide including server-side `ctx.reverse`.
695
698
  | `useLinkStatus()` | Link pending state | { pending } |
696
699
  | `useLoader()` | Loader data (strict) | data, isLoading, error |
697
700
  | `useFetchLoader()` | Loader with on-demand fetch | data, load, isLoading |
698
- | `useLoaderData()` | All loader data | Record<string, any> |
699
701
  | `useHandle()` | Accumulated handle data | T (handle type) |
700
702
  | `useAction()` | Server action state | state, error, result |
701
703
  | `useLocationState()` | History state (persists or flash) | T \| undefined |