@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c

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 (392) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +126 -16
  3. package/dist/bin/rango.js +319 -95
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +2724 -1053
  6. package/package.json +68 -14
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +64 -2
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +224 -32
  11. package/skills/caching/SKILL.md +279 -17
  12. package/skills/composability/SKILL.md +27 -3
  13. package/skills/css/SKILL.md +76 -0
  14. package/skills/debug-manifest/SKILL.md +4 -2
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +11 -9
  17. package/skills/hooks/SKILL.md +243 -29
  18. package/skills/host-router/SKILL.md +83 -23
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +68 -19
  21. package/skills/layout/SKILL.md +13 -9
  22. package/skills/links/SKILL.md +190 -23
  23. package/skills/loader/SKILL.md +235 -9
  24. package/skills/middleware/SKILL.md +18 -10
  25. package/skills/migrate-nextjs/SKILL.md +43 -19
  26. package/skills/migrate-react-router/SKILL.md +8 -2
  27. package/skills/mime-routes/SKILL.md +28 -1
  28. package/skills/observability/SKILL.md +172 -0
  29. package/skills/parallel/SKILL.md +18 -7
  30. package/skills/prerender/SKILL.md +65 -60
  31. package/skills/rango/SKILL.md +251 -24
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +115 -48
  34. package/skills/route/SKILL.md +46 -5
  35. package/skills/router-setup/SKILL.md +30 -8
  36. package/skills/scripts/SKILL.md +179 -0
  37. package/skills/server-actions/SKILL.md +775 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +130 -0
  40. package/skills/testing/bindings.md +103 -0
  41. package/skills/testing/cache-prerender.md +127 -0
  42. package/skills/testing/client-components.md +124 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +91 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +122 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +123 -0
  54. package/skills/typesafety/SKILL.md +322 -29
  55. package/skills/use-cache/SKILL.md +57 -14
  56. package/skills/view-transitions/SKILL.md +337 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +0 -65
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/connection-warmup.ts +134 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +192 -150
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/navigation-bridge.ts +94 -25
  70. package/src/browser/navigation-client.ts +121 -84
  71. package/src/browser/navigation-store-handle.ts +38 -0
  72. package/src/browser/navigation-store.ts +115 -67
  73. package/src/browser/navigation-transaction.ts +9 -59
  74. package/src/browser/network-error-handler.ts +34 -7
  75. package/src/browser/partial-update.ts +147 -128
  76. package/src/browser/prefetch/cache.ts +107 -56
  77. package/src/browser/prefetch/fetch.ts +204 -34
  78. package/src/browser/prefetch/queue.ts +6 -3
  79. package/src/browser/rango-state.ts +158 -76
  80. package/src/browser/react/Link.tsx +30 -7
  81. package/src/browser/react/NavigationProvider.tsx +283 -118
  82. package/src/browser/react/ScrollRestoration.tsx +10 -6
  83. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  84. package/src/browser/react/filter-segment-order.ts +66 -7
  85. package/src/browser/react/index.ts +0 -48
  86. package/src/browser/react/location-state-shared.ts +178 -8
  87. package/src/browser/react/location-state.ts +39 -14
  88. package/src/browser/react/use-action.ts +6 -15
  89. package/src/browser/react/use-handle.ts +17 -14
  90. package/src/browser/react/use-href.tsx +8 -1
  91. package/src/browser/react/use-link-status.ts +33 -8
  92. package/src/browser/react/use-navigation.ts +10 -5
  93. package/src/browser/react/use-params.ts +11 -11
  94. package/src/browser/react/use-reverse.ts +106 -0
  95. package/src/browser/react/use-router.ts +25 -3
  96. package/src/browser/react/use-search-params.ts +0 -5
  97. package/src/browser/react/use-segments.ts +11 -21
  98. package/src/browser/response-adapter.ts +99 -8
  99. package/src/browser/rsc-router.tsx +91 -24
  100. package/src/browser/scroll-restoration.ts +30 -17
  101. package/src/browser/segment-structure-assert.ts +2 -2
  102. package/src/browser/server-action-bridge.ts +214 -55
  103. package/src/browser/types.ts +80 -9
  104. package/src/browser/validate-redirect-origin.ts +43 -16
  105. package/src/build/collect-fallback-refs.ts +107 -0
  106. package/src/build/generate-manifest.ts +60 -35
  107. package/src/build/generate-route-types.ts +2 -1
  108. package/src/build/index.ts +8 -2
  109. package/src/build/prefix-tree-utils.ts +123 -0
  110. package/src/build/route-trie.ts +117 -14
  111. package/src/build/route-types/ast-route-extraction.ts +15 -8
  112. package/src/build/route-types/codegen.ts +16 -5
  113. package/src/build/route-types/include-resolution.ts +117 -23
  114. package/src/build/route-types/param-extraction.ts +6 -3
  115. package/src/build/route-types/per-module-writer.ts +22 -6
  116. package/src/build/route-types/router-processing.ts +55 -28
  117. package/src/build/route-types/scan-filter.ts +1 -1
  118. package/src/build/route-types/source-scan.ts +216 -0
  119. package/src/build/runtime-discovery.ts +9 -20
  120. package/src/cache/cache-error.ts +104 -0
  121. package/src/cache/cache-key-utils.ts +29 -13
  122. package/src/cache/cache-policy.ts +108 -34
  123. package/src/cache/cache-runtime.ts +224 -41
  124. package/src/cache/cache-scope.ts +188 -82
  125. package/src/cache/cache-tag.ts +103 -0
  126. package/src/cache/cf/cf-base64.ts +33 -0
  127. package/src/cache/cf/cf-cache-constants.ts +127 -0
  128. package/src/cache/cf/cf-cache-store.ts +1989 -378
  129. package/src/cache/cf/cf-cache-types.ts +349 -0
  130. package/src/cache/cf/cf-kv-utils.ts +46 -0
  131. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  132. package/src/cache/cf/index.ts +6 -16
  133. package/src/cache/document-cache.ts +89 -21
  134. package/src/cache/handle-snapshot.ts +70 -0
  135. package/src/cache/index.ts +10 -20
  136. package/src/cache/memory-segment-store.ts +136 -37
  137. package/src/cache/profile-registry.ts +46 -31
  138. package/src/cache/read-through-swr.ts +56 -12
  139. package/src/cache/segment-codec.ts +9 -17
  140. package/src/cache/tag-invalidation.ts +230 -0
  141. package/src/cache/types.ts +37 -100
  142. package/src/client.rsc.tsx +44 -21
  143. package/src/client.tsx +36 -61
  144. package/src/cloudflare/index.ts +11 -0
  145. package/src/cloudflare/tracing.ts +109 -0
  146. package/src/component-utils.ts +19 -0
  147. package/src/components/DefaultDocument.tsx +8 -2
  148. package/src/context-var.ts +18 -6
  149. package/src/decode-loader-results.ts +52 -0
  150. package/src/defer.ts +196 -0
  151. package/src/deps/ssr.ts +0 -1
  152. package/src/encode-kv.ts +49 -0
  153. package/src/errors.ts +30 -4
  154. package/src/escape-script.ts +52 -0
  155. package/src/handle.ts +31 -23
  156. package/src/handles/MetaTags.tsx +62 -19
  157. package/src/handles/Scripts.tsx +183 -0
  158. package/src/handles/breadcrumbs.ts +37 -8
  159. package/src/handles/is-thenable.ts +19 -0
  160. package/src/handles/meta.ts +51 -40
  161. package/src/handles/script.ts +244 -0
  162. package/src/host/cookie-handler.ts +9 -60
  163. package/src/host/errors.ts +0 -24
  164. package/src/host/index.ts +8 -2
  165. package/src/host/pattern-matcher.ts +23 -52
  166. package/src/host/router.ts +107 -99
  167. package/src/host/testing.ts +40 -27
  168. package/src/host/types.ts +37 -4
  169. package/src/host/utils.ts +1 -1
  170. package/src/href-client.ts +137 -22
  171. package/src/index.rsc.ts +96 -12
  172. package/src/index.ts +94 -14
  173. package/src/internal-debug.ts +11 -10
  174. package/src/loader-store.ts +500 -0
  175. package/src/loader.rsc.ts +20 -13
  176. package/src/loader.ts +12 -11
  177. package/src/missing-id-error.ts +68 -0
  178. package/src/outlet-context.ts +1 -1
  179. package/src/outlet-provider.tsx +1 -5
  180. package/src/prerender/param-hash.ts +16 -16
  181. package/src/prerender/store.ts +32 -37
  182. package/src/prerender.ts +61 -6
  183. package/src/redirect-origin.ts +100 -0
  184. package/src/regex-escape.ts +8 -0
  185. package/src/render-error-thrower.tsx +20 -0
  186. package/src/response-utils.ts +34 -0
  187. package/src/reverse.ts +65 -40
  188. package/src/root-error-boundary.tsx +1 -19
  189. package/src/route-content-wrapper.tsx +19 -77
  190. package/src/route-definition/dsl-helpers.ts +304 -309
  191. package/src/route-definition/helper-factories.ts +28 -140
  192. package/src/route-definition/helpers-types.ts +82 -55
  193. package/src/route-definition/index.ts +1 -2
  194. package/src/route-definition/redirect.ts +44 -11
  195. package/src/route-definition/resolve-handler-use.ts +12 -1
  196. package/src/route-definition/use-item-types.ts +29 -0
  197. package/src/route-map-builder.ts +0 -16
  198. package/src/route-types.ts +19 -46
  199. package/src/router/basename.ts +14 -0
  200. package/src/router/content-negotiation.ts +73 -25
  201. package/src/router/error-handling.ts +45 -18
  202. package/src/router/find-match.ts +44 -23
  203. package/src/router/handler-context.ts +27 -43
  204. package/src/router/instrument.ts +350 -0
  205. package/src/router/intercept-resolution.ts +39 -20
  206. package/src/router/lazy-includes.ts +10 -47
  207. package/src/router/loader-resolution.ts +155 -72
  208. package/src/router/logging.ts +0 -6
  209. package/src/router/manifest.ts +18 -29
  210. package/src/router/match-api.ts +9 -24
  211. package/src/router/match-context.ts +0 -22
  212. package/src/router/match-handlers.ts +58 -58
  213. package/src/router/match-middleware/background-revalidation.ts +40 -24
  214. package/src/router/match-middleware/cache-lookup.ts +159 -285
  215. package/src/router/match-middleware/cache-store.ts +64 -52
  216. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  217. package/src/router/match-middleware/segment-resolution.ts +0 -22
  218. package/src/router/match-pipelines.ts +1 -42
  219. package/src/router/match-result.ts +44 -74
  220. package/src/router/metrics.ts +0 -34
  221. package/src/router/middleware-types.ts +7 -134
  222. package/src/router/middleware.ts +247 -166
  223. package/src/router/navigation-snapshot.ts +0 -51
  224. package/src/router/params-util.ts +23 -0
  225. package/src/router/pattern-matching.ts +85 -94
  226. package/src/router/prefetch-cache-ttl.ts +51 -0
  227. package/src/router/prerender-match.ts +104 -65
  228. package/src/router/preview-match.ts +3 -1
  229. package/src/router/request-classification.ts +28 -62
  230. package/src/router/revalidation.ts +123 -73
  231. package/src/router/route-snapshot.ts +0 -1
  232. package/src/router/router-context.ts +3 -28
  233. package/src/router/router-interfaces.ts +83 -35
  234. package/src/router/router-options.ts +136 -5
  235. package/src/router/router-registry.ts +2 -5
  236. package/src/router/segment-resolution/fresh.ts +97 -84
  237. package/src/router/segment-resolution/helpers.ts +86 -6
  238. package/src/router/segment-resolution/loader-cache.ts +76 -39
  239. package/src/router/segment-resolution/revalidation.ts +272 -320
  240. package/src/router/segment-resolution/static-store.ts +19 -5
  241. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  242. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  243. package/src/router/segment-resolution.ts +5 -1
  244. package/src/router/segment-wrappers.ts +6 -5
  245. package/src/router/state-cookie-name.ts +33 -0
  246. package/src/router/substitute-pattern-params.ts +56 -0
  247. package/src/router/telemetry-otel.ts +161 -199
  248. package/src/router/telemetry.ts +96 -19
  249. package/src/router/timeout.ts +0 -20
  250. package/src/router/tracing.ts +206 -0
  251. package/src/router/trie-matching.ts +162 -64
  252. package/src/router/types.ts +9 -63
  253. package/src/router/url-params.ts +0 -5
  254. package/src/router.ts +110 -55
  255. package/src/rsc/handler-context.ts +3 -2
  256. package/src/rsc/handler.ts +264 -220
  257. package/src/rsc/helpers.ts +100 -6
  258. package/src/rsc/index.ts +2 -5
  259. package/src/rsc/json-route-result.ts +38 -0
  260. package/src/rsc/loader-fetch.ts +114 -38
  261. package/src/rsc/manifest-init.ts +28 -41
  262. package/src/rsc/origin-guard.ts +39 -25
  263. package/src/rsc/progressive-enhancement.ts +117 -11
  264. package/src/rsc/redirect-guard.ts +99 -0
  265. package/src/rsc/response-cache-serve.ts +238 -0
  266. package/src/rsc/response-error.ts +79 -12
  267. package/src/rsc/response-route-handler.ts +88 -188
  268. package/src/rsc/rsc-rendering.ts +98 -76
  269. package/src/rsc/runtime-warnings.ts +23 -10
  270. package/src/rsc/server-action.ts +281 -117
  271. package/src/rsc/ssr-setup.ts +16 -0
  272. package/src/rsc/transition-gate.ts +89 -0
  273. package/src/rsc/types.ts +23 -5
  274. package/src/runtime-env.ts +18 -0
  275. package/src/search-params.ts +35 -30
  276. package/src/segment-loader-promise.ts +31 -4
  277. package/src/segment-system.tsx +254 -143
  278. package/src/serialize.ts +243 -0
  279. package/src/server/context.ts +163 -51
  280. package/src/server/cookie-parse.ts +32 -0
  281. package/src/server/cookie-store.ts +80 -5
  282. package/src/server/handle-store.ts +21 -38
  283. package/src/server/loader-registry.ts +33 -42
  284. package/src/server/request-context.ts +287 -178
  285. package/src/ssr/index.tsx +21 -16
  286. package/src/static-handler.ts +10 -13
  287. package/src/testing/cache-status.ts +162 -0
  288. package/src/testing/collect-handle.ts +40 -0
  289. package/src/testing/dispatch.ts +701 -0
  290. package/src/testing/dom.entry.ts +22 -0
  291. package/src/testing/e2e/fixture.ts +188 -0
  292. package/src/testing/e2e/index.ts +128 -0
  293. package/src/testing/e2e/matchers.ts +35 -0
  294. package/src/testing/e2e/page-helpers.ts +272 -0
  295. package/src/testing/e2e/parity.ts +387 -0
  296. package/src/testing/e2e/server.ts +195 -0
  297. package/src/testing/flight-matchers.ts +97 -0
  298. package/src/testing/flight-normalize.ts +11 -0
  299. package/src/testing/flight-runtime.d.ts +57 -0
  300. package/src/testing/flight-tree.ts +682 -0
  301. package/src/testing/flight.entry.ts +52 -0
  302. package/src/testing/flight.ts +257 -0
  303. package/src/testing/generated-routes.ts +183 -0
  304. package/src/testing/index.ts +105 -0
  305. package/src/testing/internal/context.ts +371 -0
  306. package/src/testing/internal/flight-client-globals.ts +30 -0
  307. package/src/testing/internal/seed-vars.ts +54 -0
  308. package/src/testing/render-handler.ts +357 -0
  309. package/src/testing/render-route.tsx +581 -0
  310. package/src/testing/run-loader.ts +385 -0
  311. package/src/testing/run-middleware.ts +205 -0
  312. package/src/testing/run-transition-when.ts +164 -0
  313. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  314. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  315. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  316. package/src/testing/vitest-stubs/version.ts +5 -0
  317. package/src/testing/vitest.ts +305 -0
  318. package/src/theme/ThemeProvider.tsx +20 -58
  319. package/src/theme/ThemeScript.tsx +7 -9
  320. package/src/theme/constants.ts +52 -13
  321. package/src/theme/index.ts +0 -7
  322. package/src/theme/theme-context.ts +1 -5
  323. package/src/theme/theme-script.ts +22 -21
  324. package/src/theme/use-theme.ts +0 -3
  325. package/src/types/boundaries.ts +0 -35
  326. package/src/types/cache-types.ts +13 -4
  327. package/src/types/error-types.ts +30 -90
  328. package/src/types/global-namespace.ts +54 -41
  329. package/src/types/handler-context.ts +110 -62
  330. package/src/types/index.ts +3 -10
  331. package/src/types/loader-types.ts +11 -9
  332. package/src/types/request-scope.ts +112 -0
  333. package/src/types/route-config.ts +6 -50
  334. package/src/types/route-entry.ts +0 -6
  335. package/src/types/segments.ts +135 -14
  336. package/src/urls/include-helper.ts +9 -56
  337. package/src/urls/index.ts +1 -11
  338. package/src/urls/path-helper-types.ts +29 -12
  339. package/src/urls/path-helper.ts +17 -106
  340. package/src/urls/pattern-types.ts +36 -19
  341. package/src/urls/response-types.ts +22 -29
  342. package/src/urls/type-extraction.ts +58 -139
  343. package/src/urls/urls-function.ts +1 -19
  344. package/src/use-loader.tsx +292 -107
  345. package/src/vite/debug.ts +185 -0
  346. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  347. package/src/vite/discovery/discover-routers.ts +126 -85
  348. package/src/vite/discovery/discovery-errors.ts +194 -0
  349. package/src/vite/discovery/gate-state.ts +171 -0
  350. package/src/vite/discovery/prerender-collection.ts +96 -68
  351. package/src/vite/discovery/route-types-writer.ts +40 -84
  352. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  353. package/src/vite/discovery/state.ts +44 -0
  354. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  355. package/src/vite/index.ts +2 -0
  356. package/src/vite/inject-client-debug.ts +36 -0
  357. package/src/vite/plugin-types.ts +126 -8
  358. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  359. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  360. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  361. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  362. package/src/vite/plugins/expose-action-id.ts +48 -95
  363. package/src/vite/plugins/expose-id-utils.ts +88 -55
  364. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  365. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  366. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  367. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  368. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  369. package/src/vite/plugins/performance-tracks.ts +26 -25
  370. package/src/vite/plugins/refresh-cmd.ts +1 -1
  371. package/src/vite/plugins/use-cache-transform.ts +73 -83
  372. package/src/vite/plugins/version-injector.ts +40 -29
  373. package/src/vite/plugins/version-plugin.ts +37 -40
  374. package/src/vite/plugins/virtual-entries.ts +39 -25
  375. package/src/vite/rango.ts +109 -118
  376. package/src/vite/router-discovery.ts +718 -119
  377. package/src/vite/utils/ast-handler-extract.ts +26 -35
  378. package/src/vite/utils/banner.ts +1 -1
  379. package/src/vite/utils/bundle-analysis.ts +10 -15
  380. package/src/vite/utils/client-chunks.ts +184 -0
  381. package/src/vite/utils/directive-prologue.ts +40 -0
  382. package/src/vite/utils/forward-user-plugins.ts +171 -0
  383. package/src/vite/utils/manifest-utils.ts +4 -59
  384. package/src/vite/utils/package-resolution.ts +20 -52
  385. package/src/vite/utils/prerender-utils.ts +54 -39
  386. package/src/vite/utils/shared-utils.ts +90 -41
  387. package/src/browser/action-response-classifier.ts +0 -99
  388. package/src/browser/react/use-client-cache.ts +0 -58
  389. package/src/browser/shallow.ts +0 -40
  390. package/src/handles/index.ts +0 -7
  391. package/src/network-error-thrower.tsx +0 -23
  392. package/src/router/middleware-cookies.ts +0 -55
