@rangojs/router 0.0.0-experimental.0f44aca1

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 (305) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +538 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +469 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +540 -0
  105. package/src/cache/cf/index.ts +25 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +43 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +275 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +192 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +748 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +316 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1239 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +289 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1002 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +235 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +914 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +102 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +110 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +131 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +365 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +254 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +510 -0
  298. package/src/vite/router-discovery.ts +785 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,310 @@
1
+ ---
2
+ name: layout
3
+ description: Define layout routes that wrap child routes in @rangojs/router
4
+ argument-hint: [component]
5
+ ---
6
+
7
+ # Layouts with layout()
8
+
9
+ Layouts wrap child routes and persist during navigation within their scope.
10
+
11
+ Canonical semantics reference:
12
+ [docs/execution-model.md](../../docs/internal/execution-model.md)
13
+
14
+ ## Basic Layout
15
+
16
+ ```typescript
17
+ import { urls } from "@rangojs/router";
18
+ import { Outlet } from "@rangojs/router/client";
19
+
20
+ function ShopLayout() {
21
+ return (
22
+ <div className="shop">
23
+ <nav>Shop Navigation</nav>
24
+ <Outlet /> {/* Child routes render here */}
25
+ </div>
26
+ );
27
+ }
28
+
29
+ export const urlpatterns = urls(({ path, layout }) => [
30
+ layout(<ShopLayout />, () => [
31
+ path("/shop", ShopIndex, { name: "shop.index" }),
32
+ path("/shop/cart", CartPage, { name: "shop.cart" }),
33
+ path("/shop/product/:slug", ProductPage, { name: "shop.product" }),
34
+ ]),
35
+ ]);
36
+ ```
37
+
38
+ ## Layout Patterns
39
+
40
+ ### JSX Element
41
+
42
+ ```typescript
43
+ layout(<ShopLayout />, () => [
44
+ path("/shop", ShopIndex, { name: "shop" }),
45
+ ])
46
+ ```
47
+
48
+ ### Component Function
49
+
50
+ ```typescript
51
+ layout(ShopLayout, () => [path("/shop", ShopIndex, { name: "shop" })]);
52
+ ```
53
+
54
+ ### Handler with Context
55
+
56
+ ```typescript
57
+ layout((ctx) => {
58
+ const push = ctx.use(Breadcrumbs);
59
+ push({ label: "Shop", href: "/shop" });
60
+ return <ShopLayout />;
61
+ }, () => [
62
+ path("/shop", ShopIndex, { name: "shop" }),
63
+ ])
64
+ ```
65
+
66
+ ## Nested Layouts
67
+
68
+ Layouts compose by wrapping order (first layout wraps outer):
69
+
70
+ ```typescript
71
+ urls(({ path, layout }) => [
72
+ layout(<RootLayout />, () => [ // Outer
73
+ layout(<ShopLayout />, () => [ // Inner
74
+ path("/shop", ShopIndex, { name: "shop" }),
75
+ ]),
76
+ ]),
77
+ ])
78
+
79
+ // Result: RootLayout > ShopLayout > ShopIndex
80
+ ```
81
+
82
+ ## Layout with Children DSL
83
+
84
+ Add loaders, parallel routes, or revalidation to layouts:
85
+
86
+ ```typescript
87
+ layout(<ShopLayout />, () => [
88
+ // Loaders for layout
89
+ loader(CartLoader),
90
+ loader(UserLoader),
91
+
92
+ // Revalidation rules for layout
93
+ revalidate(shopRevalidation),
94
+
95
+ // Child routes
96
+ path("/shop", ShopIndex, { name: "shop" }),
97
+ path("/shop/cart", CartPage, { name: "cart" }),
98
+ ])
99
+ ```
100
+
101
+ ## The Outlet Component
102
+
103
+ `<Outlet />` renders child content. Import from `@rangojs/router/client`:
104
+
105
+ ```typescript
106
+ import { Outlet } from "@rangojs/router/client";
107
+
108
+ function ShopLayout() {
109
+ return (
110
+ <div className="shop-layout">
111
+ <header>Shop Header</header>
112
+ <main>
113
+ <Outlet /> {/* Child routes render here */}
114
+ </main>
115
+ <footer>Shop Footer</footer>
116
+ </div>
117
+ );
118
+ }
119
+ ```
120
+
121
+ ## Named Outlets
122
+
123
+ For parallel routes, use named outlets:
124
+
125
+ ```typescript
126
+ import { Outlet, ParallelOutlet } from "@rangojs/router/client";
127
+
128
+ function DashboardLayout() {
129
+ return (
130
+ <div className="dashboard">
131
+ <aside>
132
+ <ParallelOutlet name="@sidebar" />
133
+ </aside>
134
+ <main>
135
+ <Outlet /> {/* Main content */}
136
+ </main>
137
+ <aside>
138
+ <ParallelOutlet name="@notifications" />
139
+ </aside>
140
+ </div>
141
+ );
142
+ }
143
+ ```
144
+
145
+ ## Orphan Layout (inside route)
146
+
147
+ A layout as a child of `path()` wraps the route content and can read
148
+ data set by the route handler via `ctx.get()`. The handler always
149
+ executes before its children.
150
+
151
+ This handler-first guarantee applies to a single full render pass
152
+ (initial render, prerender, or full HTML re-render). During partial
153
+ action revalidation, only the segments that revalidate are recomputed.
154
+ If an orphan layout depends on data established by an outer handler or
155
+ layout, that outer segment must also revalidate, or the orphan must
156
+ guard/reload the data independently.
157
+
158
+ ```typescript
159
+ import { Outlet, ParallelOutlet } from "@rangojs/router/client";
160
+
161
+ urls(({ path, layout, parallel }) => [
162
+ path("/product/:slug", (ctx) => {
163
+ const product = await fetchProduct(ctx.params.slug);
164
+ ctx.set("product", product);
165
+ return <ProductPage product={product} />;
166
+ }, { name: "product" }, () => [
167
+ layout((ctx) => {
168
+ const product = ctx.get("product");
169
+ return (
170
+ <div>
171
+ <Breadcrumb name={product?.name} />
172
+ <Outlet />
173
+ <ParallelOutlet name="@related" />
174
+ </div>
175
+ );
176
+ }, () => [
177
+ parallel({
178
+ "@related": (ctx) => {
179
+ const product = ctx.get("product");
180
+ return <RelatedProducts category={product?.category} />;
181
+ },
182
+ }),
183
+ ]),
184
+ ]),
185
+ ])
186
+ ```
187
+
188
+ Orphan layouts can call `ctx.get()` to read data set by their parent
189
+ handler. They can also call `ctx.set()`, though the primary pattern is
190
+ for route handlers and middleware to write context variables and for
191
+ orphan layouts to read them.
192
+
193
+ ## Layout Revalidation
194
+
195
+ Layouts don't revalidate by default. Control with `revalidate()`:
196
+
197
+ ```typescript
198
+ layout(<ShopLayout />, () => [
199
+ // Never revalidate (default behavior)
200
+ revalidate(() => false),
201
+
202
+ path("/shop", ShopIndex, { name: "shop" }),
203
+ ])
204
+
205
+ // Or revalidate based on conditions
206
+ layout(<CartLayout />, () => [
207
+ revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
208
+
209
+ path("/cart", CartPage, { name: "cart" }),
210
+ ])
211
+ ```
212
+
213
+ If child segments read data that was established by this layout or by a
214
+ route handler above them, revalidate the outer segment too. Partial
215
+ revalidation does not re-run non-revalidated ancestors just to rebuild
216
+ their `ctx.set()` state.
217
+
218
+ ### Revalidation Contracts
219
+
220
+ For shared upstream data, define named revalidation functions and reuse
221
+ them on both producer and consumer segments:
222
+
223
+ ```typescript
224
+ // revalidation-contracts.ts
225
+ export const revalidateCartData = ({ actionId }) =>
226
+ actionId?.includes("src/actions/cart.ts#addToCart") ?? false;
227
+ ```
228
+
229
+ ```typescript
230
+ layout(<CartLayout />, () => [
231
+ revalidate(revalidateCartData), // producer
232
+ path("/cart", CartPage, { name: "cart" }, () => [
233
+ revalidate(revalidateCartData), // consumer
234
+ ]),
235
+ ]);
236
+ ```
237
+
238
+ If a segment depends on multiple upstream domains, compose multiple
239
+ contracts (`revalidateAuthData`, `revalidateCartData`, and so on).
240
+
241
+ You can also package them as importable handoff helpers:
242
+
243
+ ```typescript
244
+ // revalidation-contracts.ts
245
+ import { revalidate } from "@rangojs/router";
246
+
247
+ export const revalidateAuthData = ({ actionId }) =>
248
+ actionId?.includes("src/actions/auth.ts#") ?? false;
249
+ export const revalidateAuth = () => [revalidate(revalidateAuthData)];
250
+ ```
251
+
252
+ ```typescript
253
+ layout(<ShellLayout />, () => [
254
+ revalidateAuth(),
255
+ path("/account", AccountPage, { name: "account" }, () => [
256
+ revalidateAuth(),
257
+ ]),
258
+ ]);
259
+ ```
260
+
261
+ ## Complete Example
262
+
263
+ ```typescript
264
+ import { urls } from "@rangojs/router";
265
+ import { Outlet, ParallelOutlet } from "@rangojs/router/client";
266
+
267
+ function ShopLayout() {
268
+ return (
269
+ <div className="shop">
270
+ <ParallelOutlet name="@promoBanner" />
271
+ <nav>
272
+ <a href="/shop">Home</a>
273
+ <a href="/shop/cart">Cart</a>
274
+ </nav>
275
+ <div className="content">
276
+ <aside>
277
+ <ParallelOutlet name="@sidebar" />
278
+ </aside>
279
+ <main>
280
+ <Outlet />
281
+ </main>
282
+ </div>
283
+ </div>
284
+ );
285
+ }
286
+
287
+ export const shopPatterns = urls(({ path, layout, parallel, loader, revalidate }) => [
288
+ layout((ctx) => {
289
+ const push = ctx.use(Breadcrumbs);
290
+ push({ label: "Shop", href: "/shop" });
291
+ return <ShopLayout />;
292
+ }, () => [
293
+ // Layout loaders
294
+ loader(CartLoader, () => [
295
+ revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
296
+ ]),
297
+
298
+ // Parallel routes
299
+ parallel({
300
+ "@promoBanner": () => <PromoBanner />,
301
+ "@sidebar": () => <CategorySidebar />,
302
+ }),
303
+
304
+ // Child routes
305
+ path("/shop", ShopIndex, { name: "index" }),
306
+ path("/shop/cart", CartPage, { name: "cart" }),
307
+ path("/shop/product/:slug", ProductPage, { name: "product" }),
308
+ ]),
309
+ ]);
310
+ ```
@@ -0,0 +1,239 @@
1
+ ---
2
+ name: links
3
+ description: URL generation with ctx.reverse (server), href (client), useHref (mounted), useMount, and scopedReverse
4
+ argument-hint: [href|useHref|useMount|scopedReverse]
5
+ ---
6
+
7
+ # Links & URL Generation
8
+
9
+ @rangojs/router provides different href APIs for server and client contexts.
10
+
11
+ ## Server: ctx.reverse()
12
+
13
+ Available in route handlers via HandlerContext. Resolves named routes using the full route map.
14
+
15
+ ```typescript
16
+ import { urls, scopedReverse } from "@rangojs/router";
17
+
18
+ export const shopPatterns = urls(({ path, layout }) => [
19
+ layout(<ShopLayout />, () => [
20
+ path("/", ShopIndex, { name: "index" }),
21
+ path("/cart", CartPage, { name: "cart" }),
22
+ path("/product/:slug", ProductPage, { name: "product" }),
23
+ ]),
24
+ ]);
25
+ ```
26
+
27
+ ### Resolution rules
28
+
29
+ - **`.name`** — local route, resolved within the current `include()` scope
30
+ - **`name`** — global route, from the named-routes definition
31
+
32
+ ```typescript
33
+ // Inside a handler within shopPatterns (mounted at /shop)
34
+ path("/product/:slug", (ctx) => {
35
+ ctx.reverse(".cart"); // "/shop/cart" (local)
36
+ ctx.reverse(".product", { slug: "widget" }); // "/shop/product/widget" (local + params)
37
+ ctx.reverse("blog.post", { slug: "hi" }); // "/blog/hi" (global)
38
+
39
+ return <ProductPage slug={ctx.params.slug} />;
40
+ }, { name: "product" })
41
+ ```
42
+
43
+ ### Local names (dot-prefixed)
44
+
45
+ Prefix a name with `.` to resolve it within the current `include()` scope. The route is looked up using the include's mount namespace.
46
+
47
+ ```typescript
48
+ // urls/magazine.tsx — mounted at include("/magazine", magazinePatterns, { name: "magazine" })
49
+ (ctx) => {
50
+ ctx.reverse(".article", { slug: "design" }); // "/magazine/design"
51
+ ctx.reverse(".author.posts", { authorSlug: "alice" }); // "/magazine/author/alice/posts"
52
+ ctx.reverse(".index"); // "/magazine"
53
+ ctx.reverse(".blog.index"); // THROWS — no magazine.blog.index
54
+ };
55
+ ```
56
+
57
+ ### Auto-fill of mount params
58
+
59
+ When routes are mounted via a parameterized `include()`, `ctx.reverse()` automatically fills mount params from `ctx.params`. Inner handlers don't need to pass params that are already known from the current URL match. Explicitly passed params override auto-filled values.
60
+
61
+ ```typescript
62
+ // urls/tenant.tsx — mounted at include("/tenant/:tenantId", tenantPatterns, { name: "tenant" })
63
+ export const tenantPatterns = urls(({ path }) => [
64
+ path("/", (ctx) => {
65
+ // tenantId is auto-filled from ctx.params — no need to pass it
66
+ ctx.reverse(".settings"); // "/tenant/acme/settings" (when visiting /tenant/acme)
67
+ ctx.reverse(".user", { userId: "u1" }); // "/tenant/acme/users/u1" (tenantId auto-filled, userId explicit)
68
+ ctx.reverse(".settings", { tenantId: "other" }); // "/tenant/other/settings" (explicit override)
69
+
70
+ // Global names also get auto-filled params
71
+ ctx.reverse("tenant.settings"); // "/tenant/acme/settings"
72
+ return <TenantIndex />;
73
+ }, { name: "index" }),
74
+ path("/settings", SettingsPage, { name: "settings" }),
75
+ path("/users/:userId", UserPage, { name: "user" }),
76
+ ]);
77
+ ```
78
+
79
+ Auto-fill uses `{ ...ctx.params, ...hrefParams }` — current request params are defaults, explicit params win. Params not needed by the target route are silently ignored.
80
+
81
+ ### Global names (unprefixed)
82
+
83
+ Unprefixed names resolve against the full named-routes map (the generated `router.named-routes.gen.ts`).
84
+
85
+ ```typescript
86
+ (ctx) => {
87
+ ctx.reverse("magazine.index"); // "/magazine"
88
+ ctx.reverse("blog.post", { slug: "hello" }); // "/blog/hello"
89
+ };
90
+ ```
91
+
92
+ ### reverse with search params
93
+
94
+ When a route has a `search` schema, pass a typed search object as the third argument:
95
+
96
+ ```typescript
97
+ path("/search", (ctx) => {
98
+ // Generates: /search?q=react&page=2
99
+ const url = ctx.reverse("search", {}, { q: "react", page: 2 });
100
+ return <Link to={url}>Search React</Link>;
101
+ }, { name: "search", search: { q: "string", page: "number?" } })
102
+ ```
103
+
104
+ ### scopedReverse() - type-safe ctx.reverse
105
+
106
+ Wraps `ctx.reverse` with local route type information for autocomplete and validation:
107
+
108
+ ```typescript
109
+ import { scopedReverse } from "@rangojs/router";
110
+
111
+ path("/product/:slug", (ctx) => {
112
+ const reverse = scopedReverse<typeof shopPatterns>(ctx.reverse);
113
+
114
+ reverse("cart"); // Type-safe local name
115
+ reverse("product", { slug: "widget" }); // Type-safe with params
116
+ reverse("blog.post"); // Absolute names (dot notation) always allowed
117
+ reverse("/about"); // Path-based always allowed
118
+
119
+ return <ProductPage slug={ctx.params.slug} />;
120
+ }, { name: "product" })
121
+ ```
122
+
123
+ ## Client: href()
124
+
125
+ Plain function for absolute path-based URLs. No hook needed - works anywhere.
126
+
127
+ ```typescript
128
+ "use client";
129
+ import { href, Link } from "@rangojs/router/client";
130
+
131
+ function GlobalNav() {
132
+ return (
133
+ <nav>
134
+ <Link to={href("/")}>Home</Link>
135
+ <Link to={href("/about")}>About</Link>
136
+ <Link to={href("/blog/hello")}>Post</Link>
137
+ </nav>
138
+ );
139
+ }
140
+ ```
141
+
142
+ `href()` is an identity function at runtime but provides compile-time validation via `ValidPaths` type. Paths are validated against registered route patterns using `PatternToPath`.
143
+
144
+ ## Client: useHref()
145
+
146
+ Hook that returns a mount-aware href function. Automatically prepends the `include()` mount prefix.
147
+
148
+ ```typescript
149
+ "use client";
150
+ import { useHref, href, Link } from "@rangojs/router/client";
151
+
152
+ // Inside include("/shop", shopPatterns)
153
+ function ShopNav() {
154
+ const href = useHref();
155
+
156
+ return (
157
+ <nav>
158
+ <Link to={href("/")}>Shop Home</Link> {/* "/shop/" */}
159
+ <Link to={href("/cart")}>Cart</Link> {/* "/shop/cart" */}
160
+ <Link to={href("/product/widget")}>W</Link> {/* "/shop/product/widget" */}
161
+ </nav>
162
+ );
163
+ }
164
+ ```
165
+
166
+ Use `useHref()` for local navigation within a mounted module. Use the bare `href()` function for absolute paths outside the current mount.
167
+
168
+ ## Client: useMount()
169
+
170
+ Returns the current `include()` mount path. Useful for building custom logic based on mount location.
171
+
172
+ ```typescript
173
+ "use client";
174
+ import { useMount } from "@rangojs/router/client";
175
+
176
+ function MountInfo() {
177
+ const mount = useMount(); // "/shop" inside include("/shop", ...)
178
+ // "/" at root level
179
+
180
+ return <span>Mounted at: {mount}</span>;
181
+ }
182
+ ```
183
+
184
+ `useMount()` reads from `MountContext`, which is automatically set by `include()` in the segment tree.
185
+
186
+ ## When to use what
187
+
188
+ | Context | API | Resolves | Use for |
189
+ | ---------------- | ------------------------------- | ------------------------------- | ----------------------------------- |
190
+ | Server handler | `ctx.reverse("name")` | Named routes (local + absolute) | Server-side URL generation |
191
+ | Server handler | `scopedReverse<T>(ctx.reverse)` | Same, with type safety | Type-safe server URLs |
192
+ | Client component | `href("/path")` | Absolute paths | Global navigation |
193
+ | Client component | `useHref()` | Mount-prefixed paths | Local navigation inside `include()` |
194
+ | Client component | `useMount()` | Raw mount path | Custom mount-aware logic |
195
+
196
+ ## Complete example: mounted module
197
+
198
+ ```typescript
199
+ // urls/shop.tsx (server)
200
+ import { urls, scopedReverse } from "@rangojs/router";
201
+
202
+ export const shopPatterns = urls(({ path, layout }) => [
203
+ layout((ctx) => {
204
+ const reverse = scopedReverse<typeof shopPatterns>(ctx.reverse);
205
+ return <ShopLayout cartUrl={reverse("cart")} />;
206
+ }, () => [
207
+ path("/", ShopIndex, { name: "index" }),
208
+ path("/cart", CartPage, { name: "cart" }),
209
+ path("/product/:slug", ProductPage, { name: "product" }),
210
+ ]),
211
+ ]);
212
+
213
+ // urls.tsx (server)
214
+ export const urlpatterns = urls(({ path, include }) => [
215
+ path("/", HomePage, { name: "home" }),
216
+ include("/shop", shopPatterns),
217
+ ]);
218
+ ```
219
+
220
+ ```tsx
221
+ // components/ShopNav.tsx (client)
222
+ "use client";
223
+ import { useHref, href, Link } from "@rangojs/router/client";
224
+
225
+ export function ShopNav() {
226
+ const localHref = useHref();
227
+
228
+ return (
229
+ <nav>
230
+ {/* Local paths - auto-prefixed with /shop */}
231
+ <Link to={localHref("/cart")}>Cart</Link>
232
+ <Link to={localHref("/product/widget")}>Widget</Link>
233
+
234
+ {/* Absolute path - no prefix */}
235
+ <Link to={href("/")}>Home</Link>
236
+ </nav>
237
+ );
238
+ }
239
+ ```