@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17

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 (376) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +198 -44
  3. package/dist/bin/rango.js +287 -105
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3248 -1117
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +73 -21
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +107 -1
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +245 -21
  12. package/skills/caching/SKILL.md +302 -6
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +364 -0
  17. package/skills/hooks/SKILL.md +270 -30
  18. package/skills/host-router/SKILL.md +82 -22
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +49 -5
  21. package/skills/layout/SKILL.md +35 -9
  22. package/skills/links/SKILL.md +249 -17
  23. package/skills/loader/SKILL.md +294 -30
  24. package/skills/middleware/SKILL.md +52 -13
  25. package/skills/migrate-nextjs/SKILL.md +584 -0
  26. package/skills/migrate-react-router/SKILL.md +769 -0
  27. package/skills/mime-routes/SKILL.md +27 -0
  28. package/skills/observability/SKILL.md +137 -0
  29. package/skills/parallel/SKILL.md +203 -7
  30. package/skills/prerender/SKILL.md +123 -100
  31. package/skills/rango/SKILL.md +250 -22
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +122 -47
  34. package/skills/route/SKILL.md +97 -5
  35. package/skills/router-setup/SKILL.md +90 -5
  36. package/skills/server-actions/SKILL.md +775 -0
  37. package/skills/streams-and-websockets/SKILL.md +283 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +129 -0
  40. package/skills/testing/bindings.md +89 -0
  41. package/skills/testing/cache-prerender.md +124 -0
  42. package/skills/testing/client-components.md +122 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +92 -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 +121 -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 +120 -0
  54. package/skills/typesafety/SKILL.md +329 -27
  55. package/skills/use-cache/SKILL.md +36 -5
  56. package/skills/view-transitions/SKILL.md +294 -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 +67 -40
  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/app-version.ts +14 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +86 -147
  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/link-interceptor.ts +4 -0
  70. package/src/browser/navigation-bridge.ts +148 -19
  71. package/src/browser/navigation-client.ts +187 -67
  72. package/src/browser/navigation-store-handle.ts +38 -0
  73. package/src/browser/navigation-store.ts +76 -67
  74. package/src/browser/navigation-transaction.ts +18 -66
  75. package/src/browser/partial-update.ts +123 -94
  76. package/src/browser/prefetch/cache.ts +214 -36
  77. package/src/browser/prefetch/fetch.ts +260 -38
  78. package/src/browser/prefetch/policy.ts +6 -0
  79. package/src/browser/prefetch/queue.ts +126 -20
  80. package/src/browser/prefetch/resource-ready.ts +77 -0
  81. package/src/browser/rango-state.ts +158 -76
  82. package/src/browser/react/Link.tsx +93 -11
  83. package/src/browser/react/NavigationProvider.tsx +115 -34
  84. package/src/browser/react/ScrollRestoration.tsx +10 -6
  85. package/src/browser/react/context.ts +7 -2
  86. package/src/browser/react/filter-segment-order.ts +49 -7
  87. package/src/browser/react/index.ts +0 -48
  88. package/src/browser/react/location-state-shared.ts +166 -8
  89. package/src/browser/react/location-state.ts +39 -14
  90. package/src/browser/react/use-action.ts +6 -15
  91. package/src/browser/react/use-handle.ts +23 -69
  92. package/src/browser/react/use-link-status.ts +0 -4
  93. package/src/browser/react/use-navigation.ts +22 -5
  94. package/src/browser/react/use-params.ts +20 -10
  95. package/src/browser/react/use-reverse.ts +106 -0
  96. package/src/browser/react/use-router.ts +46 -11
  97. package/src/browser/react/use-search-params.ts +0 -5
  98. package/src/browser/react/use-segments.ts +11 -21
  99. package/src/browser/response-adapter.ts +52 -1
  100. package/src/browser/rsc-router.tsx +215 -76
  101. package/src/browser/scroll-restoration.ts +46 -39
  102. package/src/browser/segment-reconciler.ts +36 -9
  103. package/src/browser/segment-structure-assert.ts +2 -2
  104. package/src/browser/server-action-bridge.ts +176 -50
  105. package/src/browser/types.ts +95 -11
  106. package/src/browser/validate-redirect-origin.ts +43 -16
  107. package/src/build/collect-fallback-refs.ts +107 -0
  108. package/src/build/generate-manifest.ts +65 -40
  109. package/src/build/generate-route-types.ts +5 -0
  110. package/src/build/index.ts +8 -2
  111. package/src/build/prefix-tree-utils.ts +123 -0
  112. package/src/build/route-trie.ts +137 -32
  113. package/src/build/route-types/codegen.ts +4 -4
  114. package/src/build/route-types/include-resolution.ts +9 -2
  115. package/src/build/route-types/param-extraction.ts +6 -3
  116. package/src/build/route-types/per-module-writer.ts +7 -4
  117. package/src/build/route-types/router-processing.ts +278 -96
  118. package/src/build/route-types/scan-filter.ts +9 -2
  119. package/src/build/route-types/source-scan.ts +118 -0
  120. package/src/build/runtime-discovery.ts +9 -20
  121. package/src/cache/cache-error.ts +104 -0
  122. package/src/cache/cache-policy.ts +68 -28
  123. package/src/cache/cache-runtime.ts +149 -43
  124. package/src/cache/cache-scope.ts +148 -81
  125. package/src/cache/cache-tag.ts +98 -0
  126. package/src/cache/cf/cf-cache-store.ts +2550 -93
  127. package/src/cache/cf/index.ts +11 -17
  128. package/src/cache/document-cache.ts +78 -27
  129. package/src/cache/handle-snapshot.ts +63 -0
  130. package/src/cache/index.ts +23 -20
  131. package/src/cache/memory-segment-store.ts +136 -37
  132. package/src/cache/profile-registry.ts +6 -30
  133. package/src/cache/read-through-swr.ts +41 -11
  134. package/src/cache/segment-codec.ts +0 -16
  135. package/src/cache/tag-invalidation.ts +230 -0
  136. package/src/cache/taint.ts +55 -0
  137. package/src/cache/types.ts +33 -100
  138. package/src/cache/vercel/index.ts +11 -0
  139. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  140. package/src/client.rsc.tsx +6 -21
  141. package/src/client.tsx +108 -290
  142. package/src/component-utils.ts +19 -0
  143. package/src/context-var.ts +84 -2
  144. package/src/debug.ts +2 -2
  145. package/src/decode-loader-results.ts +36 -0
  146. package/src/defer.ts +196 -0
  147. package/src/deps/ssr.ts +0 -1
  148. package/src/errors.ts +30 -4
  149. package/src/handle.ts +70 -22
  150. package/src/handles/MetaTags.tsx +0 -14
  151. package/src/handles/breadcrumbs.ts +16 -5
  152. package/src/handles/meta.ts +0 -39
  153. package/src/host/cookie-handler.ts +0 -36
  154. package/src/host/errors.ts +0 -24
  155. package/src/host/index.ts +8 -2
  156. package/src/host/pattern-matcher.ts +7 -50
  157. package/src/host/router.ts +107 -99
  158. package/src/host/testing.ts +40 -27
  159. package/src/host/types.ts +37 -4
  160. package/src/host/utils.ts +1 -1
  161. package/src/href-client.ts +137 -22
  162. package/src/index.rsc.ts +52 -26
  163. package/src/index.ts +100 -38
  164. package/src/internal-debug.ts +2 -4
  165. package/src/loader-store.ts +500 -0
  166. package/src/loader.rsc.ts +20 -13
  167. package/src/loader.ts +12 -11
  168. package/src/missing-id-error.ts +68 -0
  169. package/src/network-error-thrower.tsx +1 -6
  170. package/src/outlet-context.ts +1 -1
  171. package/src/outlet-provider.tsx +1 -5
  172. package/src/prerender/param-hash.ts +10 -11
  173. package/src/prerender/store.ts +37 -41
  174. package/src/prerender.ts +198 -82
  175. package/src/redirect-origin.ts +100 -0
  176. package/src/response-utils.ts +37 -0
  177. package/src/reverse.ts +65 -15
  178. package/src/root-error-boundary.tsx +1 -19
  179. package/src/route-content-wrapper.tsx +7 -72
  180. package/src/route-definition/dsl-helpers.ts +437 -274
  181. package/src/route-definition/helper-factories.ts +29 -139
  182. package/src/route-definition/helpers-types.ts +113 -37
  183. package/src/route-definition/index.ts +3 -0
  184. package/src/route-definition/redirect.ts +52 -10
  185. package/src/route-definition/resolve-handler-use.ts +161 -0
  186. package/src/route-definition/use-item-types.ts +32 -0
  187. package/src/route-map-builder.ts +7 -17
  188. package/src/route-types.ts +37 -41
  189. package/src/router/basename.ts +14 -0
  190. package/src/router/content-negotiation.ts +108 -9
  191. package/src/router/error-handling.ts +13 -17
  192. package/src/router/find-match.ts +45 -22
  193. package/src/router/handler-context.ts +83 -41
  194. package/src/router/intercept-resolution.ts +25 -23
  195. package/src/router/lazy-includes.ts +19 -53
  196. package/src/router/loader-resolution.ts +213 -30
  197. package/src/router/logging.ts +5 -8
  198. package/src/router/manifest.ts +49 -45
  199. package/src/router/match-api.ts +121 -205
  200. package/src/router/match-context.ts +0 -22
  201. package/src/router/match-handlers.ts +58 -58
  202. package/src/router/match-middleware/background-revalidation.ts +27 -6
  203. package/src/router/match-middleware/cache-lookup.ts +205 -249
  204. package/src/router/match-middleware/cache-store.ts +45 -32
  205. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  206. package/src/router/match-middleware/segment-resolution.ts +52 -18
  207. package/src/router/match-pipelines.ts +1 -42
  208. package/src/router/match-result.ts +104 -40
  209. package/src/router/metrics.ts +5 -34
  210. package/src/router/middleware-types.ts +13 -142
  211. package/src/router/middleware.ts +173 -143
  212. package/src/router/navigation-snapshot.ts +131 -0
  213. package/src/router/params-util.ts +23 -0
  214. package/src/router/pattern-matching.ts +109 -63
  215. package/src/router/prerender-match.ts +192 -54
  216. package/src/router/preview-match.ts +32 -102
  217. package/src/router/request-classification.ts +276 -0
  218. package/src/router/revalidation.ts +63 -55
  219. package/src/router/route-snapshot.ts +244 -0
  220. package/src/router/router-context.ts +6 -28
  221. package/src/router/router-interfaces.ts +100 -35
  222. package/src/router/router-options.ts +91 -11
  223. package/src/router/router-registry.ts +2 -5
  224. package/src/router/segment-resolution/fresh.ts +242 -75
  225. package/src/router/segment-resolution/helpers.ts +64 -25
  226. package/src/router/segment-resolution/loader-cache.ts +41 -37
  227. package/src/router/segment-resolution/revalidation.ts +456 -372
  228. package/src/router/segment-resolution/static-store.ts +19 -5
  229. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  230. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  231. package/src/router/segment-resolution.ts +4 -1
  232. package/src/router/segment-wrappers.ts +2 -3
  233. package/src/router/state-cookie-name.ts +33 -0
  234. package/src/router/substitute-pattern-params.ts +56 -0
  235. package/src/router/telemetry-otel.ts +0 -20
  236. package/src/router/telemetry.ts +96 -19
  237. package/src/router/timeout.ts +0 -20
  238. package/src/router/trie-matching.ts +91 -46
  239. package/src/router/types.ts +10 -63
  240. package/src/router/url-params.ts +44 -0
  241. package/src/router.ts +134 -43
  242. package/src/rsc/handler-context.ts +3 -2
  243. package/src/rsc/handler.ts +492 -383
  244. package/src/rsc/helpers.ts +162 -46
  245. package/src/rsc/index.ts +1 -1
  246. package/src/rsc/json-route-result.ts +38 -0
  247. package/src/rsc/loader-fetch.ts +23 -3
  248. package/src/rsc/manifest-init.ts +33 -42
  249. package/src/rsc/origin-guard.ts +39 -25
  250. package/src/rsc/progressive-enhancement.ts +30 -3
  251. package/src/rsc/redirect-guard.ts +99 -0
  252. package/src/rsc/response-error.ts +79 -12
  253. package/src/rsc/response-route-handler.ts +90 -63
  254. package/src/rsc/rsc-rendering.ts +56 -54
  255. package/src/rsc/runtime-warnings.ts +23 -10
  256. package/src/rsc/server-action.ts +74 -67
  257. package/src/rsc/ssr-setup.ts +18 -2
  258. package/src/rsc/types.ts +25 -6
  259. package/src/runtime-env.ts +18 -0
  260. package/src/search-params.ts +4 -20
  261. package/src/segment-content-promise.ts +67 -0
  262. package/src/segment-loader-promise.ts +134 -0
  263. package/src/segment-system.tsx +272 -129
  264. package/src/serialize.ts +243 -0
  265. package/src/server/context.ts +309 -61
  266. package/src/server/cookie-store.ts +80 -5
  267. package/src/server/handle-store.ts +26 -24
  268. package/src/server/loader-registry.ts +10 -28
  269. package/src/server/request-context.ts +348 -128
  270. package/src/ssr/index.tsx +23 -15
  271. package/src/static-handler.ts +27 -18
  272. package/src/testing/cache-status.ts +162 -0
  273. package/src/testing/collect-handle.ts +40 -0
  274. package/src/testing/dispatch.ts +618 -0
  275. package/src/testing/dom.entry.ts +22 -0
  276. package/src/testing/e2e/fixture.ts +188 -0
  277. package/src/testing/e2e/index.ts +128 -0
  278. package/src/testing/e2e/matchers.ts +35 -0
  279. package/src/testing/e2e/page-helpers.ts +272 -0
  280. package/src/testing/e2e/parity.ts +387 -0
  281. package/src/testing/e2e/server.ts +195 -0
  282. package/src/testing/flight-matchers.ts +97 -0
  283. package/src/testing/flight-normalize.ts +11 -0
  284. package/src/testing/flight-runtime.d.ts +57 -0
  285. package/src/testing/flight-tree.ts +682 -0
  286. package/src/testing/flight.entry.ts +52 -0
  287. package/src/testing/flight.ts +232 -0
  288. package/src/testing/generated-routes.ts +183 -0
  289. package/src/testing/index.ts +99 -0
  290. package/src/testing/internal/context.ts +348 -0
  291. package/src/testing/internal/flight-client-globals.ts +30 -0
  292. package/src/testing/internal/seed-vars.ts +54 -0
  293. package/src/testing/render-handler.ts +330 -0
  294. package/src/testing/render-route.tsx +566 -0
  295. package/src/testing/run-loader.ts +378 -0
  296. package/src/testing/run-middleware.ts +205 -0
  297. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  298. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  299. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  300. package/src/testing/vitest-stubs/version.ts +5 -0
  301. package/src/testing/vitest.ts +305 -0
  302. package/src/theme/ThemeProvider.tsx +0 -52
  303. package/src/theme/ThemeScript.tsx +0 -6
  304. package/src/theme/constants.ts +0 -12
  305. package/src/theme/index.ts +0 -7
  306. package/src/theme/theme-context.ts +1 -5
  307. package/src/theme/theme-script.ts +0 -14
  308. package/src/theme/use-theme.ts +0 -3
  309. package/src/types/boundaries.ts +0 -35
  310. package/src/types/cache-types.ts +17 -8
  311. package/src/types/error-types.ts +30 -90
  312. package/src/types/global-namespace.ts +54 -41
  313. package/src/types/handler-context.ts +233 -81
  314. package/src/types/index.ts +1 -10
  315. package/src/types/loader-types.ts +44 -15
  316. package/src/types/request-scope.ts +107 -0
  317. package/src/types/route-config.ts +6 -50
  318. package/src/types/route-entry.ts +19 -7
  319. package/src/types/segments.ts +37 -14
  320. package/src/urls/include-helper.ts +33 -70
  321. package/src/urls/index.ts +1 -11
  322. package/src/urls/path-helper-types.ts +58 -11
  323. package/src/urls/path-helper.ts +57 -111
  324. package/src/urls/pattern-types.ts +48 -19
  325. package/src/urls/response-types.ts +25 -22
  326. package/src/urls/type-extraction.ts +58 -139
  327. package/src/urls/urls-function.ts +1 -18
  328. package/src/use-loader.tsx +346 -89
  329. package/src/vite/debug.ts +185 -0
  330. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  331. package/src/vite/discovery/discover-routers.ts +130 -85
  332. package/src/vite/discovery/discovery-errors.ts +194 -0
  333. package/src/vite/discovery/gate-state.ts +171 -0
  334. package/src/vite/discovery/prerender-collection.ts +192 -99
  335. package/src/vite/discovery/route-types-writer.ts +40 -84
  336. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  337. package/src/vite/discovery/state.ts +51 -6
  338. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  339. package/src/vite/index.ts +8 -0
  340. package/src/vite/plugin-types.ts +187 -69
  341. package/src/vite/plugins/cjs-to-esm.ts +8 -18
  342. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  343. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  344. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  345. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  346. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  347. package/src/vite/plugins/expose-action-id.ts +49 -98
  348. package/src/vite/plugins/expose-id-utils.ts +11 -50
  349. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  350. package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
  351. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  352. package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
  353. package/src/vite/plugins/expose-internal-ids.ts +554 -317
  354. package/src/vite/plugins/performance-tracks.ts +89 -0
  355. package/src/vite/plugins/refresh-cmd.ts +89 -27
  356. package/src/vite/plugins/use-cache-transform.ts +73 -83
  357. package/src/vite/plugins/vercel-output.ts +258 -0
  358. package/src/vite/plugins/version-injector.ts +21 -25
  359. package/src/vite/plugins/version-plugin.ts +41 -20
  360. package/src/vite/plugins/virtual-entries.ts +2 -17
  361. package/src/vite/rango.ts +257 -289
  362. package/src/vite/router-discovery.ts +930 -140
  363. package/src/vite/utils/ast-handler-extract.ts +15 -31
  364. package/src/vite/utils/banner.ts +4 -4
  365. package/src/vite/utils/bundle-analysis.ts +10 -15
  366. package/src/vite/utils/client-chunks.ts +184 -0
  367. package/src/vite/utils/forward-user-plugins.ts +171 -0
  368. package/src/vite/utils/manifest-utils.ts +4 -59
  369. package/src/vite/utils/package-resolution.ts +20 -52
  370. package/src/vite/utils/prerender-utils.ts +27 -29
  371. package/src/vite/utils/shared-utils.ts +92 -42
  372. package/src/browser/action-response-classifier.ts +0 -99
  373. package/src/browser/react/use-client-cache.ts +0 -58
  374. package/src/browser/shallow.ts +0 -40
  375. package/src/handles/index.ts +0 -7
  376. package/src/router/middleware-cookies.ts +0 -55