@@ -6,7 +6,10 @@ argument-hint: [loader]
6
6
 
7
7
  # Data Loaders with loader()
8
8
 
9
- Loaders fetch data on the server and stream it to the client.
9
+ Loaders fetch data on the server and stream it to the client. For mutations
10
+ (writes triggered by forms or buttons), use server actions instead — see
11
+ `/server-actions`. Loaders re-resolve after an action runs, so the typical
12
+ flow is _action mutates → loader re-reads → UI updates_.
10
13
 
11
14
  ## Creating a Loader
12
15
 
@@ -88,6 +91,20 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
88
91
  ]);
89
92
  ```
90
93
 
94
+ > **Client refresh `key` vs. server `cache({ key })` vs. `revalidate()`.** Three
95
+ > different "what refreshes" knobs that are easy to confuse:
96
+ >
97
+ > - `useLoader(Loader, { key })` / `useFetchLoader(Loader, { key })` — a
98
+ > **client** refresh identity. It groups which mounted reads of one loader
99
+ > refresh together when one calls `load()`. It never touches the server
100
+ > request. For refreshing **different** loaders together, tag them with
101
+ > `{ refreshGroup }` (one name or several) and call `useRefreshLoaders()(name)`
102
+ > (plain GET only). See the hooks skill ("Scoping refetch with a `key`" and
103
+ > "Refreshing multiple loaders together").
104
+ > - `cache({ key })` — a **server** cache identity (storage hit/miss/ttl/swr).
105
+ > - `revalidate()` — which **server** segments/loaders recompute during
106
+ > navigation and action refreshes.
107
+
91
108
  DSL loaders are the **live data layer** — they resolve fresh on every
92
109
  request, even when the route is inside a `cache()` boundary. The router
93
110
  excludes them from the segment cache at storage time and re-resolves them
@@ -139,7 +156,29 @@ same memoized result — loaders never run twice per request.
139
156
 
140
157
  ## Loader Context
141
158
 
142
- Loaders receive the same context as route handlers:
159
+ Loaders receive the same context shape as route handlers.
160
+
161
+ ### Full field surface
162
+
163
+ | Field | Type | Notes |
164
+ | -------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
165
+ | `params` | `TParams` | Merged route + explicit loader params; overridable by fetchable `load({ params })`. |
166
+ | `routeParams` | `Record<string, string>` | Server-trusted route params from URL pattern matching; cannot be overridden. |
167
+ | `request` | `Request` | The incoming `Request` (headers, method, body, `signal` for abort). |
168
+ | `url` | `URL` | Parsed request URL. |
169
+ | `pathname` | `string` | URL pathname (shortcut for `ctx.url.pathname`). |
170
+ | `searchParams` | `URLSearchParams` | Shortcut for `ctx.url.searchParams`. |
171
+ | `search` | `ResolveSearchSchema<TSearch>` | Typed query params when a search schema is declared on the route; `{}` otherwise. |
172
+ | `env` | `TEnv` | Plain bindings from `createRouter<TEnv>()` (DB, KV, secrets, etc.). |
173
+ | `get` | `(key \| ContextVar) => value` | Reads variables/context-vars set by middleware. |
174
+ | `use` | `(loader \| handle) => T` | Access another loader's data (Promise) or a handle's collected data (after `await ctx.rendered()`). |
175
+ | `rendered` | `() => Promise<void>` | **Experimental.** DSL loaders only — waits for all non-loader segments (including `loading()` streaming handlers) to settle before reading handle data. |
176
+ | `method` | `string` | HTTP method. `"GET"` for SSR loader runs; reflects real method for fetchable loaders. |
177
+ | `body` | `TBody \| undefined` | Parsed request body for fetchable POST/PUT/PATCH/DELETE calls. |
178
+ | `formData` | `FormData \| undefined` | Present when a fetchable loader is invoked via form submission. |
179
+ | `reverse` | `ScopedReverseFunction` | Generate type-checked URLs from route names (same scoped semantics as route handlers). |
180
+
181
+ ### Example
143
182
 
144
183
  ```typescript
