@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327

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 (440) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +301 -797
  3. package/dist/bin/rango.js +603 -145
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3750 -1160
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +96 -24
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +85 -6
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +228 -33
  12. package/skills/caching/SKILL.md +336 -19
  13. package/skills/catalog.json +271 -0
  14. package/skills/comparison/SKILL.md +50 -0
  15. package/skills/comparison/agents/openai.yaml +4 -0
  16. package/skills/comparison/references/framework-comparison.md +837 -0
  17. package/skills/composability/SKILL.md +110 -4
  18. package/skills/css/SKILL.md +76 -0
  19. package/skills/debug-manifest/SKILL.md +5 -3
  20. package/skills/defer-hydration/SKILL.md +235 -0
  21. package/skills/document-cache/SKILL.md +87 -56
  22. package/skills/fonts/SKILL.md +1 -1
  23. package/skills/handler-use/SKILL.md +12 -10
  24. package/skills/hooks/SKILL.md +73 -691
  25. package/skills/hooks/data.md +273 -0
  26. package/skills/hooks/handle-and-actions.md +103 -0
  27. package/skills/hooks/navigation.md +110 -0
  28. package/skills/hooks/outlets.md +41 -0
  29. package/skills/hooks/state.md +228 -0
  30. package/skills/hooks/urls.md +135 -0
  31. package/skills/host-router/SKILL.md +129 -27
  32. package/skills/i18n/SKILL.md +276 -0
  33. package/skills/intercept/SKILL.md +75 -19
  34. package/skills/layout/SKILL.md +40 -19
  35. package/skills/links/SKILL.md +247 -17
  36. package/skills/loader/SKILL.md +248 -10
  37. package/skills/middleware/SKILL.md +25 -13
  38. package/skills/migrate-nextjs/SKILL.md +205 -20
  39. package/skills/migrate-react-router/SKILL.md +59 -670
  40. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  41. package/skills/migrate-react-router/component-migration.md +196 -0
  42. package/skills/migrate-react-router/data-and-actions.md +225 -0
  43. package/skills/migrate-react-router/route-mapping.md +271 -0
  44. package/skills/mime-routes/SKILL.md +29 -2
  45. package/skills/observability/SKILL.md +202 -0
  46. package/skills/parallel/SKILL.md +40 -10
  47. package/skills/ppr/SKILL.md +616 -0
  48. package/skills/prerender/SKILL.md +72 -60
  49. package/skills/rango/SKILL.md +318 -26
  50. package/skills/react-compiler/SKILL.md +168 -0
  51. package/skills/response-routes/SKILL.md +138 -49
  52. package/skills/route/SKILL.md +117 -9
  53. package/skills/router-setup/SKILL.md +44 -9
  54. package/skills/scripts/SKILL.md +179 -0
  55. package/skills/server-actions/SKILL.md +776 -0
  56. package/skills/shell-manifest/SKILL.md +185 -0
  57. package/skills/streams-and-websockets/SKILL.md +283 -0
  58. package/skills/tailwind/SKILL.md +28 -4
  59. package/skills/testing/SKILL.md +130 -0
  60. package/skills/testing/bindings.md +103 -0
  61. package/skills/testing/cache-prerender.md +127 -0
  62. package/skills/testing/client-components.md +124 -0
  63. package/skills/testing/e2e-parity.md +125 -0
  64. package/skills/testing/flight.md +91 -0
  65. package/skills/testing/handles.md +131 -0
  66. package/skills/testing/loader.md +128 -0
  67. package/skills/testing/middleware.md +99 -0
  68. package/skills/testing/render-handler.md +122 -0
  69. package/skills/testing/response-routes.md +95 -0
  70. package/skills/testing/reverse-and-types.md +85 -0
  71. package/skills/testing/server-actions.md +107 -0
  72. package/skills/testing/server-tree.md +128 -0
  73. package/skills/testing/setup.md +123 -0
  74. package/skills/theme/SKILL.md +1 -1
  75. package/skills/typesafety/SKILL.md +45 -626
  76. package/skills/typesafety/env-and-bindings.md +254 -0
  77. package/skills/typesafety/generated-files-and-cli.md +335 -0
  78. package/skills/typesafety/params-and-search.md +153 -0
  79. package/skills/typesafety/route-types.md +209 -0
  80. package/skills/use-cache/SKILL.md +74 -15
  81. package/skills/vercel/SKILL.md +128 -0
  82. package/skills/view-transitions/SKILL.md +337 -0
  83. package/src/__augment-tests__/augment.ts +81 -0
  84. package/src/__augment-tests__/augmented.check.ts +116 -0
  85. package/src/__internal.ts +0 -65
  86. package/src/browser/action-coordinator.ts +53 -36
  87. package/src/browser/action-fence.ts +47 -0
  88. package/src/browser/app-shell.ts +39 -0
  89. package/src/browser/connection-warmup.ts +134 -0
  90. package/src/browser/cookie-name.ts +140 -0
  91. package/src/browser/event-controller.ts +252 -158
  92. package/src/browser/history-state.ts +21 -0
  93. package/src/browser/index.ts +3 -3
  94. package/src/browser/invalidate-client-cache.ts +52 -0
  95. package/src/browser/logging.ts +28 -0
  96. package/src/browser/merge-segment-loaders.ts +6 -4
  97. package/src/browser/navigation-bridge.ts +94 -25
  98. package/src/browser/navigation-client.ts +144 -79
  99. package/src/browser/navigation-store-handle.ts +38 -0
  100. package/src/browser/navigation-store.ts +161 -73
  101. package/src/browser/navigation-transaction.ts +9 -59
  102. package/src/browser/network-error-handler.ts +34 -7
  103. package/src/browser/partial-update.ts +183 -144
  104. package/src/browser/prefetch/cache.ts +242 -77
  105. package/src/browser/prefetch/fetch.ts +325 -69
  106. package/src/browser/prefetch/queue.ts +61 -12
  107. package/src/browser/rango-state.ts +158 -76
  108. package/src/browser/react/Link.tsx +58 -20
  109. package/src/browser/react/NavigationProvider.tsx +202 -120
  110. package/src/browser/react/ScrollRestoration.tsx +10 -6
  111. package/src/browser/react/filter-segment-order.ts +66 -7
  112. package/src/browser/react/index.ts +0 -48
  113. package/src/browser/react/location-state-shared.ts +178 -8
  114. package/src/browser/react/location-state.ts +39 -14
  115. package/src/browser/react/use-action.ts +6 -15
  116. package/src/browser/react/use-handle.ts +17 -14
  117. package/src/browser/react/use-href.tsx +8 -1
  118. package/src/browser/react/use-link-status.ts +33 -8
  119. package/src/browser/react/use-navigation.ts +32 -7
  120. package/src/browser/react/use-params.ts +20 -10
  121. package/src/browser/react/use-reverse.ts +106 -0
  122. package/src/browser/react/use-router.ts +25 -3
  123. package/src/browser/react/use-search-params.ts +0 -5
  124. package/src/browser/react/use-segments.ts +11 -21
  125. package/src/browser/response-adapter.ts +99 -8
  126. package/src/browser/rsc-router.tsx +145 -28
  127. package/src/browser/scroll-restoration.ts +37 -22
  128. package/src/browser/segment-reconciler.ts +31 -21
  129. package/src/browser/segment-structure-assert.ts +2 -2
  130. package/src/browser/server-action-bridge.ts +236 -65
  131. package/src/browser/types.ts +102 -9
  132. package/src/browser/validate-redirect-origin.ts +43 -16
  133. package/src/build/collect-fallback-refs.ts +107 -0
  134. package/src/build/generate-manifest.ts +203 -154
  135. package/src/build/generate-route-types.ts +3 -1
  136. package/src/build/index.ts +11 -3
  137. package/src/build/prefix-tree-utils.ts +123 -0
  138. package/src/build/route-trie.ts +152 -21
  139. package/src/build/route-types/ast-route-extraction.ts +15 -8
  140. package/src/build/route-types/codegen.ts +16 -5
  141. package/src/build/route-types/include-resolution.ts +456 -62
  142. package/src/build/route-types/param-extraction.ts +6 -3
  143. package/src/build/route-types/per-module-writer.ts +22 -6
  144. package/src/build/route-types/router-processing.ts +128 -51
  145. package/src/build/route-types/scan-filter.ts +1 -1
  146. package/src/build/route-types/source-scan.ts +216 -0
  147. package/src/build/runtime-discovery.ts +13 -21
  148. package/src/cache/cache-error.ts +104 -0
  149. package/src/cache/cache-key-utils.ts +58 -13
  150. package/src/cache/cache-policy.ts +108 -34
  151. package/src/cache/cache-runtime.ts +421 -58
  152. package/src/cache/cache-scope.ts +187 -96
  153. package/src/cache/cache-tag.ts +149 -0
  154. package/src/cache/cf/cf-base64.ts +33 -0
  155. package/src/cache/cf/cf-cache-constants.ts +127 -0
  156. package/src/cache/cf/cf-cache-store.ts +2202 -372
  157. package/src/cache/cf/cf-cache-types.ts +349 -0
  158. package/src/cache/cf/cf-kv-utils.ts +46 -0
  159. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  160. package/src/cache/cf/index.ts +6 -16
  161. package/src/cache/document-cache.ts +126 -41
  162. package/src/cache/handle-snapshot.ts +70 -0
  163. package/src/cache/index.ts +23 -20
  164. package/src/cache/memory-segment-store.ts +243 -37
  165. package/src/cache/profile-registry.ts +46 -31
  166. package/src/cache/read-through-swr.ts +56 -12
  167. package/src/cache/segment-codec.ts +13 -21
  168. package/src/cache/shell-snapshot.ts +417 -0
  169. package/src/cache/tag-invalidation.ts +230 -0
  170. package/src/cache/types.ts +180 -99
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1127 -0
  173. package/src/client.rsc.tsx +41 -21
  174. package/src/client.tsx +33 -61
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +108 -0
  177. package/src/component-utils.ts +19 -0
  178. package/src/components/DefaultDocument.tsx +8 -2
  179. package/src/context-var.ts +18 -6
  180. package/src/decode-loader-results.ts +52 -0
  181. package/src/defer.ts +185 -0
  182. package/src/deps/ssr.ts +0 -1
  183. package/src/encode-kv.ts +49 -0
  184. package/src/errors.ts +30 -4
  185. package/src/escape-script.ts +52 -0
  186. package/src/handle.ts +67 -37
  187. package/src/handles/MetaTags.tsx +24 -53
  188. package/src/handles/Scripts.tsx +183 -0
  189. package/src/handles/breadcrumbs.ts +35 -8
  190. package/src/handles/deferred-resolution.ts +127 -0
  191. package/src/handles/is-thenable.ts +18 -0
  192. package/src/handles/meta.ts +14 -40
  193. package/src/handles/script.ts +244 -0
  194. package/src/host/cookie-handler.ts +9 -60
  195. package/src/host/errors.ts +13 -22
  196. package/src/host/index.ts +9 -2
  197. package/src/host/pattern-matcher.ts +23 -52
  198. package/src/host/router.ts +107 -99
  199. package/src/host/testing.ts +40 -27
  200. package/src/host/types.ts +37 -4
  201. package/src/host/utils.ts +1 -1
  202. package/src/href-client.ts +137 -22
  203. package/src/index.rsc.ts +97 -12
  204. package/src/index.ts +98 -14
  205. package/src/internal-debug.ts +11 -10
  206. package/src/loader-store.ts +500 -0
  207. package/src/loader.rsc.ts +20 -13
  208. package/src/loader.ts +12 -11
  209. package/src/missing-id-error.ts +68 -0
  210. package/src/outlet-context.ts +1 -1
  211. package/src/outlet-provider.tsx +1 -5
  212. package/src/prerender/param-hash.ts +16 -16
  213. package/src/prerender/store.ts +32 -37
  214. package/src/prerender.ts +78 -10
  215. package/src/redirect-origin.ts +114 -0
  216. package/src/regex-escape.ts +8 -0
  217. package/src/render-error-thrower.tsx +20 -0
  218. package/src/response-utils.ts +62 -0
  219. package/src/reverse.ts +65 -39
  220. package/src/root-error-boundary.tsx +1 -19
  221. package/src/route-content-wrapper.tsx +19 -77
  222. package/src/route-definition/dsl-helpers.ts +304 -309
  223. package/src/route-definition/helper-factories.ts +28 -140
  224. package/src/route-definition/helpers-types.ts +87 -59
  225. package/src/route-definition/index.ts +1 -2
  226. package/src/route-definition/redirect.ts +44 -11
  227. package/src/route-definition/resolve-handler-use.ts +12 -1
  228. package/src/route-definition/use-item-types.ts +29 -0
  229. package/src/route-map-builder.ts +41 -20
  230. package/src/route-types.ts +19 -46
  231. package/src/router/basename.ts +14 -0
  232. package/src/router/content-negotiation.ts +73 -25
  233. package/src/router/error-handling.ts +45 -18
  234. package/src/router/find-match.ts +129 -30
  235. package/src/router/handler-context.ts +27 -42
  236. package/src/router/instrument.ts +355 -0
  237. package/src/router/intercept-resolution.ts +39 -20
  238. package/src/router/lazy-includes.ts +82 -59
  239. package/src/router/loader-resolution.ts +167 -72
  240. package/src/router/logging.ts +0 -6
  241. package/src/router/manifest.ts +74 -40
  242. package/src/router/match-api.ts +80 -55
  243. package/src/router/match-context.ts +0 -22
  244. package/src/router/match-handlers.ts +211 -165
  245. package/src/router/match-middleware/background-revalidation.ts +40 -24
  246. package/src/router/match-middleware/cache-lookup.ts +159 -285
  247. package/src/router/match-middleware/cache-store.ts +64 -52
  248. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  249. package/src/router/match-middleware/segment-resolution.ts +0 -22
  250. package/src/router/match-pipelines.ts +1 -42
  251. package/src/router/match-result.ts +69 -79
  252. package/src/router/metrics.ts +0 -34
  253. package/src/router/middleware-types.ts +7 -134
  254. package/src/router/middleware.ts +298 -172
  255. package/src/router/navigation-snapshot.ts +7 -56
  256. package/src/router/params-util.ts +23 -0
  257. package/src/router/parse-pattern.ts +115 -0
  258. package/src/router/pattern-matching.ts +181 -150
  259. package/src/router/prefetch-cache-ttl.ts +51 -0
  260. package/src/router/prefetch-limits.ts +37 -0
  261. package/src/router/prerender-match.ts +112 -67
  262. package/src/router/preview-match.ts +6 -2
  263. package/src/router/request-classification.ts +50 -69
  264. package/src/router/revalidation.ts +123 -73
  265. package/src/router/route-snapshot.ts +14 -3
  266. package/src/router/router-context.ts +6 -29
  267. package/src/router/router-interfaces.ts +115 -36
  268. package/src/router/router-options.ts +166 -5
  269. package/src/router/router-registry.ts +2 -5
  270. package/src/router/segment-resolution/fresh.ts +131 -86
  271. package/src/router/segment-resolution/helpers.ts +86 -6
  272. package/src/router/segment-resolution/loader-cache.ts +139 -39
  273. package/src/router/segment-resolution/loader-mask.ts +67 -0
  274. package/src/router/segment-resolution/loader-snapshot.ts +251 -0
  275. package/src/router/segment-resolution/revalidation.ts +272 -320
  276. package/src/router/segment-resolution/static-store.ts +19 -5
  277. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  278. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  279. package/src/router/segment-resolution.ts +5 -1
  280. package/src/router/segment-wrappers.ts +6 -5
  281. package/src/router/state-cookie-name.ts +33 -0
  282. package/src/router/substitute-pattern-params.ts +75 -0
  283. package/src/router/telemetry-otel.ts +160 -200
  284. package/src/router/telemetry.ts +105 -20
  285. package/src/router/timeout.ts +0 -20
  286. package/src/router/tracing.ts +215 -0
  287. package/src/router/trie-matching.ts +171 -59
  288. package/src/router/types.ts +9 -63
  289. package/src/router/url-params.ts +57 -0
  290. package/src/router.ts +157 -71
  291. package/src/rsc/full-payload.ts +70 -0
  292. package/src/rsc/handler-context.ts +3 -2
  293. package/src/rsc/handler.ts +291 -217
  294. package/src/rsc/helpers.ts +168 -46
  295. package/src/rsc/index.ts +2 -5
  296. package/src/rsc/json-route-result.ts +38 -0
  297. package/src/rsc/loader-fetch.ts +114 -38
  298. package/src/rsc/manifest-init.ts +29 -42
  299. package/src/rsc/nonce.ts +10 -1
  300. package/src/rsc/origin-guard.ts +39 -25
  301. package/src/rsc/progressive-enhancement.ts +124 -13
  302. package/src/rsc/redirect-guard.ts +100 -0
  303. package/src/rsc/response-cache-serve.ts +238 -0
  304. package/src/rsc/response-error.ts +79 -12
  305. package/src/rsc/response-route-handler.ts +99 -189
  306. package/src/rsc/rsc-rendering.ts +421 -76
  307. package/src/rsc/runtime-warnings.ts +23 -10
  308. package/src/rsc/server-action.ts +282 -116
  309. package/src/rsc/shell-capture.ts +1158 -0
  310. package/src/rsc/shell-serve.ts +150 -0
  311. package/src/rsc/ssr-setup.ts +16 -0
  312. package/src/rsc/transition-gate.ts +89 -0
  313. package/src/rsc/types.ts +53 -5
  314. package/src/runtime-env.ts +18 -0
  315. package/src/search-params.ts +35 -30
  316. package/src/segment-loader-promise.ts +49 -4
  317. package/src/segment-system.tsx +350 -149
  318. package/src/serialize.ts +243 -0
  319. package/src/server/context.ts +208 -51
  320. package/src/server/cookie-parse.ts +32 -0
  321. package/src/server/cookie-store.ts +152 -5
  322. package/src/server/handle-store.ts +21 -38
  323. package/src/server/loader-registry.ts +33 -42
  324. package/src/server/request-context.ts +395 -176
  325. package/src/ssr/index.tsx +458 -178
  326. package/src/ssr/ssr-root.tsx +228 -0
  327. package/src/static-handler.ts +10 -13
  328. package/src/testing/cache-status.ts +162 -0
  329. package/src/testing/collect-handle.ts +46 -0
  330. package/src/testing/dispatch.ts +813 -0
  331. package/src/testing/dom.entry.ts +22 -0
  332. package/src/testing/e2e/fixture.ts +188 -0
  333. package/src/testing/e2e/index.ts +128 -0
  334. package/src/testing/e2e/matchers.ts +35 -0
  335. package/src/testing/e2e/page-helpers.ts +272 -0
  336. package/src/testing/e2e/parity.ts +387 -0
  337. package/src/testing/e2e/server.ts +195 -0
  338. package/src/testing/flight-matchers.ts +97 -0
  339. package/src/testing/flight-normalize.ts +11 -0
  340. package/src/testing/flight-runtime.d.ts +57 -0
  341. package/src/testing/flight-tree.ts +682 -0
  342. package/src/testing/flight.entry.ts +52 -0
  343. package/src/testing/flight.ts +257 -0
  344. package/src/testing/generated-routes.ts +199 -0
  345. package/src/testing/index.ts +105 -0
  346. package/src/testing/internal/context.ts +371 -0
  347. package/src/testing/internal/flight-client-globals.ts +30 -0
  348. package/src/testing/internal/seed-vars.ts +54 -0
  349. package/src/testing/render-handler.ts +357 -0
  350. package/src/testing/render-route.tsx +584 -0
  351. package/src/testing/run-loader.ts +385 -0
  352. package/src/testing/run-middleware.ts +205 -0
  353. package/src/testing/run-transition-when.ts +164 -0
  354. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  355. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  356. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  357. package/src/testing/vitest-stubs/version.ts +5 -0
  358. package/src/testing/vitest.ts +305 -0
  359. package/src/theme/ThemeProvider.tsx +56 -84
  360. package/src/theme/ThemeScript.tsx +7 -9
  361. package/src/theme/constants.ts +52 -13
  362. package/src/theme/index.ts +0 -7
  363. package/src/theme/theme-context.ts +1 -5
  364. package/src/theme/theme-script.ts +22 -21
  365. package/src/theme/use-theme.ts +0 -3
  366. package/src/types/boundaries.ts +0 -35
  367. package/src/types/cache-types.ts +13 -4
  368. package/src/types/error-types.ts +30 -90
  369. package/src/types/global-namespace.ts +54 -41
  370. package/src/types/handler-context.ts +110 -62
  371. package/src/types/index.ts +3 -10
  372. package/src/types/loader-types.ts +11 -9
  373. package/src/types/request-scope.ts +112 -0
  374. package/src/types/route-config.ts +20 -52
  375. package/src/types/route-entry.ts +0 -6
  376. package/src/types/segments.ts +135 -14
  377. package/src/urls/include-helper.ts +19 -64
  378. package/src/urls/include-provider.ts +71 -0
  379. package/src/urls/index.ts +2 -11
  380. package/src/urls/path-helper-types.ts +63 -17
  381. package/src/urls/path-helper.ts +22 -106
  382. package/src/urls/pattern-types.ts +72 -19
  383. package/src/urls/response-types.ts +22 -29
  384. package/src/urls/type-extraction.ts +98 -154
  385. package/src/urls/urls-function.ts +1 -19
  386. package/src/use-loader.tsx +292 -107
  387. package/src/vercel/index.ts +11 -0
  388. package/src/vercel/tracing.ts +88 -0
  389. package/src/vite/debug.ts +185 -0
  390. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  391. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  392. package/src/vite/discovery/discover-routers.ts +127 -86
  393. package/src/vite/discovery/discovery-errors.ts +255 -0
  394. package/src/vite/discovery/gate-state.ts +171 -0
  395. package/src/vite/discovery/prerender-collection.ts +96 -68
  396. package/src/vite/discovery/route-types-writer.ts +40 -84
  397. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  398. package/src/vite/discovery/state.ts +45 -1
  399. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  400. package/src/vite/index.ts +4 -0
  401. package/src/vite/inject-client-debug.ts +88 -0
  402. package/src/vite/plugin-types.ts +210 -10
  403. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  404. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  405. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  406. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  407. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  408. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  409. package/src/vite/plugins/expose-action-id.ts +48 -95
  410. package/src/vite/plugins/expose-id-utils.ts +88 -55
  411. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  412. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  413. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  414. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  415. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  416. package/src/vite/plugins/performance-tracks.ts +26 -25
  417. package/src/vite/plugins/refresh-cmd.ts +1 -1
  418. package/src/vite/plugins/use-cache-transform.ts +73 -83
  419. package/src/vite/plugins/vercel-output.ts +384 -0
  420. package/src/vite/plugins/version-injector.ts +40 -29
  421. package/src/vite/plugins/version-plugin.ts +37 -40
  422. package/src/vite/plugins/virtual-entries.ts +138 -27
  423. package/src/vite/rango.ts +236 -138
  424. package/src/vite/router-discovery.ts +927 -136
  425. package/src/vite/utils/ast-handler-extract.ts +26 -35
  426. package/src/vite/utils/banner.ts +1 -1
  427. package/src/vite/utils/bundle-analysis.ts +10 -15
  428. package/src/vite/utils/client-chunks.ts +184 -0
  429. package/src/vite/utils/directive-prologue.ts +40 -0
  430. package/src/vite/utils/forward-user-plugins.ts +171 -0
  431. package/src/vite/utils/manifest-utils.ts +4 -59
  432. package/src/vite/utils/package-resolution.ts +20 -52
  433. package/src/vite/utils/prerender-utils.ts +71 -43
  434. package/src/vite/utils/shared-utils.ts +142 -43
  435. package/src/browser/action-response-classifier.ts +0 -99
  436. package/src/browser/react/use-client-cache.ts +0 -58
  437. package/src/browser/shallow.ts +0 -40
  438. package/src/handles/index.ts +0 -7
  439. package/src/network-error-thrower.tsx +0 -23
  440. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: react-compiler
