@rangojs/router 0.16.0 → 0.17.0

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 (245) hide show
  1. package/README.md +254 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  4. package/dist/types/browser/partial-update.d.ts +11 -0
  5. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  6. package/dist/types/browser/rsc-router.d.ts +1 -1
  7. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  8. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  9. package/dist/types/cache/cache-scope.d.ts +34 -5
  10. package/dist/types/cache/handle-capture.d.ts +41 -3
  11. package/dist/types/cache/handle-snapshot.d.ts +53 -13
  12. package/dist/types/cache/segment-codec.d.ts +10 -3
  13. package/dist/types/cache/taint.d.ts +1 -1
  14. package/dist/types/cache/types.d.ts +15 -3
  15. package/dist/types/client.d.ts +4 -4
  16. package/dist/types/client.rsc.d.ts +4 -5
  17. package/dist/types/components/DefaultDocument.d.ts +5 -2
  18. package/dist/types/context-var.d.ts +6 -5
  19. package/dist/types/escape-script.d.ts +1 -1
  20. package/dist/types/handles/MetaTags.d.ts +5 -4
  21. package/dist/types/handles/Scripts.d.ts +8 -7
  22. package/dist/types/handles/meta.d.ts +2 -2
  23. package/dist/types/handles/script.d.ts +17 -16
  24. package/dist/types/href-client.d.ts +1 -1
  25. package/dist/types/html.d.ts +12 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/index.rsc.d.ts +3 -2
  28. package/dist/types/loader-store.d.ts +7 -4
  29. package/dist/types/reverse.d.ts +36 -4
  30. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  31. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  32. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  33. package/dist/types/route-types.d.ts +1 -1
  34. package/dist/types/router/debug-manifest.d.ts +3 -2
  35. package/dist/types/router/error-handling.d.ts +20 -0
  36. package/dist/types/router/handler-context.d.ts +6 -0
  37. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  38. package/dist/types/router/middleware-types.d.ts +3 -2
  39. package/dist/types/router/router-context.d.ts +3 -1
  40. package/dist/types/router/router-interfaces.d.ts +24 -2
  41. package/dist/types/router/router-options.d.ts +8 -5
  42. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  43. package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
  44. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  45. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
  46. package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
  47. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  48. package/dist/types/router/segment-wrappers.d.ts +1 -1
  49. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  50. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  51. package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
  52. package/dist/types/rsc/shell-capture.d.ts +5 -4
  53. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  54. package/dist/types/rsc/types.d.ts +10 -0
  55. package/dist/types/server/context.d.ts +33 -3
  56. package/dist/types/server/handle-store.d.ts +26 -2
  57. package/dist/types/server/request-context.d.ts +24 -21
  58. package/dist/types/ssr/index.d.ts +15 -0
  59. package/dist/types/static-handler.d.ts +4 -1
  60. package/dist/types/testing/generated-routes.d.ts +2 -2
  61. package/dist/types/testing/internal/context.d.ts +1 -1
  62. package/dist/types/testing/render-route.d.ts +32 -6
  63. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  64. package/dist/types/testing/vitest.d.ts +35 -1
  65. package/dist/types/theme/ThemeScript.d.ts +3 -3
  66. package/dist/types/types/cache-types.d.ts +14 -10
  67. package/dist/types/types/handler-context.d.ts +14 -0
  68. package/dist/types/types/loader-types.d.ts +8 -6
  69. package/dist/types/urls/response-types.d.ts +3 -12
  70. package/dist/types/use-loader.d.ts +12 -3
  71. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  72. package/dist/types/vite/rango.d.ts +1 -1
  73. package/dist/vite/index.js +1381 -1346
  74. package/package.json +2 -2
  75. package/skills/api-client/SKILL.md +22 -10
  76. package/skills/breadcrumbs/SKILL.md +65 -22
  77. package/skills/bundle-analysis/SKILL.md +22 -22
  78. package/skills/cache-guide/SKILL.md +207 -97
  79. package/skills/caching/SKILL.md +295 -93
  80. package/skills/catalog.json +16 -16
  81. package/skills/client-urls/SKILL.md +53 -31
  82. package/skills/cloudflare/SKILL.md +72 -20
  83. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  84. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  85. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  86. package/skills/comparison/SKILL.md +5 -0
  87. package/skills/comparison/references/framework-comparison.md +39 -33
  88. package/skills/composability/SKILL.md +38 -29
  89. package/skills/css/SKILL.md +10 -13
  90. package/skills/debug-manifest/SKILL.md +74 -31
  91. package/skills/defer-hydration/SKILL.md +2 -1
  92. package/skills/deployment-caching/SKILL.md +6 -1
  93. package/skills/document-cache/SKILL.md +92 -28
  94. package/skills/fonts/SKILL.md +16 -8
  95. package/skills/handler-use/SKILL.md +53 -17
  96. package/skills/hooks/SKILL.md +28 -20
  97. package/skills/hooks/data.md +23 -3
  98. package/skills/hooks/handle-and-actions.md +31 -16
  99. package/skills/hooks/navigation.md +39 -10
  100. package/skills/hooks/outlets.md +13 -6
  101. package/skills/hooks/state.md +37 -15
  102. package/skills/hooks/urls.md +7 -5
  103. package/skills/host-router/SKILL.md +64 -12
  104. package/skills/i18n/SKILL.md +60 -34
  105. package/skills/intercept/SKILL.md +161 -26
  106. package/skills/layout/SKILL.md +106 -50
  107. package/skills/links/SKILL.md +51 -19
  108. package/skills/loader/SKILL.md +242 -106
  109. package/skills/middleware/SKILL.md +119 -23
  110. package/skills/migrate-nextjs/SKILL.md +100 -45
  111. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  112. package/skills/migrate-react-router/SKILL.md +49 -38
  113. package/skills/migrate-react-router/component-migration.md +25 -6
  114. package/skills/migrate-react-router/data-and-actions.md +18 -5
  115. package/skills/migrate-react-router/route-mapping.md +12 -6
  116. package/skills/mime-routes/SKILL.md +12 -7
  117. package/skills/observability/SKILL.md +28 -14
  118. package/skills/parallel/SKILL.md +75 -39
  119. package/skills/ppr/SKILL.md +345 -150
  120. package/skills/prerender/SKILL.md +189 -100
  121. package/skills/rango/SKILL.md +65 -34
  122. package/skills/react-compiler/SKILL.md +6 -5
  123. package/skills/response-routes/SKILL.md +133 -25
  124. package/skills/route/SKILL.md +165 -60
  125. package/skills/router-setup/SKILL.md +418 -240
  126. package/skills/scripts/SKILL.md +31 -20
  127. package/skills/server-actions/SKILL.md +220 -59
  128. package/skills/shell-manifest/SKILL.md +12 -7
  129. package/skills/streams-and-websockets/SKILL.md +32 -14
  130. package/skills/tailwind/SKILL.md +37 -33
  131. package/skills/testing/SKILL.md +68 -32
  132. package/skills/testing/cache-prerender.md +55 -29
  133. package/skills/testing/client-components.md +59 -6
  134. package/skills/testing/e2e-parity.md +12 -2
  135. package/skills/testing/flight.md +19 -11
  136. package/skills/testing/handles.md +8 -5
  137. package/skills/testing/loader.md +8 -5
  138. package/skills/testing/middleware.md +8 -3
  139. package/skills/testing/predicates.md +120 -0
  140. package/skills/testing/render-handler.md +70 -22
  141. package/skills/testing/response-routes.md +11 -4
  142. package/skills/testing/reverse-and-types.md +20 -14
  143. package/skills/testing/server-actions.md +12 -3
  144. package/skills/testing/server-tree.md +9 -0
  145. package/skills/testing/setup.md +9 -5
  146. package/skills/theme/SKILL.md +106 -29
  147. package/skills/typesafety/SKILL.md +20 -9
  148. package/skills/typesafety/env-and-bindings.md +126 -47
  149. package/skills/typesafety/generated-files-and-cli.md +26 -14
  150. package/skills/typesafety/params-and-search.md +44 -19
  151. package/skills/typesafety/route-types.md +50 -14
  152. package/skills/use-cache/SKILL.md +200 -82
  153. package/skills/vercel/SKILL.md +21 -11
  154. package/skills/view-transitions/SKILL.md +41 -20
  155. package/src/__augment-tests__/augmented.check.ts +26 -1
  156. package/src/browser/index.ts +2 -2
  157. package/src/browser/navigation-bridge.ts +3 -4
  158. package/src/browser/navigation-transaction.ts +22 -2
  159. package/src/browser/partial-update.ts +35 -25
  160. package/src/browser/react/ScrollRestoration.tsx +6 -6
  161. package/src/browser/rsc-router.tsx +1 -1
  162. package/src/browser/scroll-restoration.ts +53 -6
  163. package/src/cache/cache-exec-scope.ts +37 -4
  164. package/src/cache/cache-key-utils.ts +1 -6
  165. package/src/cache/cache-runtime.ts +230 -61
  166. package/src/cache/cache-scope.ts +76 -12
  167. package/src/cache/document-cache.ts +15 -0
  168. package/src/cache/handle-capture.ts +90 -10
  169. package/src/cache/handle-snapshot.ts +132 -28
  170. package/src/cache/segment-codec.ts +26 -7
  171. package/src/cache/tag-invalidation.ts +4 -3
  172. package/src/cache/taint.ts +1 -1
  173. package/src/cache/types.ts +16 -3
  174. package/src/client.rsc.tsx +10 -9
  175. package/src/client.tsx +5 -4
  176. package/src/components/DefaultDocument.tsx +7 -2
  177. package/src/context-var.ts +6 -5
  178. package/src/escape-script.ts +1 -1
  179. package/src/handles/MetaTags.tsx +13 -11
  180. package/src/handles/Scripts.tsx +11 -10
  181. package/src/handles/meta.ts +2 -2
  182. package/src/handles/script.ts +17 -16
  183. package/src/href-client.ts +1 -1
  184. package/src/html.ts +12 -0
  185. package/src/index.rsc.ts +5 -0
  186. package/src/index.ts +1 -0
  187. package/src/loader-store.ts +7 -4
  188. package/src/prerender/build-shell-capture.ts +8 -11
  189. package/src/reverse.ts +44 -4
  190. package/src/route-definition/dsl-helpers.ts +117 -30
  191. package/src/route-definition/helpers-types.ts +33 -9
  192. package/src/route-definition/resolve-handler-use.ts +121 -6
  193. package/src/route-types.ts +0 -3
  194. package/src/router/debug-manifest.ts +10 -2
  195. package/src/router/error-handling.ts +51 -15
  196. package/src/router/handler-context.ts +34 -25
  197. package/src/router/intercept-resolution.ts +125 -39
  198. package/src/router/loader-resolution.ts +56 -11
  199. package/src/router/match-api.ts +12 -17
  200. package/src/router/match-middleware/background-revalidation.ts +161 -96
  201. package/src/router/match-middleware/cache-lookup.ts +93 -10
  202. package/src/router/match-middleware/cache-store.ts +17 -90
  203. package/src/router/middleware-types.ts +3 -5
  204. package/src/router/middleware.ts +52 -9
  205. package/src/router/prerender-match.ts +53 -19
  206. package/src/router/route-snapshot.ts +11 -1
  207. package/src/router/router-context.ts +2 -0
  208. package/src/router/router-interfaces.ts +25 -2
  209. package/src/router/router-options.ts +8 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -12
  211. package/src/router/segment-resolution/loader-cache.ts +205 -45
  212. package/src/router/segment-resolution/loader-mask.ts +12 -22
  213. package/src/router/segment-resolution/loader-snapshot.ts +6 -6
  214. package/src/router/segment-resolution/mask-nested.ts +7 -7
  215. package/src/router/segment-resolution/revalidation.ts +34 -6
  216. package/src/router/segment-wrappers.ts +4 -0
  217. package/src/rsc/render-pipeline.ts +3 -0
  218. package/src/rsc/response-route-handler.ts +11 -3
  219. package/src/rsc/rsc-rendering.ts +8 -45
  220. package/src/rsc/shell-capture-constants.ts +13 -0
  221. package/src/rsc/shell-capture.ts +111 -90
  222. package/src/rsc/ssr-setup.ts +14 -8
  223. package/src/rsc/types.ts +11 -0
  224. package/src/server/context.ts +88 -11
  225. package/src/server/cookie-store.ts +5 -4
  226. package/src/server/handle-store.ts +232 -7
  227. package/src/server/request-context.ts +35 -30
  228. package/src/ssr/index.tsx +40 -7
  229. package/src/static-handler.ts +4 -1
  230. package/src/testing/flight-runtime.d.ts +21 -0
  231. package/src/testing/generated-routes.ts +2 -2
  232. package/src/testing/internal/context.ts +1 -1
  233. package/src/testing/internal/flight-client-globals.ts +7 -5
  234. package/src/testing/render-route.tsx +85 -24
  235. package/src/testing/run-loader.ts +5 -1
  236. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  237. package/src/testing/vitest.ts +62 -1
  238. package/src/theme/ThemeScript.tsx +3 -3
  239. package/src/types/cache-types.ts +14 -10
  240. package/src/types/handler-context.ts +14 -0
  241. package/src/types/loader-types.ts +8 -6
  242. package/src/urls/response-types.ts +3 -23
  243. package/src/use-loader.tsx +12 -3
  244. package/src/vite/plugins/use-cache-transform.ts +118 -74
  245. package/src/vite/rango.ts +7 -3