145
184
  export const ProductLoader = createLoader(async (ctx) => {
@@ -160,13 +199,24 @@ export const ProductLoader = createLoader(async (ctx) => {
160
199
  // Request headers
161
200
  const auth = ctx.request.headers.get("Authorization");
162
201
 
163
- // Variables set by middleware (from RSCRouter.Vars augmentation)
202
+ // Variables set by middleware (from Rango.Vars augmentation)
164
203
  const user = ctx.get("user");
165
204
 
166
- return { product: await fetchProduct(slug) };
205
+ // Type-checked URLs for payloads. `.name` resolves within the current
206
+ // include() scope; a bare `name` resolves globally. See /route and
207
+ // /typesafety for scope rules and route-name autocomplete.
208
+ const detailUrl = ctx.reverse(".detail", { slug });
209
+
210
+ return {
211
+ product: await fetchProduct(slug),
212
+ links: { self: detailUrl },
213
+ };
167
214
  });
168
215
  ```
169
216
 
217
+ See `/route` for the full handler-context contract (shared with loaders) and
218
+ `/typesafety` for route-name typing that powers `ctx.reverse` autocomplete.
219
+
170
220
  ### params vs routeParams
171
221
 
172
222
  - `ctx.params` — merged route params + explicit loader params. For fetchable
@@ -199,6 +249,8 @@ export const OrderLoader = createLoader(async (ctx) => {
199
249
  Add caching or revalidation to specific loaders:
200
250
 
201
251
  ```typescript
252
+ import * as CartActions from "./actions/cart";
253
+
202
254
  path("/product/:slug", ProductPage, { name: "product" }, () => [
203
255
  // Cached loader
204
256
  loader(ProductLoader, () => [cache({ ttl: 300 })]),
@@ -208,13 +260,104 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
208
260
  revalidate(() => false), // Never revalidate
209
261
  ]),
210
262
 
211
- // Loader that revalidates after cart actions
263
+ // Loader that revalidates after cart actions (defer otherwise — keeps the
264
+ // permissive loader defaults for navigation and other actions intact)
212
265
  loader(CartLoader, () => [
213
- revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
266
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined),
214
267
  ]),
215
268
  ]);