@@ -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,28 +241,78 @@ 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.
240
253
 
241
- Look up response type from the merged route map by URL pattern:
254
+ ### Rango.PathResponse (global lookup by URL pattern or concrete path)
255
+
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
+ ```
272
+
273
+ With that in place, look up the response type by URL pattern (ambient, no import):
245
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:
249
287
 
250
- // RSC routes return ResponseEnvelope<never>
251
- type Home = PathResponse<"/">;
252
- // = ResponseEnvelope<never>
288
+ ```typescript
289
+ import { href } from "@rangojs/router/client";
290
+
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
@@ -361,15 +422,17 @@ export const urlpatterns = urls(({ path, include }) => [
361
422
 
362
423
  ```typescript
363
424
  import type { RouteResponse } from "@rangojs/router";
364
- import type { PathResponse, ParamsFor } from "@rangojs/router/client";
425
+ import type { ParamsFor } from "@rangojs/router/client";
365
426
 
366
- // Scoped (before mount) -- use the module directly
427
+ // Scoped (before mount) -- use the module directly, no global wiring needed
367
428
  type Stats = RouteResponse<typeof blogApiPatterns, "stats">;
368
- // = ResponseEnvelope<{ views: number; visitors: number }>
429
+ // = { views: number; visitors: number }
369
430
 
370
- // After mounting -- names get prefixed
371
- type BlogStats = PathResponse<"/blog/api/stats">;
372
- // = ResponseEnvelope<{ views: number; visitors: number }>
431
+ // After mounting -- names get prefixed.
432
+ // Rango.PathResponse needs `RegisteredRoutes extends typeof router.routeMap` (see above),
433
+ // otherwise it resolves to never.
434
+ type BlogStats = Rango.PathResponse<"/blog/api/stats">;
435
+ // = { views: number; visitors: number }
373
436
 
374
437
  // Params work through nested includes
375
438
  type LikesParams = ParamsFor<"blog.api.likes">;
@@ -400,12 +463,24 @@ path(
400
463
  Multiple response types can share the same URL pattern. See `/mime-routes` for the
401
464
  full content negotiation API (Accept header matching, Vary: Accept, multi-variant routes).
402
465
 
466
+ ## Long-Lived Responses (SSE / WebSocket)
467
+
468
+ For Server-Sent Events (`path.stream`) and WebSocket upgrades (`path.any`
469
+ returning a 101 / `webSocket` Response), see `/streams-and-websockets`.
470
+ Upgrade responses flow through without reconstruction; `Vary` and
471
+ `Server-Timing` are skipped, and stub headers are applied in place on a
472
+ best-effort basis.
473
+
403
474
  ## How It Works
404
475
 
405
476
  1. `path.json()` tags the route at the trie level with a MIME type
406
477
  2. `coreRequestHandler()` checks the tag before the RSC pipeline
407
478
  3. Tagged routes short-circuit: handler runs, Response is returned directly
408
- 4. JSON routes auto-wrap return values in `{ data }` / `{ error }` envelope
479
+ 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
480
  5. Client-side navigation to response routes gets `X-RSC-Reload` header, triggering hard navigation
410
481
  6. Response types flow through `_responses` phantom type on `UrlPatterns`, propagated by `include()`
411
482
  7. When multiple routes share a URL pattern, the trie merges them for content negotiation (see `/mime-routes`)
483
+
484
+ ## Consuming response routes
485
+
486
+ 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.
@@ -33,6 +33,26 @@ 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
+
36
56
  ## Route Handler Patterns
37
57
 
38
58
  ### Component Function
@@ -181,16 +201,57 @@ String keys still work (`ctx.set("key", value)` / `ctx.get("key")`), but
181
201
  Only route handlers and middleware can call `ctx.set()`. Layouts, parallels,
182
202
  and intercepts can only read via `ctx.get()`.
183
203
 
204
+ #### Non-cacheable context variables
205
+
206
+ Mark a var as non-cacheable when it holds inherently request-specific data
207
+ (sessions, auth tokens, per-request IDs). There are two ways:
208
+
209
+ ```typescript
210
+ // Var-level: every value written to this var is non-cacheable
211
+ const Session = createVar<SessionData>({ cache: false });
212
+
213
+ // Write-level: escalate a normally-cacheable var for this specific write
214
+ const Theme = createVar<string>();
215
+ ctx.set(Theme, userTheme, { cache: false });
216
+ ```
217
+
218
+ "Least cacheable wins" — if either the var definition or the write site says
219
+ `cache: false`, the value is non-cacheable.
220
+
221
+ Reading a non-cacheable var inside `cache()` or `"use cache"` throws at
222
+ runtime. This prevents request-specific data from leaking into cached output:
223
+
224
+ ```typescript
225
+ // This throws — Session is non-cacheable
226
+ async function CachedWidget(ctx) {
227
+ "use cache";
228
+ const session = ctx.get(Session); // Error: non-cacheable var read inside cache scope
229
+ return <Widget />;
230
+ }
231
+ ```
232
+
233
+ Cacheable vars (the default) can be read freely inside cache scopes.
234
+
184
235
  ### Revalidation Contracts for Handler Data
185
236
 
237
+ > **Scope: `revalidate()` is a partial-render concern, not a cache concern.**
238
+ > It decides whether this segment re-runs and streams to the client on a
239
+ > navigation or action — never whether a cached value is stale. The cache
240
+ > decides hit/miss/ttl/swr independently and never reads `revalidate()`. See
241
+ > `/cache-guide` → "Two axes" and `/rango` → "The shape of rango".
242
+
186
243
  Handler-first guarantees apply within a single full render pass. For partial
187
244
  action revalidation, define named revalidation contracts and reuse them on both
188
245
  the producer route and the consumer child segments.
189
246
 
190
247
  ```typescript
191
248
  // revalidation-contracts.ts
192
- export const revalidateCheckoutData = ({ actionId }) =>
193
- actionId?.includes("src/actions/checkout.ts#") ?? false;
249
+ import * as CheckoutActions from "./actions/checkout";
250
+
251
+ // Defer (|| undefined), not ?? false: a hard `false` short-circuits the chain,
252
+ // so when the same segment composes multiple contracts the later ones never run.
253
+ export const revalidateCheckoutData = (ctx) =>
254
+ ctx.isAction(CheckoutActions) || undefined;
194
255
 
195
256
  path("/checkout", CheckoutPage, { name: "checkout" }, () => [
196
257
  revalidate(revalidateCheckoutData), // producer (route handler) reruns
@@ -219,9 +280,6 @@ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
219
280
  ]);
220
281
  ```
221
282
 
222
- For scope/revalidation guarantees and non-guarantees, see:
223
- [docs/execution-model.md](../../docs/internal/execution-model.md)
224
-
225
283
  ## Redirects
226
284
 
227
285
  ### Basic redirect
@@ -238,6 +296,12 @@ path("/old-page", () => redirect("/new-page"), { name: "oldPage" });
238
296
  path("/moved", () => redirect("/new-location", 301), { name: "moved" });
239
297
  ```
240
298
 
299
+ > **Redirecting from a route with `loading()`:** an `async` handler that returns
300
+ > a `Response`/`redirect()` on a route that also declares `loading()` is streamed,
301
+ > so the redirect is rendered into the RSC stream instead of becoming an HTTP
302
+ > redirect. Issue the redirect from `middleware`, a loader, or a **synchronous**
303
+ > handler return instead. (Dev logs a warning if this is hit.)
304
+
241
305
  ### Redirect with location state
242
306
 
243
307
  Carry typed state through redirects (e.g. flash messages):
@@ -352,6 +416,34 @@ urls(({ path, layout }) => [
352
416
  ])
353
417
  ```
354
418
 
419
+ ## View Transitions
420
+
421
+ 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.
422
+
423
+ ## Handler-attached `.use`
424
+
425
+ 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.
426
+
427
+ ```typescript
428
+ const ProductPage: Handler<"/product/:slug"> = async (ctx) => {
429
+ const product = await ctx.use(ProductLoader);
430
+ return <ProductView product={product} />;
431
+ };
432
+ ProductPage.use = () => [
433
+ loader(ProductLoader),
434
+ loading(<ProductSkeleton />),
435
+ middleware(async (ctx, next) => {
436
+ await next();
437
+ ctx.header("Cache-Control", "private, max-age=60");
438
+ }),
439
+ ];
440
+
441
+ // Mount site has no per-page wiring — defaults travel with the handler.
442
+ path("/product/:slug", ProductPage, { name: "product" });
443
+ ```
444
+
445
+ Explicit `use()` at the mount site merges with `handler.use` (handler defaults first, explicit second). See [skills/handler-use](../handler-use/SKILL.md) for the merge order, allowed item types per mount site, and override semantics.
446
+
355
447
  ## Complete Example
356
448
 
357
449
  ```typescript
@@ -71,23 +71,28 @@ urls(
71
71
  ## Router Options
72
72
 
73
73
  ```typescript
74
- interface RSCRouterOptions<TEnv> {
74
+ interface RangoOptions<TEnv> {
75
75
  // URL patterns from urls() function
76
76
  urls: UrlPatterns;
77
77
 
78
78
  // Document component wrapping entire app
79
79
  document?: ComponentType<{ children: ReactNode }>;
80
80
 
81
+ // URL prefix for sub-path deployments (e.g. "/admin")
82
+ // All routes, reverse(), href(), Link, redirect(), and router.use()
83
+ // patterns are automatically prefixed. Route names stay unprefixed.
84
+ basename?: string;
85
+
81
86
  // Enable per-request performance timeline (console waterfall + Server-Timing header)
82
87
  debugPerformance?: boolean;
83
88
 
84
89
  // Default error boundary
85
90
  defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler;
86
91
 
87
- // Default not-found boundary
92
+ // Default not-found boundary for notFound() thrown in handlers/loaders
88
93
  defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler;
89
94
 
90
- // Component for 404 routes
95
+ // Component for 404 (no route match, or notFound() without a boundary)
91
96
  notFound?: ReactNode | ((props: { pathname: string }) => ReactNode);
92
97
 
93
98
  // Error logging callback
@@ -124,6 +129,36 @@ interface RSCRouterOptions<TEnv> {
124
129
  }
125
130
  ```
126
131
 
132
+ ## Basename (Sub-Path Deployment)
133
+
134
+ When your app is served under a sub-path (e.g. `/admin` or `/v2`), set `basename`:
135
+
136
+ ```typescript
137
+ const router = createRouter({
138
+ basename: "/admin",
139
+ document: Document,
140
+ }).routes(({ path, include }) => [
141
+ path("/", Dashboard, { name: "home" }), // matches /admin
142
+ path("/users", Users, { name: "users" }), // matches /admin/users
143
+ include("/api", apiPatterns, { name: "api" }), // matches /admin/api/*
144
+ ]);
145
+
146
+ router.reverse("home"); // "/admin"
147
+ router.reverse("users"); // "/admin/users"
148
+ ```
149
+
150
+ Router-owned APIs are basename-aware:
151
+
152
+ - `reverse()` returns prefixed paths
153
+ - `<Link to="/users">` renders `<a href="/admin/users">`
154
+ - `redirect("/login")` redirects to `"/admin/login"`
155
+ - `router.use("/users/*", mw)` matches `/admin/users/*`
156
+ - `useRouter().push("/users")` navigates to `/admin/users`
157
+ - Route names stay unprefixed (`"home"`, not `"admin.home"`)
158
+
159
+ Note: `href()` is a raw path helper and does **not** auto-prefix with basename.
160
+ Use `reverse()` or `<Link>` for basename-aware URLs.
161
+
127
162
  ## Using the Request Handler
128
163
 
129
164
  The router provides a `fetch` method to handle RSC requests:
@@ -290,6 +325,56 @@ const router = createRouter({
290
325
  export default router;
291
326
  ```
292
327
 
328
+ ## Not Found Handling
329
+
330
+ Two distinct 404 scenarios:
331
+
332
+ **1. No route matches the URL** — the router renders the `notFound` component from `createRouter()` config. This is automatic.
333
+
334
+ **2. A handler/loader calls `notFound()`** — signals that the route matched but the data doesn't exist (e.g., invalid product ID).
335
+
336
+ ```typescript
337
+ import { notFound } from "@rangojs/router";
338
+
339
+ // In a handler or loader
340
+ path("/product/:slug", async (ctx) => {
341
+ const product = await db.getProduct(ctx.params.slug);
342
+ if (!product) notFound("Product not found");
343
+ return <ProductPage product={product} />;
344
+ });
345
+ ```
346
+
347
+ ### Fallback chain for `notFound()`
348
+
349
+ When `notFound()` is thrown, the router looks for a fallback in this order:
350
+
351
+ 1. **`notFoundBoundary()`** — nearest boundary in the route tree (route-level)
352
+ 2. **`defaultNotFoundBoundary`** — from `createRouter()` config (app-level)
353
+ 3. **`notFound`** — from `createRouter()` config (same component used for no-route-match)
354
+ 4. **Default `<h1>Not Found</h1>`** — built-in fallback
355
+
356
+ All cases set HTTP 404 status.
357
+
358
+ ### notFoundBoundary
359
+
360
+ Wrap routes with `notFoundBoundary()` for route-specific not-found UI:
361
+
362
+ ```typescript
363
+ urls(({ path, layout }) => [
364
+ layout(ShopLayout, () => [
365
+ notFoundBoundary(({ notFound: info }) => (
366
+ <div>
367
+ <h1>Not Found</h1>
368
+ <p>{info.message}</p>
369
+ </div>
370
+ )),
371
+ path("/product/:slug", ProductPage),
372
+ ]),
373
+ ]);
374
+ ```
375
+
376
+ `notFoundBoundary` receives `{ notFound: NotFoundInfo }` where `NotFoundInfo` contains `message`, `segmentId`, `segmentType`, and `pathname`.
377
+
293
378
  ## Including Sub-patterns
294
379
 
295
380
  ```typescript
@@ -320,7 +405,7 @@ interface AppBindings {
320
405
  KV: KVNamespace;
321
406
  }
322
407
 
323
- // Variables declared via module augmentation
408
+ // Variables declared via global namespace augmentation
324
409
  interface AppVariables {
325
410
  user?: { id: string; name: string };
326
411
  }
@@ -332,7 +417,7 @@ const router = createRouter<AppBindings>({
332
417
 
333
418
  // Register types globally for implicit typing
334
419
  declare global {
335
- namespace RSCRouter {
420
+ namespace Rango {
336
421
  interface Env extends AppBindings {}
337
422
  interface Vars extends AppVariables {}
338
423
  }