@rangojs/router 0.16.0 → 0.18.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 (309) hide show
  1. package/README.md +255 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/cookie-name.d.ts +6 -0
  4. package/dist/types/browser/history-state.d.ts +16 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  6. package/dist/types/browser/partial-update.d.ts +11 -0
  7. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  8. package/dist/types/browser/rsc-router.d.ts +1 -1
  9. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  10. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  11. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  12. package/dist/types/cache/cache-policy.d.ts +17 -0
  13. package/dist/types/cache/cache-scope.d.ts +132 -19
  14. package/dist/types/cache/cache-tag.d.ts +92 -7
  15. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  18. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  19. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  20. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  21. package/dist/types/cache/handle-capture.d.ts +41 -3
  22. package/dist/types/cache/handle-snapshot.d.ts +54 -13
  23. package/dist/types/cache/index.d.ts +1 -0
  24. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  25. package/dist/types/cache/segment-codec.d.ts +10 -3
  26. package/dist/types/cache/shell-memo.d.ts +178 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  28. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  29. package/dist/types/cache/taint.d.ts +1 -1
  30. package/dist/types/cache/types.d.ts +220 -44
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  32. package/dist/types/client.d.ts +4 -4
  33. package/dist/types/client.rsc.d.ts +4 -5
  34. package/dist/types/components/DefaultDocument.d.ts +5 -2
  35. package/dist/types/context-var.d.ts +6 -5
  36. package/dist/types/escape-script.d.ts +1 -1
  37. package/dist/types/handles/MetaTags.d.ts +5 -4
  38. package/dist/types/handles/Scripts.d.ts +8 -7
  39. package/dist/types/handles/meta.d.ts +2 -2
  40. package/dist/types/handles/script.d.ts +17 -16
  41. package/dist/types/href-client.d.ts +1 -1
  42. package/dist/types/html.d.ts +12 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +3 -2
  45. package/dist/types/loader-store.d.ts +7 -4
  46. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  47. package/dist/types/reverse.d.ts +36 -4
  48. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  49. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  50. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  51. package/dist/types/route-types.d.ts +1 -1
  52. package/dist/types/router/debug-manifest.d.ts +3 -2
  53. package/dist/types/router/error-handling.d.ts +20 -0
  54. package/dist/types/router/handler-context.d.ts +6 -0
  55. package/dist/types/router/instrument.d.ts +8 -0
  56. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  57. package/dist/types/router/middleware-types.d.ts +9 -3
  58. package/dist/types/router/router-context.d.ts +3 -1
  59. package/dist/types/router/router-interfaces.d.ts +24 -2
  60. package/dist/types/router/router-options.d.ts +8 -5
  61. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  62. package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
  63. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  64. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
  65. package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  67. package/dist/types/router/segment-wrappers.d.ts +1 -1
  68. package/dist/types/rsc/full-payload.d.ts +14 -0
  69. package/dist/types/rsc/handler.d.ts +1 -1
  70. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  71. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  72. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
  74. package/dist/types/rsc/shell-capture.d.ts +151 -19
  75. package/dist/types/rsc/shell-serve.d.ts +140 -17
  76. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  77. package/dist/types/rsc/types.d.ts +17 -0
  78. package/dist/types/server/context.d.ts +139 -15
  79. package/dist/types/server/cookie-store.d.ts +21 -0
  80. package/dist/types/server/handle-store.d.ts +26 -2
  81. package/dist/types/server/request-context.d.ts +182 -53
  82. package/dist/types/ssr/index.d.ts +24 -0
  83. package/dist/types/ssr/ssr-root.d.ts +9 -8
  84. package/dist/types/static-handler.d.ts +4 -1
  85. package/dist/types/testing/flight.entry.d.ts +7 -0
  86. package/dist/types/testing/generated-routes.d.ts +2 -2
  87. package/dist/types/testing/internal/context.d.ts +1 -1
  88. package/dist/types/testing/render-route.d.ts +32 -6
  89. package/dist/types/testing/run-loader.d.ts +18 -3
  90. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  91. package/dist/types/testing/shell-status.d.ts +23 -10
  92. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  93. package/dist/types/testing/vitest.d.ts +35 -1
  94. package/dist/types/theme/ThemeScript.d.ts +3 -3
  95. package/dist/types/types/cache-types.d.ts +14 -10
  96. package/dist/types/types/handler-context.d.ts +28 -1
  97. package/dist/types/types/loader-types.d.ts +8 -6
  98. package/dist/types/urls/pattern-types.d.ts +22 -18
  99. package/dist/types/urls/response-types.d.ts +3 -12
  100. package/dist/types/use-loader.d.ts +12 -3
  101. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  102. package/dist/types/vite/rango.d.ts +1 -1
  103. package/dist/types/vite/router-discovery.d.ts +29 -2
  104. package/dist/vite/index.js +1420 -1348
  105. package/package.json +2 -2
  106. package/skills/api-client/SKILL.md +22 -10
  107. package/skills/breadcrumbs/SKILL.md +75 -22
  108. package/skills/bundle-analysis/SKILL.md +22 -22
  109. package/skills/cache-guide/SKILL.md +306 -124
  110. package/skills/caching/SKILL.md +530 -98
  111. package/skills/catalog.json +16 -16
  112. package/skills/client-urls/SKILL.md +65 -38
  113. package/skills/cloudflare/SKILL.md +87 -20
  114. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  115. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  116. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  117. package/skills/comparison/SKILL.md +5 -0
  118. package/skills/comparison/references/framework-comparison.md +75 -57
  119. package/skills/composability/SKILL.md +38 -29
  120. package/skills/css/SKILL.md +10 -13
  121. package/skills/debug-manifest/SKILL.md +74 -31
  122. package/skills/defer-hydration/SKILL.md +52 -31
  123. package/skills/deployment-caching/SKILL.md +11 -3
  124. package/skills/document-cache/SKILL.md +98 -28
  125. package/skills/fonts/SKILL.md +16 -8
  126. package/skills/handler-use/SKILL.md +55 -17
  127. package/skills/hooks/SKILL.md +28 -20
  128. package/skills/hooks/data.md +23 -3
  129. package/skills/hooks/handle-and-actions.md +34 -17
  130. package/skills/hooks/navigation.md +39 -10
  131. package/skills/hooks/outlets.md +13 -6
  132. package/skills/hooks/state.md +39 -15
  133. package/skills/hooks/urls.md +7 -5
  134. package/skills/host-router/SKILL.md +64 -12
  135. package/skills/i18n/SKILL.md +60 -34
  136. package/skills/intercept/SKILL.md +161 -26
  137. package/skills/layout/SKILL.md +106 -50
  138. package/skills/links/SKILL.md +51 -19
  139. package/skills/loader/SKILL.md +386 -128
  140. package/skills/middleware/SKILL.md +129 -26
  141. package/skills/migrate-nextjs/SKILL.md +156 -70
  142. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  143. package/skills/migrate-react-router/SKILL.md +51 -39
  144. package/skills/migrate-react-router/component-migration.md +28 -7
  145. package/skills/migrate-react-router/data-and-actions.md +21 -6
  146. package/skills/migrate-react-router/route-mapping.md +19 -10
  147. package/skills/mime-routes/SKILL.md +12 -7
  148. package/skills/observability/SKILL.md +92 -14
  149. package/skills/parallel/SKILL.md +88 -47
  150. package/skills/ppr/SKILL.md +866 -379
  151. package/skills/prerender/SKILL.md +210 -108
  152. package/skills/rango/SKILL.md +115 -65
  153. package/skills/react-compiler/SKILL.md +6 -5
  154. package/skills/response-routes/SKILL.md +133 -25
  155. package/skills/route/SKILL.md +170 -60
  156. package/skills/router-setup/SKILL.md +420 -240
  157. package/skills/scripts/SKILL.md +39 -21
  158. package/skills/server-actions/SKILL.md +231 -59
  159. package/skills/shell-manifest/SKILL.md +28 -14
  160. package/skills/streams-and-websockets/SKILL.md +32 -14
  161. package/skills/tailwind/SKILL.md +37 -33
  162. package/skills/testing/SKILL.md +73 -33
  163. package/skills/testing/cache-prerender.md +60 -33
  164. package/skills/testing/client-components.md +59 -6
  165. package/skills/testing/e2e-parity.md +12 -2
  166. package/skills/testing/flight.md +19 -11
  167. package/skills/testing/handles.md +8 -5
  168. package/skills/testing/loader.md +26 -6
  169. package/skills/testing/middleware.md +8 -3
  170. package/skills/testing/predicates.md +120 -0
  171. package/skills/testing/render-handler.md +70 -22
  172. package/skills/testing/response-routes.md +11 -4
  173. package/skills/testing/reverse-and-types.md +20 -14
  174. package/skills/testing/serve-shell-request.md +188 -0
  175. package/skills/testing/server-actions.md +12 -3
  176. package/skills/testing/server-tree.md +9 -0
  177. package/skills/testing/setup.md +9 -5
  178. package/skills/theme/SKILL.md +155 -30
  179. package/skills/typesafety/SKILL.md +20 -9
  180. package/skills/typesafety/env-and-bindings.md +137 -50
  181. package/skills/typesafety/generated-files-and-cli.md +26 -14
  182. package/skills/typesafety/params-and-search.md +44 -19
  183. package/skills/typesafety/route-types.md +50 -14
  184. package/skills/use-cache/SKILL.md +224 -83
  185. package/skills/vercel/SKILL.md +35 -13
  186. package/skills/view-transitions/SKILL.md +42 -21
  187. package/src/__augment-tests__/augmented.check.ts +26 -1
  188. package/src/browser/cookie-name.ts +23 -0
  189. package/src/browser/history-state.ts +23 -0
  190. package/src/browser/index.ts +2 -2
  191. package/src/browser/navigation-bridge.ts +3 -4
  192. package/src/browser/navigation-transaction.ts +22 -2
  193. package/src/browser/partial-update.ts +35 -25
  194. package/src/browser/react/ScrollRestoration.tsx +6 -6
  195. package/src/browser/rsc-router.tsx +6 -1
  196. package/src/browser/scroll-restoration.ts +53 -6
  197. package/src/cache/cache-exec-scope.ts +37 -4
  198. package/src/cache/cache-key-utils.ts +23 -8
  199. package/src/cache/cache-policy.ts +22 -0
  200. package/src/cache/cache-runtime.ts +264 -68
  201. package/src/cache/cache-scope.ts +419 -43
  202. package/src/cache/cache-tag.ts +375 -11
  203. package/src/cache/cf/cf-base64.ts +36 -4
  204. package/src/cache/cf/cf-cache-constants.ts +12 -0
  205. package/src/cache/cf/cf-cache-store.ts +990 -348
  206. package/src/cache/cf/cf-cache-types.ts +32 -4
  207. package/src/cache/cf/cf-shell-frame.ts +258 -0
  208. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  209. package/src/cache/document-cache.ts +15 -0
  210. package/src/cache/handle-capture.ts +90 -10
  211. package/src/cache/handle-snapshot.ts +133 -28
  212. package/src/cache/index.ts +2 -0
  213. package/src/cache/isolate-tag-memo.ts +308 -0
  214. package/src/cache/segment-codec.ts +26 -7
  215. package/src/cache/shell-memo.ts +341 -0
  216. package/src/cache/shell-snapshot.ts +217 -27
  217. package/src/cache/tag-invalidation.ts +108 -22
  218. package/src/cache/taint.ts +1 -1
  219. package/src/cache/types.ts +225 -44
  220. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  221. package/src/client.rsc.tsx +10 -9
  222. package/src/client.tsx +5 -4
  223. package/src/components/DefaultDocument.tsx +7 -2
  224. package/src/context-var.ts +6 -5
  225. package/src/escape-script.ts +1 -1
  226. package/src/handles/MetaTags.tsx +13 -11
  227. package/src/handles/Scripts.tsx +11 -10
  228. package/src/handles/meta.ts +2 -2
  229. package/src/handles/script.ts +17 -16
  230. package/src/href-client.ts +1 -1
  231. package/src/html.ts +12 -0
  232. package/src/index.rsc.ts +5 -0
  233. package/src/index.ts +1 -0
  234. package/src/loader-store.ts +7 -4
  235. package/src/prerender/build-shell-capture.ts +73 -22
  236. package/src/reverse.ts +44 -4
  237. package/src/route-definition/dsl-helpers.ts +121 -33
  238. package/src/route-definition/helpers-types.ts +33 -9
  239. package/src/route-definition/resolve-handler-use.ts +121 -6
  240. package/src/route-types.ts +0 -3
  241. package/src/router/debug-manifest.ts +10 -2
  242. package/src/router/error-handling.ts +51 -15
  243. package/src/router/handler-context.ts +71 -28
  244. package/src/router/instrument.ts +54 -2
  245. package/src/router/intercept-resolution.ts +141 -43
  246. package/src/router/loader-resolution.ts +115 -39
  247. package/src/router/match-api.ts +40 -26
  248. package/src/router/match-handlers.ts +5 -0
  249. package/src/router/match-middleware/background-revalidation.ts +161 -96
  250. package/src/router/match-middleware/cache-lookup.ts +110 -10
  251. package/src/router/match-middleware/cache-store.ts +48 -101
  252. package/src/router/middleware-types.ts +9 -6
  253. package/src/router/middleware.ts +75 -13
  254. package/src/router/prerender-match.ts +59 -19
  255. package/src/router/route-snapshot.ts +11 -1
  256. package/src/router/router-context.ts +2 -0
  257. package/src/router/router-interfaces.ts +25 -2
  258. package/src/router/router-options.ts +8 -5
  259. package/src/router/segment-resolution/fresh.ts +62 -21
  260. package/src/router/segment-resolution/helpers.ts +4 -2
  261. package/src/router/segment-resolution/loader-cache.ts +407 -76
  262. package/src/router/segment-resolution/loader-mask.ts +12 -22
  263. package/src/router/segment-resolution/loader-snapshot.ts +208 -56
  264. package/src/router/segment-resolution/mask-nested.ts +138 -44
  265. package/src/router/segment-resolution/revalidation.ts +61 -16
  266. package/src/router/segment-wrappers.ts +4 -0
  267. package/src/rsc/full-payload.ts +25 -1
  268. package/src/rsc/handler.ts +14 -2
  269. package/src/rsc/progressive-enhancement.ts +3 -2
  270. package/src/rsc/render-pipeline.ts +3 -0
  271. package/src/rsc/response-cache-serve.ts +9 -6
  272. package/src/rsc/response-route-handler.ts +11 -3
  273. package/src/rsc/rsc-rendering.ts +608 -225
  274. package/src/rsc/shell-build-manifest.ts +27 -9
  275. package/src/rsc/shell-capture-constants.ts +73 -6
  276. package/src/rsc/shell-capture.ts +1068 -296
  277. package/src/rsc/shell-serve.ts +316 -34
  278. package/src/rsc/ssr-setup.ts +14 -8
  279. package/src/rsc/types.ts +18 -0
  280. package/src/server/context.ts +300 -32
  281. package/src/server/cookie-store.ts +87 -61
  282. package/src/server/handle-store.ts +232 -7
  283. package/src/server/request-context.ts +297 -67
  284. package/src/ssr/index.tsx +136 -44
  285. package/src/ssr/ssr-root.tsx +9 -8
  286. package/src/static-handler.ts +4 -1
  287. package/src/testing/flight-runtime.d.ts +21 -0
  288. package/src/testing/flight.entry.ts +14 -0
  289. package/src/testing/generated-routes.ts +2 -2
  290. package/src/testing/internal/context.ts +1 -1
  291. package/src/testing/internal/flight-client-globals.ts +7 -5
  292. package/src/testing/render-route.tsx +85 -24
  293. package/src/testing/run-loader.ts +68 -6
  294. package/src/testing/serve-shell-request.ts +378 -0
  295. package/src/testing/shell-status.ts +34 -12
  296. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  297. package/src/testing/vitest.ts +62 -1
  298. package/src/theme/ThemeProvider.tsx +7 -6
  299. package/src/theme/ThemeScript.tsx +3 -3
  300. package/src/types/cache-types.ts +14 -10
  301. package/src/types/handler-context.ts +28 -1
  302. package/src/types/loader-types.ts +8 -6
  303. package/src/urls/pattern-types.ts +22 -18
  304. package/src/urls/response-types.ts +3 -23
  305. package/src/use-loader.tsx +12 -3
  306. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  307. package/src/vite/plugins/use-cache-transform.ts +118 -74
  308. package/src/vite/rango.ts +7 -3
  309. package/src/vite/router-discovery.ts +14 -4