216
269
  ```
217
270
 
271
+ ### `revalidate()` return shapes
272
+
273
+ > **Scope: `revalidate()` is a partial-render concern, not a cache concern.**
274
+ > It decides whether a segment (here, a loader) re-runs and streams to the
275
+ > client on a navigation or action — never whether a cached value is stale. The
276
+ > cache decides hit/miss/ttl/swr independently and never reads `revalidate()`.
277
+ > Caching a loader is a separate, opt-in step (`loader(Fn, () => [cache({...})])`).
278
+ > See `/cache-guide` → "Two axes" and `/rango` → "The shape of rango".
279
+
280
+ A `revalidate(fn)` callback can return one of four shapes. The chain
281
+ processes revalidators in order; each call's return controls how the
282
+ chain continues:
283
+
284
+ ```typescript
285
+ // 1) Hard decision — short-circuits the chain, used as the final answer.
286
+ revalidate(() => true);
287
+ revalidate(({ actionId }) => actionId?.includes("Cart") ?? false);
288
+
289
+ // 2) Soft decision — updates the running suggestion for downstream
290
+ // revalidators on the same segment, chain continues.
291
+ revalidate(({ defaultShouldRevalidate }) => ({
292
+ defaultShouldRevalidate: !defaultShouldRevalidate,
293
+ }));
294
+
295
+ // 3) Defer (no opinion) — leaves the running suggestion unchanged and
296
+ // continues to the next revalidator. Implicit return / null /
297
+ // undefined are all equivalent and consumer-friendly.
298
+ revalidate(({ actionId }) => {
299
+ if (actionId?.includes("Cart")) return true; // hard for this branch only
300
+ // implicit return — let downstream revalidators or the segment default decide
301
+ });
302
+ revalidate(() => undefined); // explicit defer
303
+ revalidate(() => null); // explicit defer
304
+ ```
305
+
306
+ If every revalidator on a segment defers, the segment-type default
307
+ (e.g. params-changed for routes, `false` for parallels) is used.
308
+
309
+ #### `|| undefined` (defer) vs `?? false` (hard) — pick deliberately
310
+
311
+ A boolean return — including `false` — is a **hard** decision: it short-circuits
312
+ the chain and overrides the segment default. `undefined` **defers** to the
313
+ running suggestion / segment default. They are not interchangeable:
314
+
315
+ ```typescript
316
+ // Defer: "revalidate on match, otherwise let the default/downstream decide."
317
+ revalidate(({ actionId }) => actionId?.includes("Cart") || undefined);
318
+
319
+ // Hard: "revalidate ONLY on match, suppress everything else."
320
+ revalidate(({ actionId }) => actionId?.includes("Cart") ?? false);
321
+ ```
322
+
323
+ This matters most for loaders, whose defaults are permissive: a loader defaults
324
+ to revalidating on **any** action (`POST`) and on **param/search changes**
325
+ during navigation. So `?? false` on a loader silently suppresses both — the
326
+ loader will not refetch when you navigate to a different `:id`. Use
327
+ `|| undefined` when you want to _add_ a revalidation signal on top of the
328
+ sensible defaults, and reserve `?? false` for the rare case where you genuinely
329
+ want the loader to refetch on nothing but your matched action.
330
+
331
+ When **composing multiple revalidators** on one segment (see below), defer is
332
+ mandatory: the first hard `?? false` ends the chain and the later contracts
333
+ never run.
334
+
335
+ #### Matching actions: `ctx.isAction()`
336
+
337
+ To revalidate after specific server actions, match them by **reference** with
338
+ `ctx.isAction()` rather than hand-written `actionId` substrings. A rename or
339
+ moved file then becomes a type error instead of silently failing to match:
340
+
341
+ ```typescript
342
+ import { addToCart, removeFromCart } from "../actions/cart";
343
+ import * as CartActions from "../actions/cart";
344
+
345
+ loader(CartLoader, () => [
346
+ revalidate((ctx) => ctx.isAction(addToCart) || undefined), // one action
347
+ ]);
348
+ revalidate((ctx) => ctx.isAction(addToCart, removeFromCart) || undefined); // several
349
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined); // any action in the module
350
+ ```
351
+
352
+ `isAction()` is a method on the revalidate predicate's **context argument** —
353
+ there is no standalone `isAction` import; you always reach it through the callback
354
+ parameter (`revalidate((ctx) => ctx.isAction(...))`). It returns a raw boolean, so
355
+ pair it with `|| undefined` for the usual "revalidate on match, else defer"
356
+ intent. It returns `false` on plain navigation and on non-matches, and resolves
357
+ the reference the same way the router derives `actionId` (`$id` in production,
358
+ `$$id` in dev), so it matches in both modes. The raw `actionId` string stays
359
+ available on the same context as an escape hatch.
360
+
218
361
  ### Revalidation Contracts for Loader Dependencies
219
362
 
220
363
  If a loader reads `ctx.get()` data produced by an outer handler/layout, share
@@ -222,8 +365,12 @@ the same named revalidation contract across producer and consumer segments.
222
365
 
223
366
  ```typescript