@@ -44,10 +44,10 @@ and multi-region composition without replacing its routing or data model.
44
44
 
45
45
  | Dimension | **Rango** | Next.js (App Router) | TanStack Start | Waku |
46
46
  | ----------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
47
- | Routing model | Code DSL (`urls()`/`include()`), named + `reverse()` | File-system convention | File or code, type-first | File-system (+ `createPages`) |
47
+ | Routing model | Code DSL (`urls()`/`include()`), named + `ctx.reverse()` | File-system convention | File or code, type-first | File-system (+ `createPages`) |
48
48
  | Foundation | Vite + plugin-rsc | Turbopack (default in 16; Webpack opt-in) | [Vite or Rsbuild](https://tanstack.com/start/latest/docs/framework/react/overview) | Vite + plugin-rsc |
49
49
  | RSC model | RSC-first | RSC-first | SSR/client-first; RSC opt-in (experimental) | RSC-first |
50
- | Type-safe routes/params | Generated names, params, search, `reverse()`, response MIME | Stable typed links; route-local param types | Best-in-class | Typed path params |
50
+ | Type-safe routes/params | Generated names, params, search, `ctx.reverse()`/`href()`, response MIME | Stable typed links; route-local param types | Best-in-class | Typed path params |
51
51
  | Client render selection | Per-segment/loader `revalidate()` policy; typed action and result matching | Automatic segment reuse; refresh/invalidation APIs | Match/loader reload policy | Route refetch/reload |
52
52
  | Slots and intercepts | Code-defined named slots with their own loaders/policy; conditional alternate soft-navigation compositions | File-system `@slot` + intercept conventions | Route masking, no parallel RSC slot graph | No equivalent route-slot graph |
53
53
  | Caching | One segment store = runtime + build-time + `"use cache"`, tags, SWR | Cache Components plus distinct server/client cache lifecycles | Router loader cache + Query integration + HTTP/CDN policy | Minimal |
@@ -81,16 +81,16 @@ const router = createRouter().routes(({ path }) => [
81
81
  As the application grows, each requirement adds one local primitive to that same
82
82
  tree:
83
83
 
84
- | When you need to… | Add… |
85
- | ------------------------------------------ | --------------------------------- |
86
- | make navigation refactor-safe | a route `name` and `reverse()` |
87
- | split the application into modules | `urls()` and `include()` |
88
- | keep request data live beneath cached UI | `loader()` |
89
- | cache or prerender a shared shell | `cache()` or `Prerender()` |
90
- | choose what updates after an action | `revalidate()` |
91
- | render independent regions | `parallel()` and named slots |
92
- | open a route as a modal on soft navigation | `intercept()` |
93
- | inspect where request time went | `debugPerformance` or `telemetry` |
84
+ | When you need to… | Add… |
85
+ | ------------------------------------------ | ---------------------------------- |
86
+ | make navigation refactor-safe | a route `name` and `ctx.reverse()` |
87
+ | split the application into modules | `urls()` and `include()` |
88
+ | keep request data live beneath cached UI | `loader()` |
89
+ | cache or prerender a shared shell | `cache()` or `Prerender()` |
90
+ | choose what updates after an action | `revalidate()` |
91
+ | render independent regions | `parallel()` and named slots |
92
+ | open a route as a modal on soft navigation | `intercept()` |
93
+ | inspect where request time went | `debugPerformance` or `telemetry` |
94
94
 
95
95
  Nothing in the first route requires learning the last row. More importantly, the
96
96
  last row does not force a migration to a second router, client data library, or
@@ -140,7 +140,11 @@ configure a node, so they can be imported or composed through small factories:
140
140
  ```tsx
141
141
  const withAccountPolicy = () => [
142
142
  middleware(requireUser),
143
- revalidate((ctx) => ctx.isAction(AccountActions) || undefined),
143
+ // After an action, re-run only for account actions (a hard decision that ends
144
+ // the chain for later revalidators); on navigation, keep the default.
145
+ revalidate((ctx) =>
146
+ ctx.isAction() ? ctx.isAction(AccountActions) : undefined,
147
+ ),
144
148
  ];
145
149
 
146
150
  const accountPatterns = urls(({ path }) => [
@@ -208,7 +212,7 @@ you statically generate your top 1,000 products and serve the long tail
208
212
  dynamically, from one definition. Next.js approximates this with
209
213
  `generateStaticParams` + `dynamicParams`, but that is two separate knobs rather
210
214
  than one decision point inside your handler. See
211
- [prerender-api-design.md](../../../docs/prerender-api-design.md).
215
+ [prerender-api-design.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/prerender-api-design.md).
212
216
 
213
217
  ### Progressive enhancement is a tested contract
214
218
 
@@ -297,8 +301,8 @@ The router gives you guarantees most file-based routers leave implicit:
297
301
  only; parallel siblings do not bleed into each other.
298
302
  - **Dev/prod matching parity** — one trie is used in both dev and production, so
299
303
  matching cannot drift between them. See
300
- [matching-and-lazy-discovery.md](../../../docs/internal/matching-and-lazy-discovery.md)
301
- and [execution-model.md](../../../docs/internal/execution-model.md).
304
+ [matching-and-lazy-discovery.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/internal/matching-and-lazy-discovery.md)
305
+ and [execution-model.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/internal/execution-model.md).
302
306
 
303
307
  ### Observability you do not wire yourself
304
308
 
@@ -311,7 +315,7 @@ Next.js also ships automatic OTel instrumentation; Rango's distinction is that t
311
315
  same router-owned phase registry drives its traces, local waterfall, and
312
316
  `Server-Timing` output. TanStack's strength is client/data devtools; Waku does not
313
317
  ship an equivalent request-phase model. See
314
- [telemetry.md](../../../docs/telemetry.md).
318
+ [telemetry.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/telemetry.md).
315
319
 
316
320
  ### Deploy to Node, Cloudflare, and Vercel — and host many apps behind one entry
317
321
 
@@ -367,15 +371,17 @@ old-deployment request pinning.
367
371
  ### A shipped testing harness for server code
368
372
 
369
373
  The `@rangojs/router/testing/*` entry points give you `runLoader`,
370
- `runMiddleware`, `dispatch` (base `./testing`), `renderHandler` and real Flight
371
- rendering (`renderServerTree`, `findClientBoundaries`, `findElements`) from
372
- `./testing/flight`, `renderRoute` from `./testing/dom`, and a Playwright e2e
373
- harness with dev/prod parity helpers (`parityDescribe`, `expectParity`) from
374
- `./testing/e2e`. You can unit-test a loader, a middleware, or an RSC handler in
375
- isolation. Next.js and Waku ship no official primitives for testing server
376
- components/handlers; TanStack documents testing patterns (build your own harness
377
- from `createRouter`/`createMemoryHistory`) but ships no testing package, and
378
- nothing at the RSC-handler level. See [testing.md](../../../docs/testing.md).
374
+ `runMiddleware`, `runInRequestContext` (server actions), `dispatch` (base
375
+ `./testing`), `renderHandler` and real Flight rendering (`renderServerTree`,
376
+ `findClientBoundaries`, `findElements`) from `./testing/flight`, `renderRoute`
377
+ from `./testing/dom`, a vitest preset from `./testing/vitest`, and a Playwright
378
+ e2e harness with dev/prod parity helpers (`parityDescribe`, `expectParity`) from
379
+ `./testing/e2e`. You can unit-test a loader, a middleware, an action, or an RSC
380
+ handler in isolation. Next.js and Waku ship no official primitives for testing
381
+ server components/handlers; TanStack documents testing patterns (build your own
382
+ harness from `createRouter`/`createMemoryHistory`) but ships no testing package,
383
+ and nothing at the RSC-handler level. See the [testing skill](../../testing/SKILL.md)
384
+ and [testing.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/testing.md).
379
385
 
380
386
  ### Bundle discipline
381
387
 
@@ -386,7 +392,7 @@ the build if React's development bundle leaks into production. Treat those as
386
392
  Rango baselines, not a normalized cross-framework benchmark: application shape,
387
393
  React version, compiler output, and deployment transforms make headline bundle
388
394
  comparisons unreliable. Waku deliberately targets a smaller surface. See
389
- [client-chunking.md](../../../docs/client-chunking.md).
395
+ [client-chunking.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/client-chunking.md).
390
396
 
391
397
  ## Runtime mechanics in depth
392
398
 
@@ -495,7 +501,7 @@ layout(<ShopLayout />, () => [
495
501
  A click from the list renders `ProductQuickView` into `@modal` and preserves the
496
502
  list behind it. A direct visit or reload renders the canonical full page. Back
497
503
  closes the modal and restores the preserved background. The intercept can own its
498
- middleware, layout, loaders, loading/error policy, loader-level caching, and
504
+ middleware, layout, loaders, loading state, and loader-level caching and
499
505
  `revalidate()` rules; the `when` config selector (the 4th argument to
500
506
  `intercept()`) can choose by navigation source.
501
507
 
@@ -521,12 +527,12 @@ There are two distinct things named "revalidate", and the split is deliberate:
521
527
  downstream revalidators in the same decision chain. This is surgical
522
528
  post-action control: "this widget re-renders only when `addToCart` ran; the
523
529
  rest of the tree stays put."
524
- See [is-action-api-design.md](../../../docs/design/is-action-api-design.md).
530
+ See [is-action-api-design.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/design/is-action-api-design.md).
525
531
  2. **`revalidate: false` on `<Link>` / `navigate()`** — shallow navigation. When
526
532
  the pathname is unchanged (a search or hash change), it updates the URL and all
527
533
  location hooks but skips the server fetch and re-render entirely. For filters,
528
534
  tabs, and pagination. See
529
- [shallow-navigation.md](../../../docs/design/shallow-navigation.md).
535
+ [shallow-navigation.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/design/shallow-navigation.md).
530
536
 
531
537
  Both are separate from `revalidateTag()`/`updateTag()`, which hard-purge tagged
532
538
  cache entries. `updateTag()` is awaitable for read-your-own-writes;
@@ -776,7 +782,7 @@ contracts; they do not provide Rango's route-aware tainted-argument keying,
776
782
  non-cacheable typed context variables, handle capture/replay, and loader escape
777
783
  path as one system. TanStack Start and Waku leave this boundary primarily to
778
784
  application architecture. See the [cache guide](../../cache-guide/SKILL.md) and
779
- [`"use cache"` design](../../../docs/use-cache-api-design.md).
785
+ [`"use cache"` design](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/use-cache-api-design.md).
780
786
 
781
787
  ## Performance diagnostics (`debugPerformance`)
782
788
 
@@ -809,7 +815,7 @@ Next.js has no equivalent flip-a-boolean per-request phase waterfall: its automa
809
815
  OTel spans still need an exporter and trace viewer. TanStack's strength is
810
816
  client/data devtools, a different axis from server request-phase diagnostics. Waku
811
817
  does not ship an equivalent phase model. See
812
- [telemetry.md](../../../docs/telemetry.md).
818
+ [telemetry.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/telemetry.md).
813
819
 
814
820
  ## Coming from a specific framework
815
821
 
@@ -819,7 +825,7 @@ layers for a code-defined, refactor-safe router and one caching model. You gain
819
825
  Cloudflare as a first-class target and phase spans for free. You give up the
820
826
  largest ecosystem and talent pool in the React world — the real cost.
821
827
 
822
- **From TanStack Start.** You get comparable type-safe routing (`reverse()`, typed
828
+ **From TanStack Start.** You get comparable type-safe routing (`ctx.reverse()`/`href()`, typed
823
829
  params/search, even typed response MIME via `.json()`/`.text()`/`.image()`), plus a
824
830
  true RSC-first model (zero-bundle server components, Flight) that TanStack's
825
831
  client-first core added later and opt-in. You give up TanStack's best-in-class
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: composability
3
- description: Reusable composition patterns with globally importable route helpers in @rangojs/router. Use when sharing loaders, middleware, or handler logic across multiple route files, or avoiding copy-pasting the same route setup everywhere.
3
+ description: Reusable composition patterns with globally importable route helpers and include() modules in @rangojs/router. Use when sharing loaders, middleware, or caching config across multiple route files, splitting routes into eager or code-split include() groups, or avoiding copy-pasting the same route setup everywhere.
4
4
  argument-hint: "pattern-name"
5
5
  ---
6
6
 
@@ -8,6 +8,11 @@ argument-hint: "pattern-name"
8
8
 
9
9
  Route helpers can be imported directly from `@rangojs/router` and used to build reusable composition factories. This enables sharing common route configurations across multiple routes and modules.
10
10
 
11
+ Use this skill when the same config (auth middleware, caching, loading and error
12
+ UI, revalidation) repeats across routes, when splitting a route tree into
13
+ modules with `include()`, or when sizing code-split route groups. To attach
14
+ defaults to one handler instead of a factory, see `/handler-use`.
15
+
11
16
  ## Globally Importable Helpers
12
17
 
13
18
  These helpers can be imported and called outside the `urls()` callback parameter:
@@ -24,10 +29,11 @@ import {
24
29
  intercept,
25
30
  errorBoundary,
26
31
  notFoundBoundary,
32
+ transition,
27
33
  } from "@rangojs/router";
28
34
  ```
29
35
 
30
- They work because they use AsyncLocalStorage internally and resolve context at call time, not import time.
36
+ They work because they read the active `urls()` builder from AsyncLocalStorage at call time, not import time. Calling one outside a running `urls()` builder throws a `DslContextError`.
31
37
 
32
38
  ## Why path() and include() Are Not Global
33
39
 
@@ -49,26 +55,25 @@ The globally importable helpers (`cache`, `middleware`, `loading`, etc.) are con
49
55
  Define reusable factories that return arrays of use items:
50
56
 
51
57
  ```typescript
52
- import { cache, revalidate, loading, errorBoundary, middleware } from "@rangojs/router";
58
+ // route-config.tsx
59
+ import type { ReactNode } from "react";
60
+ import { cache, loading, errorBoundary, middleware } from "@rangojs/router";
61
+ import { authMiddleware, loggingMiddleware } from "./middleware";
53
62
 
54
63
  // Shared caching configuration
55
- const withCaching = () => [
56
- cache({ ttl: 600_000 }),
57
- // Defer on navigation (|| undefined) so each route keeps its own param/search
58
- // revalidation default; only force a re-run when an action ran.
59
- revalidate(({ actionId }) => (actionId ? true : undefined)),
64
+ export const withCaching = () => [
65
+ cache({ ttl: 600 }), // seconds
60
66
  ];
61
67
 
62
68
  // Shared loading and error handling
63
- const withLoadingAndError = (skeleton: ReactNode) => [
69
+ export const withLoadingAndError = (skeleton: ReactNode) => [
64
70
  loading(skeleton),
65
71
  errorBoundary(() => <div>Something went wrong</div>),
66
72
  ];
67
73
 
68
- // Shared auth middleware
69
- const withAuth = () => [
70
- middleware(authMiddleware),
71
- middleware(loggingMiddleware),
74
+ // Shared middleware (the array form registers both in order)
75
+ export const withAuth = () => [
76
+ middleware([authMiddleware, loggingMiddleware]),
72
77
  ];
73
78
  ```
74
79
 
@@ -97,7 +102,7 @@ const withAuth = () => [
97
102
 
98
103
  ## Using Factories in Routes
99
104
 
100
- Place factory calls inside `path()` or `layout()` use callbacks. The returned arrays are flattened automatically (up to 3 levels):
105
+ Place factory calls inside any use callback (`path()`, `layout()`, `parallel()`, `intercept()`, `loader()`), as long as every item is valid there. The returned arrays are flattened automatically (up to 3 levels):
101
106
 
102
107
  ```typescript
103
108
  import { urls } from "@rangojs/router";
@@ -126,13 +131,10 @@ Factories can be defined in shared modules and reused across separate `urls()` d
126
131
 
127
132
  ```typescript
128
133
  // src/route-config.ts
129
- import { cache, revalidate, middleware } from "@rangojs/router";
134
+ import { cache, middleware } from "@rangojs/router";
130
135
  import { authMiddleware } from "./middleware/auth";
131
136
 
132
- export const withPublicDefaults = () => [
133
- cache({ ttl: 300 }),
134
- revalidate(({ actionId }) => (actionId ? true : undefined)),
135
- ];
137
+ export const withPublicDefaults = () => [cache({ ttl: 300 })];
136
138
 
137
139
  export const withProtectedDefaults = () => [
138
140
  middleware(authMiddleware),
@@ -195,7 +197,10 @@ urls(({ include }) => [
195
197
  ]);
196
198
  ```
197
199
 
198
- The split module exposes its `urls()` value as the default export (convention):
200
+ The provider must resolve to a `urls()` value — either as the module's `default`
201
+ export (the usual form) or returned directly
202
+ (`() => import("./shop-patterns").then((m) => m.shopPatterns)`). Anything else
203
+ throws when the group is first loaded:
199
204
 
200
205
  ```typescript
201
206
  // src/shop-patterns.ts
@@ -219,7 +224,7 @@ splitting a thin group buys little). Both match identically at runtime — only
219
224
  module's runtime evaluation timing differs.
220
225
 
221
226
  What you do NOT lose by splitting: build-time discovery `await`s the provider, so
222
- `href()`, `reverse()`, generated route types, and prerender still see every route
227
+ `href()`, `ctx.reverse()`, generated route types, and prerender still see every route
223
228
  in the group — including nested `include()`s inside the split module. Only the
224
229
  module's runtime evaluation defers. `rango generate` resolves the `() => import()`
225
230
  the same way, so a code-split group is still fully typed.
@@ -227,8 +232,10 @@ the same way, so a code-split group is still fully typed.
227
232
  ### Sizing async include groups (measured)
228
233
 
229
234
  The first request into an async group pays that group's chunk import; every
230
- request after that is flat. Measured on a deployed Cloudflare worker with
231
- 26k routes (2026-07, warm RTT floor ~23 ms):
235
+ request after that is flat. One cold, single-run measurement right after
236
+ deploying a 26k-route app to Cloudflare Workers (2026-07, warm RTT floor
237
+ ~23 ms, see `tests/cloudflare-stress-demo/BENCHMARK-2026-07-04-edge-26k.md`).
238
+ Treat the numbers as orders of magnitude:
232
239
 
233
240
  | Group size | First-hit latency |
234
241
  | -------------------------- | ------------------------------------ |
@@ -261,11 +268,12 @@ erases first-hit cost for the isolate entirely.
261
268
  For typed factories, import the composition types:
262
269
 
263
270
  ```typescript
271
+ import { cache, loading, middleware } from "@rangojs/router";
264
272
  import type { RouteUseItem, LayoutUseItem, UseItems } from "@rangojs/router";
265
273
 
266
274
  // Factory for path() use callbacks
267
275
  const withCaching = (): RouteUseItem[] => [
268
- cache({ ttl: 600_000 }),
276
+ cache({ ttl: 600 }),
269
277
  ];
270
278
 
271
279
  // Factory for layout() use callbacks
@@ -281,12 +289,13 @@ const withEverything = (): UseItems<RouteUseItem> => [
281
289
  ```
282
290
 
283
291
  - `RouteUseItem[]` -- flat array for `path()` use callbacks
284
- - `LayoutUseItem[]` -- flat array for `layout()` use callbacks
285
- - `UseItems<T>` -- allows nested arrays from composing factories together
292
+ - `LayoutUseItem[]` -- flat array for `layout()` use callbacks (every item type)
293
+ - `UseItems<T>` -- allows one level of nested arrays from composing factories together
294
+ - `HandlerUseItem` -- items a `handler.use` callback may return (see `/handler-use`)
286
295
 
287
296
  ## Rules
288
297
 
289
- - Helpers execute lazily -- factory functions are defined anywhere, but only called inside a `urls()` context (within `path()` or `layout()` use callbacks)
290
- - Calling helpers outside a `urls()` context throws an error
298
+ - Helpers execute lazily -- factory functions are defined anywhere, but only called while a `urls()` builder runs (inside a use callback)
299
+ - Calling helpers outside a `urls()` builder throws a `DslContextError`
291
300
  - Nested arrays from factories are flattened automatically via `.flat(3)`
292
- - `path()` and `include()` cannot be used in factories -- they define route structure and must remain visible in the `urls()` callback
301
+ - `path()` and `include()` are not exported as standalone helpers -- they define route structure and must remain visible in the `urls()` callback
@@ -7,9 +7,10 @@ argument-hint:
7
7
  # CSS imports
8
8
 
9
9
  Document/app CSS in Rango lives in the Document `<head>`, loaded with Vite's
10
- `?url` import and a `precedence`-managed `<link rel="stylesheet">`. This page is
11
- the why and the one cross-app caveat; `/tailwind` is the concrete setup, `/theme`
12
- is dark mode, `/fonts` is fonts.
10
+ `?url` import and a `precedence`-managed `<link rel="stylesheet">`. This page
11
+ explains that pattern, when a side-effect `import "./x.css"` is fine, and how CSS
12
+ behaves across host-router apps. `/tailwind` is the concrete Tailwind setup,
13
+ `/theme` is dark mode, `/fonts` is fonts.
13
14
 
14
15
  ## The pattern
15
16
 
@@ -18,7 +19,7 @@ is dark mode, `/fonts` is fonts.
18
19
  "use client";
19
20
 
20
21
  import type { ReactNode } from "react";
21
- import { MetaTags } from "@rangojs/router/client";
22
+ import { Html } from "@rangojs/router/client";
22
23
  import styles from "./index.css?url";
23
24
 
24
25
  export function Document({ children }: { children: ReactNode }) {
@@ -27,7 +28,7 @@ export function Document({ children }: { children: ReactNode }) {
27
28
  <head>
28
29
  <link rel="preload" href={styles} as="style" precedence="default" />
29
30
  <link rel="stylesheet" href={styles} precedence="default" />
30
- <MetaTags />
31
+ <Html.Meta />
31
32
  </head>
32
33
  <body>{children}</body>
33
34
  </html>
@@ -50,14 +51,10 @@ A client-side navigation that crosses an app boundary is a **full document load*
50
51
  (the server returns `X-RSC-Reload` on an app switch — see `/host-router`), so the
51
52
  target app's entire document — its stylesheets, theme, meta — is re-established
52
53
  by the target app's own load. Each app owns its document; how one app renders a
53
- stylesheet has no effect on another.
54
-
55
- (This replaced an earlier soft cross-app swap. Under it, a stylesheet shared
56
- across apps by `href` — classically every app's `@import "tailwindcss"` compiling
57
- to one hashed asset — could be silently dropped by React's by-`href` resource
58
- dedup when the apps disagreed on `precedence` (one unmanaged, one managed). The
59
- full reload removes that footgun entirely, which is the main reason cross-app
60
- navigation is a hard boundary.)
54
+ stylesheet has no effect on another. In particular, apps may disagree on
55
+ `precedence` for a stylesheet they share by `href` (for example every app's
56
+ `@import "tailwindcss"` compiling to one hashed asset) without React's by-`href`
57
+ dedup dropping it.
61
58
 
62
59
  ## Side-effect imports vs `?url`
63
60
 
@@ -1,30 +1,49 @@
1
1
  ---
2
2
  name: debug-manifest
3
- description: Debug and inspect route manifest structure. Use when routes aren't matching as expected, you need to see the generated route tree, or a path resolves to the wrong handler.
3
+ description: Inspect the router's route manifest (route/layout tree, shortCodes, parent links, loader/middleware flags) and diff it between versions. Use when routes aren't matching as expected, you need to see the generated route tree, or a path resolves to the wrong handler.
4
4
  argument-hint:
5
5
  ---
6
6
 
7
7
  # Debug Manifest
8
8
 
9
- Inspect the route manifest to verify parent relationships, shortCodes, and route structure.
9
+ Dump the route manifest the router builds from your `urls()` tree to verify
10
+ parent relationships, shortCodes, and which segments carry loaders, middleware,
11
+ error boundaries, parallel slots, or intercepts. This is a development aid; the
12
+ diff helpers below are internal and may change between releases.
10
13
 
11
- ## Programmatic Access
14
+ ## Programmatic access
12
15
 
13
- Call `router.debugManifest()` — an `async` method on the router instance:
16
+ The router returned by `createRouter()` has an async `debugManifest()` method:
14
17
 
15
18
  ```typescript
16
19
  import { router } from "./router.js";
17
20
 
18
- // Only in development
21
+ // Development only
19
22
  if (process.env.NODE_ENV !== "production") {
20
23
  const manifest = await router.debugManifest();
21
24
  console.log(JSON.stringify(manifest, null, 2));
22
25
  }
23
26
  ```
24
27
 
25
- ## Manifest Structure
28
+ Run it on the server side (for example from a dev-only route handler), since
29
+ it evaluates your `urls()` tree. The result type is `SerializedManifest`
30
+ (`import type { SerializedManifest } from "@rangojs/router"`).
26
31
 
27
- The programmatic `router.debugManifest()` call returns `{ routes, layouts, totalRoutes, totalLayouts }`:
32
+ ### `include()` groups are not expanded
33
+
34
+ `include()` groups are lazy: the router evaluates one on the first request that
35
+ matches its prefix. `debugManifest()` does not evaluate them. It lists the
36
+ routes and layouts declared directly in each `.routes()` / `urls` mount, so the
37
+ routes inside an included group are missing from `routes` and `totalRoutes`.
38
+ For their names and patterns, read the generated `*.named-routes.gen.ts` file,
39
+ which lists every route including those in `include()` groups.
40
+
41
+ ## Manifest structure
42
+
43
+ `debugManifest()` returns `{ routes, layouts, totalRoutes, totalLayouts }`.
44
+ Every entry has the same fields; `pattern` is present on routes only. Cache
45
+ boundaries are listed under `layouts` with `type: "cache"`. Shape (values are
46
+ illustrative):
28
47
 
29
48
  ```json
30
49
  {
@@ -47,7 +66,12 @@ The programmatic `router.debugManifest()` call returns `{ routes, layouts, total
47
66
  "id": "debug.M0.$root",
48
67
  "shortCode": "M0L0",
49
68
  "type": "layout",
50
- "parentShortCode": null
69
+ "parentShortCode": null,
70
+ "hasLoader": false,
71
+ "hasMiddleware": false,
72
+ "hasErrorBoundary": false,
73
+ "parallelCount": 0,
74
+ "interceptCount": 0
51
75
  }
52
76
  },
53
77
  "totalRoutes": 45,
@@ -55,30 +79,39 @@ The programmatic `router.debugManifest()` call returns `{ routes, layouts, total
55
79
  }
56
80
  ```
57
81
 
58
- ## ShortCode Format
82
+ ## ShortCode format
59
83
 
60
- | Prefix | Meaning |
61
- | ------ | ---------------------------------------- |
62
- | **M** | Mount index (multiple `.routes()` calls) |
63
- | **L** | Layout |
64
- | **C** | Cache boundary |
65
- | **R** | Route |
66
- | **P** | Parallel slot |
84
+ A shortCode is the parent's shortCode plus one segment per level:
67
85
 
68
- Example: `M0L0L1C0R0` = Mount 0 → Root Layout → Nested Layout → Cache → Route
86
+ | Prefix | Meaning |
87
+ | ------ | ----------------------------------------------------------------------- |
88
+ | **M** | Mount index (`.routes()` / `urls` registrations and `include()` mounts) |
89
+ | **L** | Layout (the router adds a root layout, `M<n>L0`) |
90
+ | **C** | Cache boundary |
91
+ | **R** | Route |
92
+ | **P** | Parallel slot |
93
+ | **I** | `include()` scope token, so included routes never collide with siblings |
69
94
 
70
- ## Debugging Checklist
95
+ Example: `M0L0L1C0R0` = Mount 0 → Root Layout → Nested Layout → Cache → Route.
96
+ At runtime, a route from an `include()` placed under the root layout gets a
97
+ shortCode such as `M0L0I0R0`.
71
98
 
72
- 1. **Routes have parents**: `parentShortCode` should NOT be `null` (except root layout)
73
- 2. **Correct hierarchy**: ShortCode should reflect nesting (e.g., `M0L0R0` not `M0R0`)
74
- 3. **Loaders attached**: Check `hasLoader: true` for routes with data requirements
75
- 4. **Intercepts registered**: `interceptCount > 0` for modal/overlay patterns
99
+ ## Debugging checklist
76
100
 
77
- ## Comparing Manifests
101
+ 1. **Correct hierarchy**: the shortCode reflects nesting (`M0L0L1R0` for a
102
+ route in a nested layout, not `M0L0R0`).
103
+ 2. **Parents**: only a mount's root layout (`M<n>L0`) has
104
+ `parentShortCode: null`.
105
+ 3. **Loaders attached**: `hasLoader: true` on routes/layouts that declare
106
+ `loader()`.
107
+ 4. **Intercepts registered**: `interceptCount > 0` where you declared
108
+ `intercept()` for modal/overlay patterns.
109
+ 5. **Slots**: `parallelCount` matches the number of `parallel()` slots.
110
+
111
+ ## Comparing manifests
78
112
 
79
113
  ```typescript
80
114
  import {
81
- serializeManifest,
82
115
  compareManifests,
83
116
  formatManifestDiff,
84
117
  } from "@rangojs/router/__internal";
@@ -91,16 +124,26 @@ const diff = compareManifests(oldManifest, newManifest);
91
124
  console.log(formatManifestDiff(diff));
92
125
  ```
93
126
 
94
- ## Common Issues
127
+ `compareManifests` reports added, removed, and changed routes and layouts
128
+ (field-level: `old` / `new` per changed field).
129
+
130
+ ## Common issues
95
131
 
96
- ### Routes have `parentShortCode: null`
132
+ ### A route sits under the wrong layout
97
133
 
98
- Routes should have a layout parent. Check that `urls()` handler is being wrapped in root layout.
134
+ Check that the `layout()` call wraps the route in its `use` callback
135
+ (`layout(<Shell />, () => [path(...)])`). A `path()` listed next to a
136
+ `layout()` is a sibling, not a child.
99
137
 
100
- ### Missing layouts in hierarchy
138
+ ### Unexpected mount index
101
139
 
102
- Verify `layout()` calls wrap child routes correctly.
140
+ Each `.routes()` call (and the `urls` option) registers a separate mount (M0,
141
+ M1, ...), and each `include()` also takes a mount index. Routes registered by
142
+ separate `.routes()` calls do not share layouts or middleware; compose modules
143
+ with `include()` inside one `urls()` tree when they should (`/composability`).
103
144
 
104
- ### Wrong mount index
145
+ ### Missing flags
105
146
 
106
- Multiple `.routes()` calls create separate mounts (M0, M1, etc.). Use `include()` to share context.
147
+ `hasMiddleware` / `hasLoader` / `hasErrorBoundary` are per entry. Middleware or
148
+ a loader declared on a parent layout shows on that layout, not on each child
149
+ route.
@@ -32,7 +32,8 @@ hydration cost lives under the boundary (see "Verifying and measuring").
32
32
 
33
33
  ## The recipe
34
34
 
35
- ~40 lines, plain React, no rango imports — copy it into your app:
35
+ About 50 lines of plain React, no rango imports. Copy it into a client module in
36
+ your app:
36
37
 
37
38
  ```tsx
38
39
  "use client";
@@ -10,6 +10,9 @@ Start with one question: **does the request reach Rango before shared bytes are
10
10
  served?** That boundary decides whether middleware runs, whether loaders stay
11
11
  live, and which invalidation system owns the result.
12
12
 
13
+ This skill is about where each cache sits relative to your deployment platform.
14
+ To choose between the in-app layers themselves, start at `/cache-guide`.
15
+
13
16
  ## The execution matrix
14
17
 
15
18
  | Mechanism | Stored artifact | Function/worker runs on a hit? | Rango middleware on a hit? | What stays live? |
@@ -173,4 +176,6 @@ provably public and shared.
173
176
  - `/document-cache` — store-backed complete-response middleware
174
177
  - `/vercel` — Vercel Build Output preset and Runtime Cache wiring
175
178
  - `/cloudflare` — Worker deployment and bindings
176
- - `/cache-guide` — function, loader, and segment cache selection
179
+ - `/cache-guide` — choosing between the in-app cache layers
180
+ - `/caching` — `cache()` segments, stores, and tag invalidation
181
+ - `/use-cache` — function-level `"use cache"`