@@ -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,19 @@ 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`) plus a real PPR shell capture and HIT
377
+ (`serveShellRequest`, `resetShellTestState`) from `./testing/flight`,
378
+ `renderRoute` from `./testing/dom`, a vitest preset from `./testing/vitest`, and
379
+ a Playwright e2e harness with dev/prod parity helpers (`parityDescribe`,
380
+ `expectParity`) from `./testing/e2e`. You can unit-test a loader, a middleware,
381
+ an action, or an RSC handler in isolation. Next.js and Waku ship no official
382
+ primitives for testing server components/handlers; TanStack documents testing
383
+ patterns (build your own harness from `createRouter`/`createMemoryHistory`) but
384
+ ships no testing package, and nothing at the RSC-handler level. See the
385
+ [testing skill](../../testing/SKILL.md) and
386
+ [testing.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/testing.md).
379
387
 
380
388
  ### Bundle discipline
381
389
 
@@ -386,7 +394,7 @@ the build if React's development bundle leaks into production. Treat those as
386
394
  Rango baselines, not a normalized cross-framework benchmark: application shape,
387
395
  React version, compiler output, and deployment transforms make headline bundle
388
396
  comparisons unreliable. Waku deliberately targets a smaller surface. See
389
- [client-chunking.md](../../../docs/client-chunking.md).
397
+ [client-chunking.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/client-chunking.md).
390
398
 
391
399
  ## Runtime mechanics in depth
392
400
 
@@ -495,7 +503,7 @@ layout(<ShopLayout />, () => [
495
503
  A click from the list renders `ProductQuickView` into `@modal` and preserves the
496
504
  list behind it. A direct visit or reload renders the canonical full page. Back
497
505
  closes the modal and restores the preserved background. The intercept can own its
498
- middleware, layout, loaders, loading/error policy, loader-level caching, and
506
+ middleware, layout, loaders, loading state, and loader-level caching and
499
507
  `revalidate()` rules; the `when` config selector (the 4th argument to
500
508
  `intercept()`) can choose by navigation source.
501
509
 
@@ -521,17 +529,20 @@ There are two distinct things named "revalidate", and the split is deliberate:
521
529
  downstream revalidators in the same decision chain. This is surgical
522
530
  post-action control: "this widget re-renders only when `addToCart` ran; the
523
531
  rest of the tree stays put."
524
- See [is-action-api-design.md](../../../docs/design/is-action-api-design.md).
532
+ See [is-action-api-design.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/design/is-action-api-design.md).
525
533
  2. **`revalidate: false` on `<Link>` / `navigate()`** — shallow navigation. When
526
534
  the pathname is unchanged (a search or hash change), it updates the URL and all
527
535
  location hooks but skips the server fetch and re-render entirely. For filters,
528
536
  tabs, and pagination. See
529
- [shallow-navigation.md](../../../docs/design/shallow-navigation.md).
530
-
531
- Both are separate from `revalidateTag()`/`updateTag()`, which hard-purge tagged
532
- cache entries. `updateTag()` is awaitable for read-your-own-writes;
533
- `revalidateTag()` schedules the same invalidation in the background. Neither API
534
- selects a client segment to render. Next.js offers `router.refresh()` (and
537
+ [shallow-navigation.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/design/shallow-navigation.md).
538
+
539
+ Both are separate from `revalidateTag()`/`updateTag()`, which evict tagged
540
+ cache entries; the request that calls either reads its own writes.
541
+ `updateTag()` also awaits the durable write; `revalidateTag()` schedules it in
542
+ the background. The mutating user's next requests skip the stores' per-isolate
543
+ PPR shell memos via a fresh-reads cookie, and other users converge within the
544
+ memo window and the platform's own propagation. Neither API selects a client
545
+ segment to render. Next.js offers `router.refresh()` (and
535
546
  `refresh()` from `next/cache`) plus path/tag cache invalidation, but no per-segment
536
547
  render predicate and no typed action discrimination — and its invalidations clear
537
548
  the whole client cache rather than selecting a segment. TanStack's `shouldReload`/`router.invalidate()` is the nearest
@@ -614,18 +625,22 @@ this resource-aware policy layer.
614
625
 
615
626
  `createLoader(fn)` / `loader()` define **live-by-default** data units: they are
616
627
  excluded from an enclosing segment cache and resolve on every request unless the
617
- loader itself explicitly opts into `cache()`. They may safely read `cookies()`,
618
- `headers()`, request context, and `env` because loader execution is outside the
619
- cached shell. Loaders run in parallel, stream independently under `loading()`
620
- boundaries, compose server-side via `ctx.use(OtherLoader)`, and carry
621
- route-level authority: they can throw `notFound()`/`redirect()`, WRITE handles
622
- (`ctx.use(Meta)({ title })` — data-derived page metadata pushed from the data's
623
- producer), read handle data after the render settles (`ctx.get(handle)` behind
624
- `await ctx.rendered()`), and opt into `loader(Def, { ssr: false })`
625
- so a document render awaits them before first flush (deterministic SSR'd data,
626
- meta, and 404 status). Reads happen through `useLoader` in a client component
627
- (including its SSR pass) or `useFetchLoader` for standalone client fetches. "Fetchable" loaders are callable
628
- endpoints with their own middleware and GET/POST/PUT/PATCH/DELETE bodies.
628
+ loader itself explicitly opts into `cache()`. A loader without `ssr: false` may
629
+ safely read `cookies()`, `headers()`, request context, and `env` because its
630
+ execution is outside the cached shell; a loader with its own `cache()` must put
631
+ those reads in its `key()`, or its miss fails and stores nothing. On a `ppr`
632
+ route an `ssr: false` loader bakes into the shell, so an identity read there
633
+ refuses the capture. Loaders run in parallel, stream independently under
634
+ `loading()` boundaries, compose server-side via `ctx.use(OtherLoader)`, and
635
+ carry route-level authority: they can throw `notFound()`/`redirect()`, WRITE
636
+ handles (`ctx.use(Meta)({ title })` — data-derived page metadata pushed from the
637
+ data's producer), read handle data after the render settles (`ctx.get(handle)`
638
+ behind `await ctx.rendered()`), and opt into `loader(Def, { ssr: false })` so a
639
+ document render awaits them before first flush (deterministic SSR'd data, meta,
640
+ and 404 status). Reads happen through `useLoader` in a client component
641
+ (including its SSR pass) or `useFetchLoader` for standalone client fetches.
642
+ "Fetchable" loaders are callable endpoints with their own middleware and
643
+ GET/POST/PUT/PATCH/DELETE bodies.
629
644
 
630
645
  One sharp edge is worth stating because the distinction matters: a cached handler
631
646
  can call `await ctx.use(Loader)`, but if it renders that result inline, it bakes the
@@ -675,7 +690,7 @@ a fully prerendered route. Caching loader data is a separate, explicit opt-in on
675
690
  that loader.
676
691
 
677
692
  The mechanism is concrete: a prerendered route serves its baked Flight shell from
678
- the store, then `resolveLoadersOnly()` resolves the loaders through their own
693
+ the store, then the router resolves the loaders through their own
679
694
  freshness policy at request time and merges their segments into that replayed
680
695
  shell. With the default policy you get a cached/prerendered shell plus live data,
681
696
  automatically, with no per-route static-vs-dynamic decision. The loader is the
@@ -686,7 +701,8 @@ designated live data slot in a cache-first RSC tree.** Several properties follow
686
701
  that no loader-before-render has:
687
702
 
688
703
  - it is the **cache-safety escape hatch** — the only place request-coupled reads
689
- are allowed inside a cache scope, because it is the part guaranteed to re-run;
704
+ are allowed inside a cache scope, because it is the part guaranteed to re-run
705
+ (a loader without `ssr: false` and without its own unkeyed `cache()`);
690
706
  - it is **standalone and composable**, not route-coupled — one `createLoader()`
691
707
  read by many segments, composed via `ctx.use`, or exposed as a fetchable endpoint;
692
708
  - it **streams as a hole, not a gate** — concurrent, Suspense-resolved under
@@ -767,16 +783,18 @@ safe rule remains simple: read request-specific context at the live point of use
767
783
  normally a loader, rather than deriving it outside and carrying it into a cached
768
784
  shell.
769
785
 
770
- This is where the taint and loader designs meet. Loaders are the sanctioned dynamic
771
- holes: they run outside the enclosing segment cache and may read the current
772
- request, while the shared shell remains protected. Next.js also isolates runtime
773
- APIs from ordinary `"use cache"`, and React's optional taint APIs protect values at
774
- the server-to-client serialization boundary. Those are useful but different
786
+ This is where the taint and loader designs meet. Loaders are the sanctioned
787
+ dynamic holes: they run outside the enclosing segment cache and may read the
788
+ current request (except, on a `ppr` route, an `ssr: false` loader or a loader a
789
+ handler awaits, which the capture runs and refuses on such a read), while the
790
+ shared shell remains protected. Next.js also isolates runtime APIs from ordinary
791
+ `"use cache"`, and React's optional taint APIs protect values at the
792
+ server-to-client serialization boundary. Those are useful but different
775
793
  contracts; they do not provide Rango's route-aware tainted-argument keying,
776
794
  non-cacheable typed context variables, handle capture/replay, and loader escape
777
795
  path as one system. TanStack Start and Waku leave this boundary primarily to
778
796
  application architecture. See the [cache guide](../../cache-guide/SKILL.md) and
779
- [`"use cache"` design](../../../docs/use-cache-api-design.md).
797
+ [`"use cache"` design](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/use-cache-api-design.md).
780
798
 
781
799
  ## Performance diagnostics (`debugPerformance`)
782
800
 
@@ -809,7 +827,7 @@ Next.js has no equivalent flip-a-boolean per-request phase waterfall: its automa
809
827
  OTel spans still need an exporter and trace viewer. TanStack's strength is
810
828
  client/data devtools, a different axis from server request-phase diagnostics. Waku
811
829
  does not ship an equivalent phase model. See
812
- [telemetry.md](../../../docs/telemetry.md).
830
+ [telemetry.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/telemetry.md).
813
831
 
814
832
  ## Coming from a specific framework
815
833
 
@@ -819,7 +837,7 @@ layers for a code-defined, refactor-safe router and one caching model. You gain
819
837
  Cloudflare as a first-class target and phase spans for free. You give up the
820
838
  largest ecosystem and talent pool in the React world — the real cost.
821
839
 
822
- **From TanStack Start.** You get comparable type-safe routing (`reverse()`, typed
840
+ **From TanStack Start.** You get comparable type-safe routing (`ctx.reverse()`/`href()`, typed
823
841
  params/search, even typed response MIME via `.json()`/`.text()`/`.image()`), plus a
824
842
  true RSC-first model (zero-bundle server components, Flight) that TanStack's
825
843
  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.