224
367
  // revalidation-contracts.ts
225
- export const revalidateAccountScope = ({ actionId }) =>
226
- actionId?.includes("src/actions/account.ts#") ?? false;
368
+ import * as AccountActions from "./actions/account";
369
+
370
+ // Match by reference with ctx.isAction() (rename-safe), and defer (|| undefined)
371
+ // so these contracts compose — a hard `false` would short-circuit the rest.
372
+ export const revalidateAccountScope = (ctx) =>
373
+ ctx.isAction(AccountActions) || undefined;
227
374
 
228
375
  layout(AccountLayout, () => [
229
376
  revalidate(revalidateAccountScope), // producer reruns
@@ -266,6 +413,64 @@ follows the same rule: at build time, loaders are skipped entirely (there is no
266
413
  real request context), and at runtime the worker resolves them fresh against
267
414
  the live database.
268
415
 
416
+ ### Parallel and streaming — latency overlaps first paint
417
+
418
+ Loaders do not block the page. As the render pass begins — the pass that route
419
+ middleware wraps, so loaders run right after middleware, not in a later
420
+ phase — every matched loader is kicked off **concurrently** (their promises start in the
421
+ same tick), and each result is **streamed** to the client as its own RSC Flight
422
+ chunk rather than awaited up front. Pair a loader with `loading()` (or a
423
+ client `<Suspense>`) and the shell paints immediately while the data streams in.
424
+
425
+ This is why **"cached UI still pays full data latency" is the wrong intuition**:
426
+ on a `cache()` hit the UI segments stream instantly from cache while the live
427
+ loaders resolve fresh **in parallel** — data latency _overlaps_ first paint
428
+ instead of being added on top of it. (Without a `loading()` / `<Suspense>`
429
+ boundary a parallel loader blocks its parent, so add one to keep the overlap.)
430
+
431
+ If you come from a framework where the loader is a blocking step that runs
432
+ before the response is built, this is the shift to internalize: here the
433
+ response starts streaming first and loader data fills in.
434
+
435
+ ### See it: `debugPerformance`
436
+
437
+ Turn on the per-request performance timeline early — it is the fastest way to
438
+ confirm loaders overlap rather than serialize, and to find the real bottleneck
439
+ locally instead of guessing:
440
+
441
+ ```typescript
442
+ const router = createRouter({ document: Document, debugPerformance: true });
443
+ ```
444
+
445
+ Or enable it per-request from middleware (e.g. only when `?debug` is present) by
446
+ calling `ctx.debugPerformance()` **before** `await next()`. Each HTML request
447
+ then prints a shared-axis waterfall (and emits a `Server-Timing` header):
448
+
449
+ ```
450
+ [RSC Perf] GET /product/widget (24.53ms)
451
+ start dur span timeline
452
+ 0.08ms 3.20ms route-matching |#####...................................|
453
+ 3.40ms 8.70ms ssr-render-html |.....##############.....................|
454
+ 3.42ms 11.90ms loader:…#ProductLoader |.....###################................|
455
+ 3.45ms 11.40ms loader:…#ReviewsLoader |.....##################.................|
456
+ 0.00ms 24.53ms handler:total |########################################|
457
+ ```
458
+
459
+ How to read it:
460
+
461
+ - **Humans:** scan the `#` bars on the shared axis. Bars that start at the same
462
+ offset and run side by side are executing **in parallel** — loaders should
463
+ overlap `ssr-render-html` / `render:total`, not sit alone to the right of
464
+ everything. A lone `loader:*` bar past the render bar is serialized latency to
465
+ chase. `handler:total` is the whole request; `render:total` is the render pass.
466
+ - **LLMs / programmatic:** read each row as `{ start, dur, label }`. A loader
467
+ overlaps paint when its `[start, start+dur]` interval intersects
468
+ `render:total` / `ssr-render-html`. Flag a regression when a `loader:*`
469
+ interval is **disjoint from and starts after** `render:total`, or when its
470
+ `dur` approaches `handler:total` — that loader is on the critical path instead
471
+ of overlapping it. Two `loader:*` rows with near-equal `start` confirm
472
+ parallel execution.
473
+
269
474
  ### Opting a Loader into Caching
270
475
 
271
476
  To cache a specific loader's data, attach a `cache()` child:
@@ -356,6 +561,8 @@ entirely (no read, no write).
356
561
  ### Per-Loader Store Override
357
562
 
358
563
  ```typescript
564
+ import { MemorySegmentCacheStore } from "@rangojs/router/cache";
565
+
359
566
  const hotStore = new MemorySegmentCacheStore({ defaults: { ttl: 10 } });
360
567
 
361
568
  loader(PricingLoader, () => [
@@ -464,6 +671,16 @@ export const SearchLoader = createLoader(async (ctx) => {
464
671
  }, true); // true = fetchable
465
672
  ```
466
673
 
674
+ > **No registration needed — and no worker-entry import.** A fetchable loader
675
+ > does not have to be registered with `loader()` in the route DSL, and it does
676
+ > not have to be imported by any server module. Importing it into the client
677
+ > component that calls `useFetchLoader()` / `load()` is enough. Rango discovers
678
+ > every `createLoader(fn, true)` at build time and registers it for the
679
+ > `_rsc_loader` endpoint, so a loader reachable only through a client component
680
+ > still resolves in production — on both the generated entry and a hand-written
681
+ > worker entry (e.g. a Cloudflare `worker.rsc.tsx`). You do **not** need to
682
+ > force-import the loader in your worker entry to make it resolve.
683
+
467
684
  ### Fetchable Loader with Middleware
468
685
 
469
686
  Pass an options object instead of `true` to attach per-loader middleware.
@@ -539,6 +756,13 @@ export const FileUploadLoader = createLoader(async (ctx) => {
539
756
 
540
757
  Client usage — see `/hooks useFetchLoader` for the full client-side pattern.
541
758
 
759
+ > **Refetch sharing**: when the loader is registered on the route via
760
+ > `loader()`, a plain `load()` call (no `params`, no `body`) broadcasts
761
+ > the new value to every component reading the same loader id —
762
+ > `useLoader` reads in layouts, pages, and parallel slots all converge.
763
+ > Calls with `params` or a non-GET method stay local to the call site.
764
+ > See `/hooks` → "Shared refetch behavior" for the full contract.
765
+
542
766
  ## Complete Example
543
767
 
544
768
  ```typescript
@@ -571,10 +795,12 @@ export const CartLoader = createLoader(async (ctx) => {
571
795
  });
572
796
 
573
797
  // urls.tsx — register loaders in the DSL
798
+ import * as CartActions from "./actions/cart";
799
+
574
800
  export const urlpatterns = urls(({ path, layout, loader, loading, cache, revalidate }) => [
575
801
  layout(<ShopLayout />, () => [
576
802
  loader(CartLoader, () => [
577
- revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
803
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined),
578
804
  ]),
579
805
 
580
806
  path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
@@ -10,9 +10,6 @@ Middleware runs before/after route handlers using the onion model.
10
10
 
11
11
  ## Execution Model
12
12
 
13
- Canonical semantics reference:
14
- [docs/execution-model.md](../../docs/internal/execution-model.md)
15
-
16
13
  There are two levels of middleware with different execution scopes:
17
14
 
18
15
  ### Global middleware (`router.use()`)
@@ -32,17 +29,26 @@ When the router has a `basename`, pattern-scoped `.use()` patterns are automatic
32
29
 
33
30
  Registered inside `urls()` callback. Wraps **rendering only** -- it does NOT wrap server action execution. Actions run before route middleware, so when route middleware executes during post-action revalidation, it can observe state that the action set (cookies, context variables, headers).
34
31
 
32
+ > **Implication for auth:** route middleware cannot guard server actions. Use `router.use("/admin/*", requireAuth)` (global, scoped) for action protection, or check inside the action body. See `/server-actions` for action-side auth patterns.
33
+
35
34
  ```
36
35
  Request flow (with action):
37
- global mw -> action executes -> route mw -> layout -> handler -> loaders
36
+ global mw -> action executes -> route mw -> render pass
38
37
 
39
38
  Request flow (no action):
40
- global mw -> route mw -> layout -> handler -> loaders
39
+ global mw -> route mw -> render pass
41
40
 
42
41
  Progressive enhancement (no-JS form POST):
43
42
  global mw -> action executes -> route mw -> full page re-render
44
43
  ```
45
44
 
45
+ The **render pass** resolves handler, layouts, parallels, and loaders together —
46
+ it is not a handler-then-loaders sequence. Handler-first ordering is guaranteed
47
+ only between a route handler and its child/orphan layouts and parallels (so
48
+ `ctx.set` is visible); loaders run **concurrently** and stream their results, so
49
+ their latency overlaps rendering rather than blocking it. See `/loader` →
50
+ "Parallel and streaming".
51
+
46
52
  The contract is: **route middleware wraps rendering regardless of transport** (JS-enabled RSC stream or no-JS HTML). During PE re-render, route middleware observes action-set state (cookies, context variables) the same way it does during JS-enabled post-action revalidation.
47
53
 
48
54
  Revalidation is still partial. Route middleware wraps the render pass that
@@ -61,8 +67,10 @@ For shared segment data, use named revalidation contracts on both the producer
61
67
  and consumer segments, even when middleware is present in the chain.
62
68
 
63
69
  ```typescript
64
- export const revalidateCartData = ({ actionId }) =>
65
- actionId?.includes("src/actions/cart.ts#") ?? false;
70
+ import * as CartActions from "./actions/cart";
71
+
72
+ export const revalidateCartData = (ctx) =>
73
+ ctx.isAction(CartActions) || undefined;
66
74
 
67
75
  layout(CartLayout, () => [
68
76
  middleware(cartRenderMiddleware),
@@ -190,7 +198,7 @@ export const myMiddleware: Middleware = async (ctx, next) => {
190
198
  ctx.env.DB; // D1Database
191
199
  ctx.env.KV; // KVNamespace
192
200
 
193
- // Set variables for downstream handlers (typed via RSCRouter.Vars)
201
+ // Set variables for downstream handlers (typed via Rango.Vars)
194
202
  ctx.set("user", { id: "123", name: "John" });
195
203
 
196
204
  // Continue to next middleware/handler
@@ -231,8 +239,8 @@ const Dashboard: Handler<"dashboard"> = (ctx) => {
231
239
  ```
232
240
 
233
241
  This works alongside `ctx.get("key")` / `ctx.set("key", value)` (global typing
234
- via RSCRouter.Vars augmentation). Use `createVar` for route-local or feature-scoped
235
- data; use RSCRouter.Vars for app-wide middleware state.
242
+ via Rango.Vars augmentation). Use `createVar` for route-local or feature-scoped
243
+ data; use Rango.Vars for app-wide middleware state.
236
244
 
237
245
  ## Redirect with State in Middleware
238
246
 
@@ -193,9 +193,9 @@ The main content always goes through `<Outlet />` via the `path()` handler.
193
193
  // Rango: explicit intercept in layout
194
194
  layout(<ShopLayout />, () => [
195
195
  path("/product/:id", ProductPage, { name: "product" }),
196
- intercept("@modal", ".product", <ProductModal />, () => [
197
- when(({ from }) => from.pathname.startsWith("/shop")),
198
- ]),
196
+ intercept("@modal", ".product", <ProductModal />, {
197
+ when: ({ from }) => from.pathname.startsWith("/shop"),
198
+ }),
199
199
  ])
200
200
  ```
201
201
 
@@ -225,7 +225,7 @@ Loaders are Rango's live data layer. Use them when you need:
225
225
 
226
226
  - **Client-side data refresh** — `useLoader()` in client components for reactive data
227
227
  - **Per-loader caching** — opt in with `loader(MyLoader, () => [cache({ ttl: 60 })])`; loaders stay live by default
228
- - **Revalidation control** — `revalidate()` targets specific loaders after actions
228
+ - **Revalidation control** — `revalidate()` targets specific segments and loaders after actions
229
229
  - **Loading skeletons** — `loading()` shows a Suspense fallback while loaders resolve
230
230
 
231
231
  ```typescript
@@ -288,21 +288,40 @@ export const Product = Passthrough(ProductDef, async (ctx) => {
288
288
  Use `Passthrough()` whenever the Next.js route has `dynamicParams: true` (the
289
289
  default) or serves an open-ended param space. See `/prerender` for full API.
290
290
 
291
- ### Revalidation: different model
291
+ ### Revalidation: two distinct axes
292
292
 
293
- Next.js uses path/tag-based cache invalidation (`revalidatePath`, `revalidateTag`)
294
- to bust cached responses. Rango does not currently have a direct equivalent.
293
+ Next.js conflates two things under "revalidation." Rango separates them — and
294
+ tag-based cache invalidation now maps directly.
295
295
 
296
- In Rango, separate these two concepts:
296
+ **1. Cache invalidation (bust cached values) — direct equivalent.** Tag entries
297
+ with `cache({ tags })` or, inside a `"use cache"` function, runtime
298
+ `cacheTag(...tags)`. Then invalidate by tag:
297
299
 
298
- **Partial rendering revalidation** — `revalidate()` controls which segments
299
- (layouts, paths, loaders, parallels) should re-run during partial action
300
- re-rendering. This is about the segment tree, not cache invalidation:
300
+ ```typescript
301
+ // Next.js Rango
302
+ // revalidateTag("products") → await updateTag("products") // in a server action: awaitable,
303
+ // // read-your-own-writes (next render is fresh)
304
+ // or revalidateTag("products") // in a route handler / webhook:
305
+ // // background, non-blocking (hard-purge)
306
+ ```
307
+
308
+ `updateTag` is awaitable and immediate; `revalidateTag` is fire-and-forget. Both
309
+ hard-purge (the next read re-renders fresh); the only difference is awaitability —
310
+ despite the Next.js name, `revalidateTag` here is NOT stale-while-revalidate.
311
+ Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`) index by tag. Next's
312
+ `revalidatePath` has no path-based equivalent — tag the relevant entries instead.
313
+
314
+ **2. Partial-render selection (which segments re-run after an action).** This is
315
+ NOT cache invalidation — it is `revalidate()`, controlling which segments
316
+ (layouts, paths, loaders, parallels) recompute during partial action
317
+ re-rendering:
301
318
 
302
319
  ```typescript
320
+ import { updateBlog } from "./actions/blog";
321
+
303
322
  // Re-run this layout when a blog action fires
304
323
  layout(BlogLayout, () => [
305
- revalidate(({ actionId }) => actionId?.includes("updateBlog") ?? false),
324
+ revalidate((ctx) => ctx.isAction(updateBlog) || undefined),
306
325
  path("/blog/:slug", BlogPost, { name: "blogPost" }),
307
326
  ]);
308
327
 
@@ -323,15 +342,18 @@ cache({ ttl: 60, swr: 300 }, () => [
323
342
  ]);
324
343
  ```
325
344
 
326
- The key shift is:
345
+ The two axes compose: `updateTag()` / `revalidateTag()` bust cached values;
346
+ `revalidate()` selects which segments re-render and stream to the client after an
347
+ action.
327
348
 
328
- - Next.js asks "which cached path or tag should I invalidate?"
329
- - Rango asks "which segments should re-run after this action?"
349
+ When migrating:
330
350
 
331
- When migrating `revalidatePath()` / `revalidateTag()` usage, the Rango version
332
- usually is not a 1:1 API replacement. Instead, decide which layouts, routes,
333
- loaders, or parallels should recompute after an action and declare
334
- `revalidate()` at those segment boundaries.
351
+ - `revalidateTag(tag)` `await updateTag(tag)` (in a server action) or
352
+ `revalidateTag(tag)` (in a route handler / webhook). Effectively 1:1.
353
+ - `revalidatePath(path)` no path-based equivalent; tag the entries on that
354
+ route (`cache({ tags })` / `cacheTag(...)`) and invalidate by tag.
355
+ - To also force specific segments to re-render after the action (independent of
356
+ cache busting), attach a `revalidate()` rule at those segment boundaries.
335
357
 
336
358
  ## 4. Middleware
337
359
 
@@ -463,6 +485,8 @@ Server actions work the same way — `"use server"` directive, `useActionState`,
463
485
 
464
486
  Key difference: in Rango, route middleware does NOT wrap action execution. Actions only see global middleware context. Use `getRequestContext()` in actions to access `ctx.set()`/`ctx.get()`.
465
487
 
488
+ Next.js's `revalidateTag()` maps directly: tag entries via `cache({ tags })` / `cacheTag(...)`, then invalidate. **In a server action use `await updateTag(tag)`** — it is read-your-own-writes, so the action's own re-render sees fresh data; `revalidateTag(tag)` is a background (non-blocking) hard-purge and is NOT read-your-own-writes, so reserve it for route handlers / webhooks (calling it from an action can leave that action's re-render stale). `revalidatePath()` has no path-based equivalent — tag the route's entries instead. Separately, to force specific matched segments (path/layout/parallel/intercept) and their loaders to re-render after an action, attach a `revalidate(({ actionId }) => ...)` rule to that segment or loader registration. See `/server-actions` for the full pattern (validation, error handling, file uploads), `/caching` for tag invalidation, and `/loader` for revalidation rule semantics.
489
+
466
490
  ## 8. Metadata / Head
467
491
 
468
492
  Rango uses the `Meta` handle + `<MetaTags />` client component:
@@ -483,6 +483,10 @@ Since Rango uses RSC server actions, all React action patterns work:
483
483
  `useActionState`, `useOptimistic`, `useTransition`, `startTransition`,
484
484
  and plain `<form action={serverAction}>`. No framework-specific hook needed.
485
485
 
486
+ For the full guide — defining actions, validation with Zod, error handling,
487
+ revalidation rules, file uploads, and progressive enhancement — see
488
+ `/server-actions`.
489
+
486
490
  ### clientLoader / clientAction (framework mode)
487
491
 
488
492
  RR7 framework mode's `clientLoader` and `clientAction` run in the browser.
@@ -593,11 +597,13 @@ function ErrorBoundary() {
593
597
  }
594
598
 
595
599
  // Rango: errorBoundary() wrapping a group of routes
600
+ // Server-side error boundaries only receive `error` (no `reset` — server render
601
+ // cannot be retried; users can navigate away or refresh).
596
602
  layout(<DashboardLayout />, () => [
597
- errorBoundary(({ error, reset }) => (
603
+ errorBoundary(({ error }) => (
598
604
  <div>
599
605
  <h2>Something went wrong</h2>
600
- <button onClick={reset}>Try again</button>
606
+ <p>{error.message}</p>
601
607
  </div>
602
608
  )),
603
609
  path("/dashboard", DashboardIndex, { name: "dashboard" }),
@@ -81,7 +81,7 @@ export const urlpatterns = urls(({ path }) => [
81
81
  - `Accept: application/json` — JSON handler
82
82
  - `Accept: text/plain` — text handler
83
83
  - `Accept: application/xml` — XML handler
84
- - `Accept: */*` — first variant (JSON, since it was registered first)
84
+ - `Accept: */*` — RSC page (the primary, since it was registered first)
85
85
 
86
86
  ## Wildcard Routes
87
87
 
@@ -108,6 +108,33 @@ path.text("/api/data", () => "plain text version", { name: "dataText" }),
108
108
  Without an RSC primary, there is no `text/html` candidate — the Accept header
109
109
  picks among the response-type candidates directly.
110
110
 
111
+ ## Type Safety For Negotiated Paths
112
+
113
+ `router.named-routes.gen.ts` validates route names, params, search, `href()`, and
114
+ the `Rango.Path` type, but it does not carry response payload metadata. For MIME or
115
+ response payload types, use one of these surfaces:
116
+
117
+ - `RouteResponse<typeof patterns, "routeName">` for a specific response variant
118
+ by route name. This is the clearest option when several MIME variants share
119
+ one URL pattern.
120
+ - `Rango.PathResponse<"/products/:id">` (ambient, no import) for global lookup by URL pattern or concrete path after the app
121
+ registers `typeof router.routeMap`:
122
+
123
+ ```typescript
124
+ // router.tsx
125
+ export const router = createRouter({ document: Document }).routes(urlpatterns);
126
+
127
+ declare global {
128
+ namespace Rango {
129
+ interface RegisteredRoutes extends typeof router.routeMap {}
130
+ }
131
+ }
132
+ ```
133
+
134
+ `RegisteredRoutes` is what exposes the richer routeMap entries containing
135
+ response payload metadata. Without it, URL-pattern response lookup has paths but
136
+ no payloads, so response types resolve to `never`.
137
+
111
138
  ## How It Works
112
139
 
113
140
  1. **Build time**: `buildRouteTrie()` calls `mergeLeaves()` when multiple routes share a pattern.