@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
@@ -27,6 +27,7 @@ export interface AppVariables {
27
27
 
28
28
  ```typescript
29
29
  // router.tsx
30
+ import { createRouter } from "@rangojs/router";
30
31
  import type { AppBindings, AppVariables } from "./env";
31
32
 
32
33
  const router = createRouter<AppBindings>({
@@ -40,7 +41,9 @@ declare global {
40
41
  interface Vars extends AppVariables {}
41
42
  }
42
43
  }
44
+ ```
43
45
 
46
+ ```typescript
44
47
  // middleware - typed via ctx.set / ctx.get
45
48
  import type { Middleware } from "@rangojs/router";
46
49
 
@@ -52,38 +55,35 @@ export const authMiddleware: Middleware = async (ctx, next) => {
52
55
  });
53
56
  await next();
54
57
  };
55
-
56
- // loaders - typed context
57
- export const UserLoader = createLoader(async (ctx) => {
58
- const db = ctx.env.DB; // D1Database (plain bindings)
59
- const userId = ctx.get("user")?.id; // from Rango.Vars
60
- return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
61
- });
62
58
  ```
63
59
 
64
60
  ## Global Environment Registration
65
61
 
66
- Register environment types globally for implicit typing:
62
+ The `declare global` block above is what types the contexts. `createRouter<TEnv>()`
63
+ types the router instance, but `urls()` modules, `Handler`, middleware, and
64
+ response routes read `Rango.Env` / `Rango.Vars` — they cannot see the router's
65
+ generic. Put the block in a file that is part of every TypeScript program that
66
+ typechecks handlers (usually `router.tsx`, kept in via tsconfig `files`).
67
67
 
68
- ```typescript
69
- // router.tsx
70
- declare global {
71
- namespace Rango {
72
- interface Env extends AppBindings {}
73
- interface Vars extends AppVariables {}
74
- }
75
- }
76
- ```
68
+ What each context sees:
77
69
 
78
- Now handlers have typed context without explicit imports:
70
+ | Context | `ctx.env` | `ctx.get("key")` |
71
+ | ------------------------------------- | ----------------------------------------- | -------------------------------------- |
72
+ | Handlers, middleware, response routes | `Rango.Env` (`unknown` if not registered) | `Rango.Vars` (`any` if not registered) |
73
+ | Loaders (`createLoader`) | `any` (not typed from `Rango.Env`) | `Rango.Vars` (`any` if not registered) |
74
+
75
+ An unregistered `Rango.Env` is deliberately `unknown`, so `ctx.env.DB` is a
76
+ compile error rather than a silent `any`. An unregistered `Rango.Vars` falls
77
+ back to `any` so string-key vars work with zero config.
79
78
 
80
79
  ```typescript
81
80
  // In loaders
81
+ import { createLoader } from "@rangojs/router";
82
+
82
83
  export const DashboardLoader = createLoader(async (ctx) => {
83
- // ctx.env.DB is typed from global Rango.Env
84
- // ctx.get("user") is typed from global Rango.Vars
85
- const user = ctx.get("user");
86
- return { user };
84
+ const user = ctx.get("user"); // typed from global Rango.Vars
85
+ const db = ctx.env.DB as D1Database; // ctx.env is `any` in loaders
86
+ return { user, count: await countOrders(db, user?.id) };
87
87
  });
88
88
  ```
89
89
 
@@ -103,8 +103,12 @@ interface PaginationData {
103
103
  }
104
104
  export const Pagination = createVar<PaginationData>();
105
105
 
106
- // Non-cacheable var — reading inside cache() or "use cache" throws at runtime
107
- const Session = createVar<SessionData>({ cache: false });
106
+ // Non-cacheable var — reading inside cache(), "use cache", or a PPR shell
107
+ // capture throws at runtime
108
+ interface SessionData {
109
+ userId: string;
110
+ }
111
+ export const Session = createVar<SessionData>({ cache: false });
108
112
  ```
109
113
 
110
114
  `createVar` accepts an optional options object. The `cache` option (default
@@ -112,11 +116,25 @@ const Session = createVar<SessionData>({ cache: false });
112
116
  Write-level escalation is also supported: `ctx.set(Var, value, { cache: false })`
113
117
  marks a specific write as non-cacheable even if the var itself is cacheable.
114
118
  "Least cacheable wins" — if either says `cache: false`, the value throws on
115
- read inside `cache()` or `"use cache"`.
116
-
117
- ### Producer (handler or middleware)
119
+ read inside `cache()` or `"use cache"`. On a `ppr` route the read also throws
120
+ anywhere the shell capture waits for it (a handler, a promise it passes or
121
+ pushes, an async server component, an `ssr: false` loader, a loader a handler
122
+ awaits) and refuses the capture: the route keeps rendering without a shell,
123
+ and a warning fires once per key. A loader without `ssr: false` that no
124
+ handler awaits may read it, unless it is bound with its own `cache()` and no
125
+ `key()`: that miss fails, since the entry is shared (`/loader` → "Cache
126
+ Key").
127
+
128
+ ### Producer (handler, layout, or middleware)
129
+
130
+ Write with `ctx.set()` from middleware, route handlers, or layout handlers;
131
+ read with `ctx.get()` in the segments they wrap. A route handler runs before
132
+ the layouts and parallels declared in its own use callback (handler-first), so
133
+ those see the value; an outer layout wrapping the route renders first and
134
+ does not. See `/route` for the full ordering.
118
135
 
119
136
  ```typescript
137
+ import type { Handler } from "@rangojs/router";
120
138
  import { Pagination } from "../vars/pagination.js";
121
139
 
122
140
  const ArticleList: Handler<"articles.list"> = async (ctx) => {
@@ -132,15 +150,30 @@ const ArticleList: Handler<"articles.list"> = async (ctx) => {
132
150
  ### Consumer (layout, parallel, or any context with get)
133
151
 
134
152
  ```typescript
153
+ import type { Handler } from "@rangojs/router";
154
+ import { Outlet } from "@rangojs/router/client";
135
155
  import { Pagination } from "../vars/pagination.js";
136
156
 
137
- export function PaginationLayout(ctx: any) {
138
- const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
139
- if (!pagination) return <Outlet />;
140
- return <nav>Page {pagination.current} of {pagination.total}</nav>;
141
- }
157
+ // Mounted inside the producer's use callback:
158
+ // path("/articles", ArticleList, { name: "articles.list" }, () => [layout(PaginationLayout)])
159
+ export const PaginationLayout: Handler = (ctx) => {
160
+ const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
161
+ return (
162
+ <>
163
+ <Outlet />
164
+ {pagination && (
165
+ <nav>
166
+ Page {pagination.current} of {pagination.total}
167
+ </nav>
168
+ )}
169
+ </>
170
+ );
171
+ };
142
172
  ```
143
173
 
174
+ Type the layout (`Handler`) rather than annotating `ctx: any`: with `any`,
175
+ `ctx.get(Pagination)` is `any` too and the token contract is lost.
176
+
144
177
  ### Why not just use Rango.Vars?
145
178
 
146
179
  `Rango.Vars` (via global namespace augmentation) provides app-global typing for
@@ -154,7 +187,9 @@ Both approaches coexist: `ctx.get("user")` (global via Vars) and
154
187
 
155
188
  ## Handle Type Safety
156
189
 
157
- Handles have typed data:
190
+ A handle is typed by two parameters: `Handle<TData, TAccumulated>`. `TData` is
191
+ what each segment pushes; `TAccumulated` is what readers get after the collect
192
+ function runs (default `TData[][]`, one array per segment).
158
193
 
159
194
  ```typescript
160
195
  // Built-in Breadcrumbs handle — import from "@rangojs/router"
@@ -168,18 +203,33 @@ path("/shop/product/:slug", (ctx) => {
168
203
  breadcrumb({ label: "Products", href: "/shop/products" });
169
204
  return <ProductPage />;
170
205
  }, { name: "product" });
206
+ ```
171
207
 
208
+ ```tsx
172
209
  // In client — typed array
210
+ "use client";
173
211
  import { useHandle, Breadcrumbs } from "@rangojs/router/client";
212
+
174
213
  function BreadcrumbNav() {
175
- const crumbs = useHandle(Breadcrumbs);
176
- // crumbs: BreadcrumbItem[]
214
+ const crumbs = useHandle(Breadcrumbs); // crumbs: BreadcrumbItem[]
215
+ return (
216
+ <nav>
217
+ {crumbs.map((c) => (
218
+ <a key={c.href} href={c.href}>
219
+ {c.label}
220
+ </a>
221
+ ))}
222
+ </nav>
223
+ );
177
224
  }
225
+ ```
178
226
 
179
- // Custom handles also work the same way
227
+ ```typescript
228
+ // Custom handle: give TAccumulated whenever collect changes the shape
180
229
  import { createHandle } from "@rangojs/router";
230
+
181
231
  export const PageTitle = createHandle<string, string>(
182
- (segments) => segments.flat().at(-1) ?? "Default Title"
232
+ (segments) => segments.flat().at(-1) ?? "Default Title",
183
233
  );
184
234
  ```
185
235
 
@@ -190,11 +240,16 @@ Use `typeof` to get the full typed definition without manually specifying generi
190
240
 
191
241
  ```typescript
192
242
  // loaders.ts
243
+ import { createLoader } from "@rangojs/router";
244
+
193
245
  export const ProductLoader = createLoader(async (ctx) => {
194
246
  return { product: await fetchProduct(ctx.params.slug) };
195
247
  });
196
248
 
197
- // Built-in Breadcrumbs — or any custom handle created with createHandle()
249
+ // Server: pass the definitions as props
250
+ path("/product/:slug", () => (
251
+ <MyComponent loader={ProductLoader} handle={Breadcrumbs} />
252
+ ), { name: "product" }, () => [loader(ProductLoader)]);
198
253
  ```
199
254
 
200
255
  ```tsx
@@ -208,20 +263,27 @@ function MyComponent({
208
263
  handle,
209
264
  }: {
210
265
  loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
211
- handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
266
+ handle: typeof Breadcrumbs; // Handle<BreadcrumbItem, BreadcrumbItem[]>
212
267
  }) {
213
268
  const { data } = useLoader(loader); // data is typed
214
- const crumbs = useHandle(handle); // crumbs is typed array
269
+ const crumbs = useHandle(handle); // crumbs: BreadcrumbItem[]
215
270
  // ...
216
271
  }
217
272
  ```
218
273
 
219
- RSC Flight serialization calls `toJSON()` on both loaders and handles,
220
- sending only `{ __brand, $$id }` to the client. The hooks recover the
221
- full functionality from module-level registries.
274
+ Loader and handle definitions are plain `{ __brand, $$id }` objects — the
275
+ loader function and the handle's collect function live in module-level
276
+ registries keyed by `$$id` — so they cross the RSC boundary as-is and the
277
+ hooks look the rest up by id. `useLoader()` still needs the loader registered
278
+ on the route with `loader()` (or use `useFetchLoader()` for on-demand fetching).
222
279
 
223
280
  ## Location State Type Safety
224
281
 
282
+ `createLocationState<T>()` returns a typed definition; the Vite plugin injects
283
+ its key, so it must be exported from a module. Values are stored in
284
+ `history.state`, so `T` must be structured-cloneable: functions, symbols,
285
+ class constructors, React elements, and `unknown` are compile errors.
286
+
225
287
  ```typescript
226
288
  // location-states.ts
227
289
  import { createLocationState } from "@rangojs/router";
@@ -233,13 +295,28 @@ export const ProductPreview = createLocationState<{
233
295
  image: string;
234
296
  }>();
235
297
 
298
+ // Flash state: cleared after the first read
299
+ export const FlashMessage = createLocationState<{ text: string }>({
300
+ flash: true,
301
+ });
302
+ ```
303
+
304
+ ```tsx
305
+ "use client";
306
+ import { Link, href, useLocationState } from "@rangojs/router/client";
307
+ import { ProductPreview } from "./location-states";
308
+
236
309
  // Passing state through Link
237
- <Link
238
- to={href("product", { slug: "widget" })}
239
- state={[ProductPreview({ name: "Widget", price: 99, image: "/img.jpg" })]}
240
- >
241
- View Product
242
- </Link>
310
+ function ProductCard() {
311
+ return (
312
+ <Link
313
+ to={href("/product/widget")}
314
+ state={[ProductPreview({ name: "Widget", price: 99, image: "/img.jpg" })]}
315
+ >
316
+ View Product
317
+ </Link>
318
+ );
319
+ }
243
320
 
244
321
  // Reading state in component
245
322
  function ProductHeader() {
@@ -247,8 +324,18 @@ function ProductHeader() {
247
324
  // preview: { name: string; price: number; image: string } | undefined
248
325
 
249
326
  if (preview) {
250
- return <h1>{preview.name} - ${preview.price}</h1>;
327
+ return (
328
+ <h1>
329
+ {preview.name} - ${preview.price}
330
+ </h1>
331
+ );
251
332
  }
252
333
  return <h1>Loading...</h1>;
253
334
  }
254
335
  ```
336
+
337
+ Other entry points: pass a getter (`ProductPreview(() => ({ ... }))`) to compute
338
+ the value at click time; `ctx.setLocationState(entry)` attaches state from a
339
+ handler; `redirect(url, { state })` carries it through a redirect; and
340
+ `ProductPreview.read()` / `.write(value)` / `.delete()` access the current
341
+ history entry outside React (client-only, non-reactive).
@@ -5,23 +5,23 @@
5
5
  ```typescript
6
6
  // router.tsx
7
7
  import { createRouter } from "@rangojs/router";
8
+ import { Document } from "./document";
8
9
  import { urlpatterns } from "./urls";
10
+ import type { AppBindings } from "./env";
9
11
 
10
- const router = createRouter<AppBindings>({
12
+ export const router = createRouter<AppBindings>({
11
13
  document: Document,
12
14
  }).routes(urlpatterns);
13
15
 
14
16
  // Server-side named-route reverse (type-safe via routeMap)
15
17
  export const reverse = router.reverse;
16
-
17
- export default router;
18
18
  ```
19
19
 
20
20
  ### Which global type should I use?
21
21
 
22
22
  Use the generated route map by default. Manual `RegisteredRoutes` augmentation
23
23
  is only needed when you want the richer `typeof router.routeMap` shape
24
- available globally.
24
+ available globally — in practice, for `Rango.PathResponse`.
25
25
 
26
26
  - `GeneratedRouteMap` — auto-registered by `router.named-routes.gen.ts`
27
27
  Use for `Handler<"name">` (type annotation), `Prerender<"name">(...)` (function
@@ -95,19 +95,27 @@ instance in the same TypeScript program:
95
95
  }
96
96
  ```
97
97
 
98
- Then generate the route types from the router file:
98
+ The Vite plugin writes `src/router.named-routes.gen.ts` (next to the router
99
+ file) automatically: before the dev server starts, again when route files
100
+ change, and during build. To produce it without starting Vite (a fresh clone,
101
+ a CI typecheck step), run the CLI against the router file:
99
102
 
100
103
  ```bash
101
104
  npx rango generate src/router.tsx
102
105
  ```
103
106
 
104
- This creates `src/router.named-routes.gen.ts`, which augments
105
- `Rango.GeneratedRouteMap`. Keep that generated file committed with the router
107
+ The file augments `Rango.GeneratedRouteMap`. Keep it committed with the router
106
108
  source. The `files` entry keeps `router.tsx` in the program even when nothing
107
109
  imports it directly, so `Rango.Env`, `Rango.Vars`, and optional
108
110
  `Rango.RegisteredRoutes` augmentation are visible to handlers, loaders, actions,
109
111
  and client helpers.
110
112
 
113
+ CLI flags: the default mode fails (and writes nothing) when an `include()` cannot
114
+ be resolved statically (factory functions, dynamic expressions); `--static`
115
+ accepts partial output with warnings; `--runtime` uses Vite-based discovery for
116
+ full coverage (needs `vite` and `@vitejs/plugin-rsc`, optional
117
+ `--config <path>`). Passing a directory scans it for router and `urls()` files.
118
+
111
119
  ### Named Routes, `$$routeNames`, And `router.routeMap`
112
120
 
113
121
  There are two runtime/type surfaces with similar names:
@@ -299,7 +307,9 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
299
307
  ]);
300
308
 
301
309
  // 3. router.tsx - Create router and export reverse
302
- const router = createRouter<AppBindings>({
310
+ import { createRouter } from "@rangojs/router";
311
+
312
+ export const router = createRouter<AppBindings>({
303
313
  document: Document,
304
314
  }).routes(urlpatterns);
305
315
 
@@ -312,10 +322,10 @@ declare global {
312
322
  }
313
323
 
314
324
  export const reverse = router.reverse;
315
- export default router;
316
325
 
317
- // 4. Run `npx rango generate src/router.tsx` to generate
318
- // router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
326
+ // 4. router.named-routes.gen.ts is written by the Vite plugin (dev + build);
327
+ // run `npx rango generate src/router.tsx` to produce it without Vite.
328
+ // It auto-registers GeneratedRouteMap globally.
319
329
  // No manual RegisteredRoutes declaration is needed for named-route handlers,
320
330
  // ctx.reverse, prerender, href(), or Rango.Path. Add `RegisteredRoutes
321
331
  // extends AppRoutes` (an alias of typeof router.routeMap) when global
@@ -323,10 +333,12 @@ export default router;
323
333
  // router.routeMap metadata.
324
334
 
325
335
  // 5. loaders/*.ts - Type-safe loaders
336
+ import { createLoader } from "@rangojs/router";
337
+
326
338
  export const ProductLoader = createLoader(async (ctx) => {
327
- // ctx.params: { slug: string }
328
- // ctx.get("user"): User | undefined (from Rango.Vars)
329
- // ctx.env.DB: D1Database (plain bindings from Rango.Env)
339
+ // ctx.params.slug: string | undefined (a loader is not bound to one route)
340
+ // ctx.get("user"): AppVariables["user"] (from Rango.Vars)
341
+ // ctx.env: any (createLoader does not apply Rango.Env; cast if you need types)
330
342
  return { product: await fetchProduct(ctx.params.slug) };
331
343
  });
332
344
 
@@ -22,12 +22,17 @@ global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
22
22
  import type { Handler } from "@rangojs/router";
23
23
 
24
24
  export const SearchPage: Handler<"search"> = (ctx) => {
25
- // ctx.search is typed: { q: string; page?: number; sort?: string }
26
- const { q, page, sort } = ctx.search;
25
+ // ctx.search is typed: { q: string | undefined; page?: number; sort?: string }
26
+ const { q = "", page = 1, sort } = ctx.search;
27
27
  return <SearchResults q={q} page={page} sort={sort} />;
28
28
  };
29
29
  ```
30
30
 
31
+ Required keys (no `?`) still resolve to `T | undefined` inside the handler:
32
+ the schema tells callers (`ctx.reverse`, `useReverse`) what to send, but the
33
+ server cannot trust the client to send it, so the handler must handle a
34
+ missing value.
35
+
31
36
  This avoids circular references because `Handler` defaults to `GeneratedRouteMap`
32
37
  (from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
33
38
 
@@ -48,8 +53,12 @@ Note the difference: `Handler<"search">` (no dot) resolves against the global
48
53
  `routes` for param/search inference and only uses it for local `ctx.reverse(".x")`.
49
54
 
50
55
  Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
51
- Values are automatically coerced from query string (e.g., `"2"` becomes `2` for numbers).
52
- Routes without a `search` schema keep the standard `URLSearchParams` behavior.
56
+ Values are automatically coerced from the query string: numbers accept finite
57
+ decimal values (`"2"` becomes `2`; `"0x10"`, `"Infinity"` or an empty value are
58
+ treated as missing), and a present boolean is `true` for `"true"`/`"1"` and
59
+ `false` otherwise. Absent keys are omitted, required or not. Routes without a
60
+ `search` schema get `ctx.search` as an empty object; read the raw query from
61
+ `ctx.searchParams` (a `URLSearchParams`).
53
62
 
54
63
  ### RouteSearchParams and RouteParams utility types
55
64
 
@@ -104,12 +113,15 @@ export const NamedRoutes = {
104
113
  } as const;
105
114
  ```
106
115
 
107
- You never open a `.gen.ts` by hand. Treat the generated types as call-site
108
- honesty checks, not modules to read:
116
+ You never edit a `.gen.ts` by hand. Treat the generated types as call-site
117
+ checks, not modules to read:
109
118
 
110
- - **Do not import `router.named-routes.gen.ts` directly**, and don't reach for
111
- `Rango.GeneratedRouteMap`. It is the whole-app manifest, auto-wired
112
- globally — `Handler<"name">` and `ctx.reverse("name")` already see it.
119
+ - **You rarely need to import `router.named-routes.gen.ts`**, and you don't
120
+ need to reference `Rango.GeneratedRouteMap`. It is the whole-app manifest,
121
+ auto-wired globally — `Handler<"name">` and `ctx.reverse("name")` already
122
+ see it. Importing its `NamedRoutes` value is an escape hatch (global names
123
+ in `useReverse`, an absolute-path map for `/api-client`); on the client it
124
+ ships every route name and pattern in the app.
113
125
  - **Per-module `*.gen.ts` imports are fine** — they are the opt-in local-route
114
126
  pattern for `useReverse(routes)` and explicit local handler typing
115
127
  (`Handler<".name", routes>`). See `/links`.
@@ -119,35 +131,48 @@ smell — fix the call site (or regenerate), never edit the generated file.
119
131
 
120
132
  ## Loader Type Safety
121
133
 
122
- Loaders have typed return values:
134
+ A loader's return type is inferred from its function and flows to every
135
+ consumer. `createLoader` is not bound to one route, so `ctx.params` is
136
+ `Record<string, string | undefined>`:
123
137
 
124
138
  ```typescript
125
139
  // loaders/product.ts
140
+ import { createLoader } from "@rangojs/router";
141
+
126
142
  export const ProductLoader = createLoader(async (ctx) => {
127
143
  return {
128
- id: ctx.params.slug,
144
+ id: ctx.params.slug ?? "",
129
145
  name: "Widget",
130
146
  price: 99,
131
147
  };
132
148
  });
149
+ ```
133
150
 
134
- // In server component - type is inferred
135
- import { useLoader } from "@rangojs/router/client";
151
+ Server handler (escape hatch — reads the data in the handler itself):
136
152
 
137
- async function ProductPage() {
138
- const product = await useLoader(ProductLoader);
153
+ ```typescript
154
+ path("/product/:slug", async (ctx) => {
155
+ const product = await ctx.use(ProductLoader);
139
156
  // product: { id: string; name: string; price: number }
140
157
  return <h1>{product.name}</h1>;
141
- }
158
+ }, { name: "product" }, () => [loader(ProductLoader)]);
159
+ ```
160
+
161
+ Client component (the default consumption site):
142
162
 
143
- // In client component - same type
163
+ ```typescript
144
164
  "use client";
145
165
  import { useLoader } from "@rangojs/router/client";
166
+ import { ProductLoader } from "../loaders/product";
146
167
 
147
168
  function ProductPrice() {
148
169
  const { data } = useLoader(ProductLoader);
149
170
  // data: { id: string; name: string; price: number }
150
- const product = data;
151
- return <span>${product.price}</span>;
171
+ return <span>${data.price}</span>;
152
172
  }
153
173
  ```
174
+
175
+ `useLoader()` is a client hook — never call it in a server component.
176
+ Client-side data is typed through `Rango.FlightSerialize<T>` (the RSC Flight
177
+ boundary), so values Flight preserves (such as `Date`) keep their type. See
178
+ `/loader` for when to use `ctx.use()` versus `useLoader()`.
@@ -1,12 +1,19 @@
1
1
  # Route Types
2
2
 
3
+ Route names come from the `name` option on `path()`. Names are typed in two
4
+ places: the `urls()` value itself (local names, patterns and response payloads
5
+ as phantom types) and the generated `router.named-routes.gen.ts` (the global
6
+ name map behind `Handler<"name">`, `ctx.reverse()` and `href()`). See
7
+ [`./generated-files-and-cli.md`](./generated-files-and-cli.md) for which surface
8
+ feeds which API.
9
+
3
10
  ## Route Definition with Type-Safe Names
4
11
 
5
12
  ```typescript
6
13
  // urls.tsx
7
14
  import { urls } from "@rangojs/router";
8
15
 
9
- export const urlpatterns = urls(({ path, layout }) => [
16
+ export const urlpatterns = urls(({ path }) => [
10
17
  path("/", HomePage, { name: "home" }),
11
18
  path("/products", ProductsPage, { name: "products" }),
12
19
  path("/product/:slug", ProductPage, { name: "product" }),
@@ -17,7 +24,7 @@ export const urlpatterns = urls(({ path, layout }) => [
17
24
  // Route names are inferred from the { name } option
18
25
  ```
19
26
 
20
- ## Type-Safe href()
27
+ ## Type-Safe URL Generation
21
28
 
22
29
  ### Server: ctx.reverse with route names
23
30
 
@@ -36,8 +43,15 @@ export const ProductHandler: Handler<"shop.product"> = (ctx) => {
36
43
  };
37
44
  ```
38
45
 
39
- For type-safe local names, generate a route types file with `npx rango generate urls/shop.tsx`
40
- and pass it as the second generic to `Handler` or `Prerender`:
46
+ Without a local route map, `.name` calls accept any string (the include scope
47
+ is only known at runtime). Middleware and response-route handlers are the
48
+ exception: their `ctx.reverse` (`GlobalReverseFunction`) has no include scope,
49
+ so it takes global names only and a `.name` call there is a type error. Global names are checked against the generated
50
+ map once it exists. For type-safe local names, generate a route types file with
51
+ `npx rango generate urls/shop.tsx` and pass its `routes` type as the second
52
+ generic to `Handler` or `Prerender`. With a local map, local names also
53
+ require their params (mount params such as `:tenantId` may still be passed as
54
+ extras):
41
55
 
42
56
  ```typescript
43
57
  import type { Handler } from "@rangojs/router";
@@ -61,6 +75,7 @@ import { href, useHref, Link } from "@rangojs/router/client";
61
75
  // href() validates absolute paths via PatternToPath types
62
76
  href("/about"); // Valid path
63
77
  href("/blog/hello"); // Matches /blog/:slug
78
+ href("/blog/hello?page=2#top"); // Query and hash suffixes are allowed
64
79
 
65
80
  // useHref() auto-prefixes with include() mount
66
81
  function ShopNav() {
@@ -69,6 +84,10 @@ function ShopNav() {
69
84
  }
70
85
  ```
71
86
 
87
+ `href()` is compile-time only: at runtime it returns the path unchanged. It
88
+ does not resolve route names. `useHref()` prepends the mount but its argument
89
+ is typed as any `/${string}`, not checked against routes.
90
+
72
91
  `href()` and the `Rango.Path` type read from `RegisteredRoutes` when you augment
73
92
  it, otherwise from the auto-generated `GeneratedRouteMap` — so `rango generate`
74
93
  alone type-checks `href()` paths with no manual augmentation. The augmentation
@@ -76,6 +95,7 @@ below is only needed for **`Rango.PathResponse`** (response-payload inference),
76
95
  `GeneratedRouteMap` cannot provide:
77
96
 
78
97
  ```typescript
98
+ // router.tsx, below `export const router = createRouter(...).routes(urlpatterns)`
79
99
  // The alias is required: an interface heritage clause cannot take a `typeof`
80
100
  // type query directly (TS1109).
81
101
  type AppRoutes = typeof router.routeMap;
@@ -200,15 +220,31 @@ a rename is a type error in one place instead of silent drift:
200
220
  ```ts
201
221
  import { addToCart, removeFromCart } from "./actions/cart";
202
222
  import * as CartActions from "./actions/cart";
203
-
204
- revalidate((ctx) => ctx.isAction(addToCart) || undefined); // one action
205
- revalidate((ctx) => ctx.isAction(addToCart, removeFromCart) || undefined); // several
206
- revalidate((ctx) => ctx.isAction(CartActions) || undefined); // any action in the module
207
- revalidate((ctx) => ctx.isAction({ addToCart, removeFromCart }) || undefined); // object form
223
+ import * as OrderActions from "./actions/order";
224
+
225
+ // On a loader: after an action, re-run only for cart actions; on navigation,
226
+ // keep the default.
227
+ revalidate((ctx) => (ctx.isAction() ? ctx.isAction(CartActions) : undefined));
228
+
229
+ // Matcher forms (each returns a boolean):
230
+ ctx.isAction(); // any action at all
231
+ ctx.isAction(addToCart); // one action
232
+ ctx.isAction(addToCart, removeFromCart); // several
233
+ ctx.isAction(CartActions); // any action in the module
234
+ ctx.isAction({ addToCart, removeFromCart }); // object form
235
+ ctx.isAction({ Cart: CartActions, Order: OrderActions }); // grouped namespaces
208
236
  ```
209
237
 
210
- `ctx.isAction()` (only available on the revalidate predicate's context) returns a
211
- raw boolean — combine with `|| undefined` for the "revalidate on match, else
212
- defer" intent. It resolves the reference the same way the router derives
213
- `actionId` (`$id` in production, `$$id` in dev), so matching
214
- works in both modes. `actionId` stays available for advanced cases.
238
+ `ctx.isAction()` (only available on the revalidate predicate's argument)
239
+ returns a raw boolean and is `false` on plain navigation. Wrap it by what the
240
+ segment's default already does: `|| undefined` adds a re-render on a parent
241
+ layout or layout-level parallel (skipped after actions by default); on a loader
242
+ or route segment, which already re-run after every action, `|| undefined`
243
+ changes nothing, so narrow with the ternary above. Avoid a bare
244
+ `ctx.isAction(X)`: it is a hard `false` on navigation too. See `/loader` →
245
+ "`revalidate()` return shapes". It resolves
246
+ the reference the same way the router derives `actionId` (`$id ?? $$id`: the
247
+ file-path `$id` injected in a production RSC build, otherwise React's `$$id`),
248
+ so matching works in dev and production. `actionId` stays available for
249
+ advanced cases. In `clientUrls()` groups the predicate runs in the browser,
250
+ where `actionId` is the hashed form (see `/client-urls`).