3
+ description: Enable the React Compiler in a Rango app the @vitejs/plugin-rsc way — a separate @rolldown/plugin-babel running reactCompilerPreset(), ordered after react() and before the plugin that supplies @vitejs/plugin-rsc. Use when a consumer wants to turn React Compiler on, hits the dead plugin-react v6 `react({ babel })` path, or is unsure why server components aren't being compiled.
4
+ argument-hint:
5
+ ---
6
+
7
+ # React Compiler
8
+
9
+ React Compiler is **opt-in** in Rango. The plugin pipeline is fully compatible —
10
+ you just add one more plugin. The catch on a current Rango stack (Vite 8 +
11
+ `@vitejs/plugin-react` v6) is that **v6 dropped its internal Babel for oxc**, so
12
+ the way the React docs and most blog posts show it — `react({ babel: { plugins:
13
+ [...] } })` — silently does nothing. The compiler has to be its own top-level
14
+ plugin.
15
+
16
+ ## The shape (read first)
17
+
18
+ - The compiler is a **Babel** plugin, run via
19
+ [`@rolldown/plugin-babel`](https://www.npmjs.com/package/@rolldown/plugin-babel)
20
+ with `reactCompilerPreset()` from `@vitejs/plugin-react`.
21
+ - **Ordering is load-bearing:** put `babel(...)` **after `react()`** and
22
+ **before the plugin that supplies `@vitejs/plugin-rsc`**. In a default Rango
23
+ app that plugin is `rango()` itself; in a Cloudflare app it is
24
+ `@cloudflare/vite-plugin`.
25
+ - **It is client-only.** `reactCompilerPreset()` gates itself to the client
26
+ environment. Server/RSC components are not compiled, and that is the upstream
27
+ example's behavior — not a Rango limitation. See
28
+ [What gets compiled](#what-gets-compiled-client-only).
29
+ - **Rango's build-time prerender is unaffected.** You do not need to do anything
30
+ special. See [Prerender](#interaction-with-build-time-prerender).
31
+
32
+ ## Step 1: Install
33
+
34
+ ```bash
35
+ pnpm add -D @rolldown/plugin-babel @babel/core babel-plugin-react-compiler
36
+ # TypeScript users also want the Babel core types:
37
+ pnpm add -D @types/babel__core
38
+ ```
39
+
40
+ React 19 ships `react/compiler-runtime` in-tree, so there is **no** extra runtime
41
+ to install and **no** `target` option to set. Only pass `target: '17' | '18'` to
42
+ `reactCompilerPreset()` if you are on an older React.
43
+
44
+ ## Step 2: Wire it in
45
+
46
+ ### Default (non-Cloudflare) app
47
+
48
+ ```ts
49
+ // vite.config.ts
50
+ import { defineConfig } from "vite";
51
+ import react, { reactCompilerPreset } from "@vitejs/plugin-react";
52
+ import babel from "@rolldown/plugin-babel";
53
+ import { rango } from "@rangojs/router/vite";
54
+
55
+ export default defineConfig({
56
+ plugins: [
57
+ react(),
58
+ babel({ presets: [reactCompilerPreset()] }),
59
+ rango(), // supplies @vitejs/plugin-rsc
60
+ ],
61
+ });
62
+ ```
63
+
64
+ ### Cloudflare app
65
+
66
+ ```ts
67
+ // vite.config.ts
68
+ import { cloudflare } from "@cloudflare/vite-plugin";
69
+ import react, { reactCompilerPreset } from "@vitejs/plugin-react";
70
+ import babel from "@rolldown/plugin-babel";
71
+ import { defineConfig } from "vite";
72
+ import { rango } from "@rangojs/router/vite";
73
+
74
+ export default defineConfig({
75
+ plugins: [
76
+ react(),
77
+ babel({ presets: [reactCompilerPreset()] }),
78
+ rango({ preset: "cloudflare" }),
79
+ cloudflare({
80
+ /* ... */
81
+ }), // supplies @vitejs/plugin-rsc
82
+ ],
83
+ });
84
+ ```
85
+
86
+ ## What gets compiled (client-only)
87
+
88
+ `reactCompilerPreset()` carries
89
+ `rolldown.applyToEnvironmentHook: (env) => env.config.consumer === "client"`, so
90
+ even though the babel plugin is top-level, the transform runs **only in the
91
+ `client` environment**:
92
+
93
+ | Environment | `consumer` | Compiled? |
94
+ | ----------- | ---------- | --------- |
95
+ | client | `client` | Yes |
96
+ | ssr | `server` | No |
97
+ | rsc | `server` | No |
98
+
99
+ This matches the upstream `@vitejs/plugin-rsc` example. If you genuinely need to
100
+ compile **server** components, you would have to invoke
101
+ `babel-plugin-react-compiler` yourself without the preset's
102
+ `applyToEnvironmentHook` — that is outside what the example does and is not
103
+ covered here.
104
+
105
+ ## Options
106
+
107
+ `reactCompilerPreset()` forwards to `babel-plugin-react-compiler`:
108
+
109
+ | Option | Effect |
110
+ | ------------------------------- | -------------------------------------------------------------------------------------- |
111
+ | `compilationMode: 'annotation'` | Compile only components marked with the `"use memo"` directive, not every eligible one |
112
+ | `target: '17' \| '18'` | Emit `react-compiler-runtime` calls for React < 19. Omit on React 19+. |
113
+
114
+ ## Interaction with build-time prerender
115
+
116
+ Nothing to configure. Rango's discovery/prerender step runs a throwaway temp Vite
117
+ server (`createTempRscServer`) that forwards only your **resolution** plugins
118
+ (`resolveId` / `load`). A pure transform plugin like `@rolldown/plugin-babel` is
119
+ intentionally **not** forwarded — and that is correct: the temp runner only
120
+ produces **data** (serialized Flight payloads + the route manifest), not shipped
121
+ code, and React Compiler is a memoization-only transform that does not change
122
+ rendered output. Your shipped client bundle still gets compiled, because the
123
+ babel plugin lives in your app's top-level plugin array alongside `react()`.
124
+
125
+ ## Step 3: Verify the compiler actually ran
126
+
127
+ A compiled module imports the cache allocator from `react/compiler-runtime` and
128
+ calls `_c(n)`. Those two appear in **every** compiled module, so they are the
129
+ reliable per-module signal in dev:
130
+
131
+ ```bash
132
+ pnpm dev
133
+ # fetch any client component module straight from Vite and look for the markers:
134
+ curl -s "http://localhost:5173/src/components/SomeClientComponent.tsx" \
135
+ | grep -E "compiler-runtime|_c\("
136
+ ```
137
+
138
+ For a production build, grep the built client bundle for the compiler's
139
+ input-independent cache check, which has a **zero baseline** without the compiler:
140
+
141
+ ```bash
142
+ pnpm build
143
+ grep -r "Symbol.for(\"react.memo_cache_sentinel\")" dist/client/assets/ | head
144
+ ```
145
+
146
+ Note the **comparison** form `$[i] === Symbol.for("react.memo_cache_sentinel")`
147
+ is only emitted for components with input-independent JSX, so it is reliable over
148
+ the **whole** client bundle, not necessarily in one chosen module. (React core
149
+ also defines that symbol once with a single `=` assignment, so count comparisons,
150
+ not the bare string.) Run the same grep over `dist/rsc` / `dist/ssr` and you
151
+ should find **none** — that is the client-only contract.
152
+
153
+ ## Troubleshooting
154
+
155
+ | Symptom | Cause / fix |
156
+ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
157
+ | Nothing is compiled; no `compiler-runtime` import anywhere | You used `react({ babel: { plugins: [...] } })`. plugin-react v6 has no internal Babel — add `@rolldown/plugin-babel` as its own plugin. |
158
+ | Client compiled, but server/RSC components are not | Expected. `reactCompilerPreset()` is client-only (see the table). Not a bug. |
159
+ | `Cannot find module 'babel-plugin-react-compiler'` (or `@babel/core`) | Install the peer deps from Step 1; they are not bundled by `reactCompilerPreset()`. |
160
+ | Build pulls in `react-compiler-runtime` | You set `target: '17'`/`'18'` on React 19. Drop `target` — React 19 ships `react/compiler-runtime` in-tree. |
161
+ | Output looks compiled but a component misbehaves | The component likely breaks the Rules of React. Fix the component, or scope the compiler with `compilationMode: 'annotation'` while you do. |
162
+
163
+ ## Reference
164
+
165
+ A worked, tested wiring (dev + production e2e markers, incl. the client-only
166
+ contract) lives in the `@rangojs/router` repo: `docs/react-compiler.md` and the
167
+ `react-compiler.test.ts` files under `e2e/e2e-basic`, `tests/cloudflare-basic`,
168
+ and `tests/vite-rsc-demo`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: response-routes
3
- description: Response routes (path.json, path.text, etc.) for non-RSC endpoints with typed responses
3
+ description: Response routes (path.json, path.text, etc.) for non-RSC endpoints with typed responses. Use when building a JSON/text API endpoint alongside your pages, or asking how to return raw JSON instead of RSC from a route.
4
4
  argument-hint: [json|text|html|xml|md|image|stream]
5
5
  ---
6
6
 
@@ -68,16 +68,16 @@ export const urlpatterns = urls(({ path, layout, include }) => [
68
68
 
69
69
  ## Available Tags
70
70
 
71
- | Tag | Usage | Handler returns | Auto-wrap |
72
- | -------- | --------------- | ------------------ | ------------------------ |
73
- | `json` | `path.json()` | plain object/array | `{ data: T }` envelope |
74
- | `text` | `path.text()` | string | text/plain Response |
75
- | `html` | `path.html()` | string | text/html Response |
76
- | `xml` | `path.xml()` | string | application/xml Response |
77
- | `md` | `path.md()` | string | text/markdown Response |
78
- | `image` | `path.image()` | Response | pass-through |
79
- | `stream` | `path.stream()` | Response | pass-through |
80
- | `any` | `path.any()` | Response | pass-through |
71
+ | Tag | Usage | Handler returns | Auto-wrap |
72
+ | -------- | --------------- | ------------------ | ----------------------------- |
73
+ | `json` | `path.json()` | plain object/array | bare JSON value (no envelope) |
74
+ | `text` | `path.text()` | string | text/plain Response |
75
+ | `html` | `path.html()` | string | text/html Response |
76
+ | `xml` | `path.xml()` | string | application/xml Response |
77
+ | `md` | `path.md()` | string | text/markdown Response |
78
+ | `image` | `path.image()` | Response | pass-through |
79
+ | `stream` | `path.stream()` | Response | pass-through |
80
+ | `any` | `path.any()` | Response | pass-through |
81
81
 
82
82
  ## ResponseHandlerContext
83
83
 
@@ -139,22 +139,31 @@ path.json(
139
139
  );
140
140
  ```
141
141
 
142
- ## JSON Envelope
142
+ ## JSON Wire Shape
143
143
 
144
- `path.json()` handlers return plain data. The framework auto-wraps it
145
- in a `ResponseEnvelope<T>` discriminated union:
144
+ `path.json()` handlers return plain data. The framework serializes the handler's
145
+ return value **verbatim** (no envelope) on success, and an RFC 9457 `problem+json`
146
+ body on error. Discriminate with `res.ok` / the HTTP status — there is no in-body
147
+ `data`/`error` union:
146
148
 
147
149
  ```typescript
148
- // Success: HTTP 200
149
- { "data": { "status": "ok", "timestamp": 1700000000 } }
150
-
151
- // Error: HTTP 404 (or whatever status RouterError specifies)
152
- { "error": { "message": "Product 999 not found", "code": "NOT_FOUND" } }
150
+ // Success: HTTP 200, content-type application/json
151
+ { "status": "ok", "timestamp": 1700000000 }
152
+
153
+ // Error: HTTP 404 (or whatever status RouterError specifies),
154
+ // content-type application/problem+json
155
+ {
156
+ "title": "Not Found",
157
+ "status": 404,
158
+ "detail": "Product 999 not found",
159
+ "code": "NOT_FOUND"
160
+ // "stack": included in development only
161
+ }
153
162
  ```
154
163
 
155
164
  ### Error Handling with RouterError
156
165
 
157
- Throw `RouterError` to return structured error envelopes:
166
+ Throw `RouterError` to return a structured `problem+json` body:
158
167
 
159
168
  ```typescript
160
169
  import { RouterError } from "@rangojs/router";
@@ -199,25 +208,27 @@ path.json(
199
208
 
200
209
  ## Client-Side Type Safety
201
210
 
202
- ### ResponseEnvelope and isResponseError
211
+ ### Discriminating success vs. error with res.ok
212
+
213
+ Success bodies are the bare value; error bodies are RFC 9457 `ProblemDetails`.
214
+ Branch on `res.ok` (or the HTTP status) — not an in-body union:
203
215
 
204
216
  ```typescript
205
217
  "use client";
206
- import type { ResponseEnvelope, ResponseError } from "@rangojs/router/client";
207
- import { isResponseError } from "@rangojs/router/client";
218
+ import type { ProblemDetails } from "@rangojs/router";
208
219
 
209
220
  // Fetch a typed response
210
221
  const res = await fetch("/api/products/1");
211
- const result: ResponseEnvelope<Product> = await res.json();
212
222
 
213
- if (isResponseError(result)) {
214
- // result.error: ResponseError (message, code?, type?)
215
- // result.data: undefined
216
- console.error(result.error.message);
223
+ if (!res.ok) {
224
+ // Error body: application/problem+json
225
+ const problem: ProblemDetails = await res.json();
226
+ // problem.detail: string, problem.code: string, problem.status: number
227
+ console.error(problem.code, problem.detail);
217
228
  } else {
218
- // result.data: Product
219
- // result.error: undefined
220
- console.log(result.data.name);
229
+ // Success body: the bare value (no envelope)
230
+ const product: Product = await res.json();
231
+ console.log(product.name);
221
232
  }
222
233
  ```
223
234
 
@@ -230,32 +241,82 @@ import type { RouteResponse } from "@rangojs/router";
230
241
 
231
242
  // From the apiPatterns module (before include)
232
243
  type HealthData = RouteResponse<typeof apiPatterns, "health">;
233
- // = ResponseEnvelope<{ status: string; timestamp: number }>
244
+ // = { status: string; timestamp: number }
234
245
 
235
246
  type ProductsData = RouteResponse<typeof apiPatterns, "products">;
236
- // = ResponseEnvelope<{ id: string; name: string; price: number }[]>
247
+ // = { id: string; name: string; price: number }[]
237
248
  ```
238
249
 
239
- ### PathResponse (global lookup by URL pattern)
250
+ `RouteResponse` is the bare success payload (the JSON wire shape) — the same value
251
+ a `fetch().then(r => r.json())` yields on a 2xx. Error bodies are `ProblemDetails`,
252
+ keyed off `res.ok` at runtime, not part of this type.
253
+
254
+ ### Rango.PathResponse (global lookup by URL pattern or concrete path)
240
255
 
241
- Look up response type from the merged route map by URL pattern:
256
+ `Rango.PathResponse` is ambient (no import) and reads from `RegisteredRoutes`,
257
+ which carries response payload metadata. That surface is **not** auto-wired —
258
+ without the augmentation below, `Rango.PathResponse` falls back to the generated
259
+ path/search map, or to a permissive map when nothing is generated. Either way, it
260
+ has no response payload metadata, so response routes resolve to `never`:
242
261
 
243
262
  ```typescript
244
- import type { PathResponse } from "@rangojs/router/client";
263
+ // router.tsx
264
+ export const router = createRouter({ document: Document }).routes(urlpatterns);
265
+
266
+ declare global {
267
+ namespace Rango {
268
+ interface RegisteredRoutes extends typeof router.routeMap {}
269
+ }
270
+ }
271
+ ```
245
272
 
273
+ With that in place, look up the response type by URL pattern (ambient, no import):
274
+
275
+ ```typescript
246
276
  // After include("/api", apiPatterns) in main urls
247
- type Health = PathResponse<"/api/health">;
248
- // = ResponseEnvelope<{ status: string; timestamp: number }>
277
+ type Health = Rango.PathResponse<"/api/health">;
278
+ // = { status: string; timestamp: number }
279
+
280
+ // RSC routes (no JSON payload) return never
281
+ type Home = Rango.PathResponse<"/">;
282
+ // = never
283
+ ```
284
+
285
+ `Rango.PathResponse` also accepts a **concrete path**, so it types a `fetch`
286
+ wrapper whose response is inferred from the path you pass:
287
+
288
+ ```typescript
289
+ import { href } from "@rangojs/router/client";
249
290
 
250
- // RSC routes return ResponseEnvelope<never>
251
- type Home = PathResponse<"/">;
252
- // = ResponseEnvelope<never>
291
+ async function get<T extends Rango.Path>(
292
+ path: T,
293
+ ): Promise<Rango.PathResponse<T>> {
294
+ return fetch(href(path)).then((r) => r.json());
295
+ }
296
+
297
+ const product = await get("/api/products/42"); // Product (bare value)
253
298
  ```
254
299
 
300
+ Pattern keys (`/:id`) match exactly; a concrete path under a _nested_ dynamic
301
+ route can match several patterns and union their responses.
302
+
303
+ `Rango.PathResponse` reports the JSON **wire** shape, not the handler's raw
304
+ return: `path.json()` serializes with `JSON.stringify`, so a handler returning
305
+ `{ createdAt: Date }` resolves to the bare `{ createdAt: string }`. This
306
+ runs through the ambient `Rango.JsonSerialize<T>` transform (`Date -> string`,
307
+ honors `toJSON()`, drops functions/`undefined`, `bigint -> never`). The
308
+ `RouteResponse` surface below applies the same `Rango.JsonSerialize` transform, so
309
+ both response lookups report the identical wire shape.
310
+
311
+ For local/scoped response typing without global augmentation, prefer
312
+ `RouteResponse<typeof patterns, "routeName">` (see the section above) — it reads
313
+ the response payload straight from the `urls()` patterns and needs no
314
+ `RegisteredRoutes` wiring.
315
+
255
316
  ### ParamsFor with Response Routes
256
317
 
257
318
  ```typescript
258
- import type { ParamsFor } from "@rangojs/router/client";
319
+ import type { ParamsFor } from "@rangojs/router";
259
320
 
260
321
  // Works for both RSC and response routes
261
322
  type ProductParams = ParamsFor<"api.productDetail">;
@@ -357,19 +418,35 @@ export const urlpatterns = urls(({ path, include }) => [
357
418
  ]);
358
419
  ```
359
420
 
421
+ A heavy module like this is a good code-split candidate. Pass an async provider
422
+ and the module — its handlers, response serializers, and any nested
423
+ `include()`s — loads on the first request under the prefix instead of at startup:
424
+
425
+ ```typescript
426
+ // blog/urls.tsx: `export default blogPatterns`
427
+ include("/blog", () => import("./blog/urls"), { name: "blog" }),
428
+ ```
429
+
430
+ Named routes and response types still resolve through the split: `TRoutes` and
431
+ the `_responses` phantom are inferred from the resolved `urls()` value, so
432
+ `Rango.PathResponse<"/blog/api/stats">` and `ctx.reverse` are unchanged. See
433
+ `/composability`.
434
+
360
435
  ### Type safety after mounting
361
436
 
362
437
  ```typescript
363
438
  import type { RouteResponse } from "@rangojs/router";
364
- import type { PathResponse, ParamsFor } from "@rangojs/router/client";
439
+ import type { ParamsFor } from "@rangojs/router";
365
440
 
366
- // Scoped (before mount) -- use the module directly
441
+ // Scoped (before mount) -- use the module directly, no global wiring needed
367
442
  type Stats = RouteResponse<typeof blogApiPatterns, "stats">;
368
- // = ResponseEnvelope<{ views: number; visitors: number }>
443
+ // = { views: number; visitors: number }
369
444
 
370
- // After mounting -- names get prefixed
371
- type BlogStats = PathResponse<"/blog/api/stats">;
372
- // = ResponseEnvelope<{ views: number; visitors: number }>
445
+ // After mounting -- names get prefixed.
446
+ // Rango.PathResponse needs `RegisteredRoutes extends typeof router.routeMap` (see above),
447
+ // otherwise it resolves to never.
448
+ type BlogStats = Rango.PathResponse<"/blog/api/stats">;
449
+ // = { views: number; visitors: number }
373
450
 
374
451
  // Params work through nested includes
375
452
  type LikesParams = ParamsFor<"blog.api.likes">;
@@ -400,12 +477,24 @@ path(
400
477
  Multiple response types can share the same URL pattern. See `/mime-routes` for the
401
478
  full content negotiation API (Accept header matching, Vary: Accept, multi-variant routes).
402
479
 
480
+ ## Long-Lived Responses (SSE / WebSocket)
481
+
482
+ For Server-Sent Events (`path.stream`) and WebSocket upgrades (`path.any`
483
+ returning a 101 / `webSocket` Response), see `/streams-and-websockets`.
484
+ Upgrade responses flow through without reconstruction; `Vary` and
485
+ `Server-Timing` are skipped, and stub headers are applied in place on a
486
+ best-effort basis.
487
+
403
488
  ## How It Works
404
489
 
405
490
  1. `path.json()` tags the route at the trie level with a MIME type
406
491
  2. `coreRequestHandler()` checks the tag before the RSC pipeline
407
492
  3. Tagged routes short-circuit: handler runs, Response is returned directly
408
- 4. JSON routes auto-wrap return values in `{ data }` / `{ error }` envelope
493
+ 4. JSON routes serialize the return value verbatim (bare) on success; a thrown error becomes an RFC 9457 `problem+json` body (`application/problem+json`)
409
494
  5. Client-side navigation to response routes gets `X-RSC-Reload` header, triggering hard navigation
410
495
  6. Response types flow through `_responses` phantom type on `UrlPatterns`, propagated by `include()`
411
496
  7. When multiple routes share a URL pattern, the trie merges them for content negotiation (see `/mime-routes`)
497
+
498
+ ## Consuming response routes
499
+
500
+ To call your own response-route JSON APIs from first-party TypeScript with a typed client (typed params, typed payloads inferred from the handler, no `.data`, typed `ProblemDetails` errors), see `/api-client` — a copy-paste recipe over `RouteResponse` + `ExtractParams` + a client-safe path builder. External/third-party consumers use the plain wire directly: bare JSON on success, `application/problem+json` on error.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: route
3
- description: Define routes with path() in @rangojs/router
3
+ description: Define routes with path() in @rangojs/router. Use when creating a new page or route, or asking how to define a URL path and its handler.
4
4
  argument-hint: [pattern]
5
5
  ---
6
6
 
@@ -33,6 +33,69 @@ urls(({ path }) => [
33
33
  ]);
34
34
  ```
35
35
 
36
+ ### Optional URL params at runtime
37
+
38
+ Absent optional params are **omitted from `ctx.params`** — `ctx.params.<name>`
39
+ reads as `undefined`, matching the `RouteParams<"name">` type
40
+ (`{ query?: string }`). Use `??` to default and `=== undefined` to check
41
+ absence:
42
+
43
+ ```typescript
44
+ path("/search/:query?", (ctx) => {
45
+ const query = ctx.params.query ?? ""; // works — undefined coalesces
46
+ if (ctx.params.query === undefined) return <EmptySearch />;
47
+ return <Results query={ctx.params.query} />;
48
+ }, { name: "search" });
49
+ ```
50
+
51
+ For the common pattern of an optional locale prefix
52
+ (`include("/:locale?", routes)`) and the wider react-intl integration —
53
+ locale detection, fallback chains, URL generation with absent locale —
54
+ see `/i18n`.
55
+
56
+ ### Named catch-all params (`:name+` / `:name*`)
57
+
58
+ A catch-all consumes the **rest of the path** and exposes it as a single
59
+ decoded string at `ctx.params.<name>`, with the internal `/` separators kept.
60
+ It must be the **last** segment of the pattern.
61
+
62
+ - `:name+` — **one-or-more** segments (Next `[...name]`, React-Router splat).
63
+ `/docs/:slug+` matches `/docs/a` and `/docs/a/b/c`, but **not** the bare
64
+ `/docs`.
65
+ - `:name*` — **zero-or-more** segments (Next `[[...name]]`). `/docs/:slug*`
66
+ additionally matches the bare `/docs`, binding `ctx.params.slug` to `""`.
67
+
68
+ ```typescript
69
+ urls(({ path }) => [
70
+ // /shop/electronics/phones -> ctx.params.path === "electronics/phones"
71
+ path("/shop/:path+", ShopCatchAll, { name: "shopCatchAll" }),
72
+
73
+ // /docs -> ctx.params.slug === ""
74
+ // /docs/intro -> ctx.params.slug === "intro"
75
+ // /docs/a/b -> ctx.params.slug === "a/b"
76
+ path("/docs/:slug*", (ctx) => {
77
+ const parts = ctx.params.slug === "" ? [] : ctx.params.slug.split("/");
78
+ return <Docs segments={parts} />;
79
+ }, { name: "docs" }),
80
+ ]);
81
+ ```
82
+
83
+ `ctx.params.<name>` is always a `string` for a catch-all (never `undefined`) —
84
+ `:name*` binds `""` for the empty case, so read it directly. `reverse()` /
85
+ `ctx.reverse()` rebuild the URL with separators preserved:
86
+ `reverse("docs", { slug: "a/b" })` -> `/docs/a/b`.
87
+
88
+ The value is the URL-decoded remainder. `split("/")` recovers the segments in the
89
+ common case, but note that a segment containing an encoded slash (`%2F`) decodes
90
+ to a literal `/` and is therefore indistinguishable from a separator — the same
91
+ trade-off the bare `*` splat has. If you need to distinguish those, match on the
92
+ raw pathname instead.
93
+
94
+ The bare unnamed wildcard `path("/files/*", …)` still works and is read at
95
+ `ctx.params["*"]`; prefer a named catch-all when you want a typed param key.
96
+ Combining a modifier with `?`, a literal suffix, or a constraint
97
+ (`:slug*?`, `:slug*.html`, `:slug(a|b)+`) is rejected at build time.
98
+
36
99
  ## Route Handler Patterns
37
100
 
38
101
  ### Component Function
@@ -134,6 +197,13 @@ first. Use `ctx.set(key, value)` to share data with children, who read it
134
197
  via `ctx.get(key)`. Caching wraps all segments together, so either all run
135
198
  or none do.
136
199
 
200
+ This pattern is also safe under partial action revalidation: on an action,
201
+ the route entry re-runs as a unit by default — route segment, loaders, and
202
+ `belongsToRoute` children (orphan layouts, entry parallels) all seed
203
+ revalidate-true, with handler-first ordering preserved. Handler-set data
204
+ stays consistent with no configuration. See `/rango` → "Passing data down
205
+ the tree" for the safest-first ladder.
206
+
137
207
  ### Typed context variables with createVar
138
208
 
139
209
  Use `createVar<T>()` to create a typed token for `ctx.set()`/`ctx.get()`.
@@ -214,14 +284,33 @@ Cacheable vars (the default) can be read freely inside cache scopes.
214
284
 
215
285
  ### Revalidation Contracts for Handler Data
216
286
 
217
- Handler-first guarantees apply within a single full render pass. For partial
218
- action revalidation, define named revalidation contracts and reuse them on both
219
- the producer route and the consumer child segments.
287
+ > **Scope: `revalidate()` is a partial-render concern, not a cache concern.**
288
+ > It decides whether this segment re-runs and streams to the client on a
289
+ > navigation or action never whether a cached value is stale. The cache
290
+ > decides hit/miss/ttl/swr independently and never reads `revalidate()`. See
291
+ > `/cache-guide` → "Two axes" and `/rango` → "The shape of rango".
292
+
293
+ With no `revalidate()` configured, an entry needs no contract: on an action
294
+ the route handler and its children re-run together by default, so handler
295
+ data stays consistent on its own. Contracts matter in two cases:
296
+
297
+ 1. **You narrow the entry's revalidation** with a predicate that can return a
298
+ hard `false` (e.g. bare `ctx.isAction(X)`). A hard `false` on one side of a
299
+ producer/consumer pair desyncs it — the child re-runs by default and reads
300
+ `undefined`, or vice versa. Put the same named contract on the route and
301
+ its dependent children so they narrow together.
302
+ 2. **The producer is an outer entry** (a standalone `layout()` above this
303
+ route). Outer entries skip action revalidation by default, so the shared
304
+ contract is mandatory — see `/layout` → "Revalidation Contracts".
220
305
 
221
306
  ```typescript
222
307
  // revalidation-contracts.ts
223
- export const revalidateCheckoutData = ({ actionId }) =>
224
- actionId?.includes("src/actions/checkout.ts#") ?? false;
308
+ import * as CheckoutActions from "./actions/checkout";
309
+
310
+ // Defer (|| undefined), not ?? false: a hard `false` short-circuits the chain,
311
+ // so when the same segment composes multiple contracts the later ones never run.
312
+ export const revalidateCheckoutData = (ctx) =>
313
+ ctx.isAction(CheckoutActions) || undefined;
225
314
 
226
315
  path("/checkout", CheckoutPage, { name: "checkout" }, () => [
227
316
  revalidate(revalidateCheckoutData), // producer (route handler) reruns
@@ -250,9 +339,6 @@ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
250
339
  ]);
251
340
  ```
252
341
 
253
- For scope/revalidation guarantees and non-guarantees, see:
254
- [docs/execution-model.md](../../docs/internal/execution-model.md)
255
-
256
342
  ## Redirects
257
343
 
258
344
  ### Basic redirect
@@ -269,6 +355,12 @@ path("/old-page", () => redirect("/new-page"), { name: "oldPage" });
269
355
  path("/moved", () => redirect("/new-location", 301), { name: "moved" });
270
356
  ```
271
357
 
358
+ > **Redirecting from a route with `loading()`:** an `async` handler that returns
359
+ > a `Response`/`redirect()` on a route that also declares `loading()` is streamed,
360
+ > so the redirect is rendered into the RSC stream instead of becoming an HTTP
361
+ > redirect. Issue the redirect from `middleware`, a loader, or a **synchronous**
362
+ > handler return instead. (Dev logs a warning if this is hit.)
363
+
272
364
  ### Redirect with location state
273
365
 
274
366
  Carry typed state through redirects (e.g. flash messages):
@@ -313,6 +405,10 @@ state persists on back/forward. See `/hooks` for details.
313
405
  Attach location state to any server response (not just redirects):
314
406
 
315
407
  ```typescript
408
+ import { createLocationState } from "@rangojs/router";
409
+
410
+ const ServerInfo = createLocationState<{ data: string }>();
411
+
316
412
  path("/dashboard", (ctx) => {
317
413
  ctx.setLocationState(ServerInfo({ data: "welcome" }));
318
414
  return <Dashboard />;
@@ -383,6 +479,18 @@ urls(({ path, layout }) => [
383
479
  ])
384
480
  ```
385
481
 
482
+ For composing whole route MODULES, reach for `include()` — and prefer the
483
+ code-split form `include("/shop", () => import("./shop-patterns"))` for any
484
+ group that is a natural unit: it keeps the group off the cold-start path, and
485
+ measured first-hit cost scales with routes-per-chunk, so many small groups
486
+ beat one giant one. Sizing rules and the numbers behind them:
487
+ [skills/composability](../composability/SKILL.md) → "Sizing async include
488
+ groups (measured)".
489
+
490
+ ## View Transitions
491
+
492
+ A route can configure its own `transition()` — the wrap goes around the route's component itself (routes are leaves; they have no separate default outlet channel). If the route component renders a `<ParallelOutlet />` directly, that slot remains inside the route's VT subtree, so prefer mounting parallel slots in a layout when combining intercept modals with route-level transitions. See [skills/view-transitions](../view-transitions/SKILL.md) for examples and the wrap-location rules across layouts, routes, and slots.
493
+
386
494
  ## Handler-attached `.use`
387
495
 
388
496
  Page handlers can carry their own loader, middleware, error boundaries, parallels, and other defaults via a `.use` callback — so the page is self-contained and reusable across mount sites without re-wiring the same items.