@rangojs/router 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +254 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  4. package/dist/types/browser/partial-update.d.ts +11 -0
  5. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  6. package/dist/types/browser/rsc-router.d.ts +1 -1
  7. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  8. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  9. package/dist/types/cache/cache-scope.d.ts +34 -5
  10. package/dist/types/cache/handle-capture.d.ts +41 -3
  11. package/dist/types/cache/handle-snapshot.d.ts +53 -13
  12. package/dist/types/cache/segment-codec.d.ts +10 -3
  13. package/dist/types/cache/taint.d.ts +1 -1
  14. package/dist/types/cache/types.d.ts +15 -3
  15. package/dist/types/client.d.ts +4 -4
  16. package/dist/types/client.rsc.d.ts +4 -5
  17. package/dist/types/components/DefaultDocument.d.ts +5 -2
  18. package/dist/types/context-var.d.ts +6 -5
  19. package/dist/types/escape-script.d.ts +1 -1
  20. package/dist/types/handles/MetaTags.d.ts +5 -4
  21. package/dist/types/handles/Scripts.d.ts +8 -7
  22. package/dist/types/handles/meta.d.ts +2 -2
  23. package/dist/types/handles/script.d.ts +17 -16
  24. package/dist/types/href-client.d.ts +1 -1
  25. package/dist/types/html.d.ts +12 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/index.rsc.d.ts +3 -2
  28. package/dist/types/loader-store.d.ts +7 -4
  29. package/dist/types/reverse.d.ts +36 -4
  30. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  31. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  32. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  33. package/dist/types/route-types.d.ts +1 -1
  34. package/dist/types/router/debug-manifest.d.ts +3 -2
  35. package/dist/types/router/error-handling.d.ts +20 -0
  36. package/dist/types/router/handler-context.d.ts +6 -0
  37. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  38. package/dist/types/router/middleware-types.d.ts +3 -2
  39. package/dist/types/router/router-context.d.ts +3 -1
  40. package/dist/types/router/router-interfaces.d.ts +24 -2
  41. package/dist/types/router/router-options.d.ts +8 -5
  42. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  43. package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
  44. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  45. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
  46. package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
  47. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  48. package/dist/types/router/segment-wrappers.d.ts +1 -1
  49. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  50. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  51. package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
  52. package/dist/types/rsc/shell-capture.d.ts +5 -4
  53. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  54. package/dist/types/rsc/types.d.ts +10 -0
  55. package/dist/types/server/context.d.ts +33 -3
  56. package/dist/types/server/handle-store.d.ts +26 -2
  57. package/dist/types/server/request-context.d.ts +24 -21
  58. package/dist/types/ssr/index.d.ts +15 -0
  59. package/dist/types/static-handler.d.ts +4 -1
  60. package/dist/types/testing/generated-routes.d.ts +2 -2
  61. package/dist/types/testing/internal/context.d.ts +1 -1
  62. package/dist/types/testing/render-route.d.ts +32 -6
  63. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  64. package/dist/types/testing/vitest.d.ts +35 -1
  65. package/dist/types/theme/ThemeScript.d.ts +3 -3
  66. package/dist/types/types/cache-types.d.ts +14 -10
  67. package/dist/types/types/handler-context.d.ts +14 -0
  68. package/dist/types/types/loader-types.d.ts +8 -6
  69. package/dist/types/urls/response-types.d.ts +3 -12
  70. package/dist/types/use-loader.d.ts +12 -3
  71. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  72. package/dist/types/vite/rango.d.ts +1 -1
  73. package/dist/vite/index.js +1381 -1346
  74. package/package.json +2 -2
  75. package/skills/api-client/SKILL.md +22 -10
  76. package/skills/breadcrumbs/SKILL.md +65 -22
  77. package/skills/bundle-analysis/SKILL.md +22 -22
  78. package/skills/cache-guide/SKILL.md +207 -97
  79. package/skills/caching/SKILL.md +295 -93
  80. package/skills/catalog.json +16 -16
  81. package/skills/client-urls/SKILL.md +53 -31
  82. package/skills/cloudflare/SKILL.md +72 -20
  83. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  84. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  85. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  86. package/skills/comparison/SKILL.md +5 -0
  87. package/skills/comparison/references/framework-comparison.md +39 -33
  88. package/skills/composability/SKILL.md +38 -29
  89. package/skills/css/SKILL.md +10 -13
  90. package/skills/debug-manifest/SKILL.md +74 -31
  91. package/skills/defer-hydration/SKILL.md +2 -1
  92. package/skills/deployment-caching/SKILL.md +6 -1
  93. package/skills/document-cache/SKILL.md +92 -28
  94. package/skills/fonts/SKILL.md +16 -8
  95. package/skills/handler-use/SKILL.md +53 -17
  96. package/skills/hooks/SKILL.md +28 -20
  97. package/skills/hooks/data.md +23 -3
  98. package/skills/hooks/handle-and-actions.md +31 -16
  99. package/skills/hooks/navigation.md +39 -10
  100. package/skills/hooks/outlets.md +13 -6
  101. package/skills/hooks/state.md +37 -15
  102. package/skills/hooks/urls.md +7 -5
  103. package/skills/host-router/SKILL.md +64 -12
  104. package/skills/i18n/SKILL.md +60 -34
  105. package/skills/intercept/SKILL.md +161 -26
  106. package/skills/layout/SKILL.md +106 -50
  107. package/skills/links/SKILL.md +51 -19
  108. package/skills/loader/SKILL.md +242 -106
  109. package/skills/middleware/SKILL.md +119 -23
  110. package/skills/migrate-nextjs/SKILL.md +100 -45
  111. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  112. package/skills/migrate-react-router/SKILL.md +49 -38
  113. package/skills/migrate-react-router/component-migration.md +25 -6
  114. package/skills/migrate-react-router/data-and-actions.md +18 -5
  115. package/skills/migrate-react-router/route-mapping.md +12 -6
  116. package/skills/mime-routes/SKILL.md +12 -7
  117. package/skills/observability/SKILL.md +28 -14
  118. package/skills/parallel/SKILL.md +75 -39
  119. package/skills/ppr/SKILL.md +345 -150
  120. package/skills/prerender/SKILL.md +189 -100
  121. package/skills/rango/SKILL.md +65 -34
  122. package/skills/react-compiler/SKILL.md +6 -5
  123. package/skills/response-routes/SKILL.md +133 -25
  124. package/skills/route/SKILL.md +165 -60
  125. package/skills/router-setup/SKILL.md +418 -240
  126. package/skills/scripts/SKILL.md +31 -20
  127. package/skills/server-actions/SKILL.md +220 -59
  128. package/skills/shell-manifest/SKILL.md +12 -7
  129. package/skills/streams-and-websockets/SKILL.md +32 -14
  130. package/skills/tailwind/SKILL.md +37 -33
  131. package/skills/testing/SKILL.md +68 -32
  132. package/skills/testing/cache-prerender.md +55 -29
  133. package/skills/testing/client-components.md +59 -6
  134. package/skills/testing/e2e-parity.md +12 -2
  135. package/skills/testing/flight.md +19 -11
  136. package/skills/testing/handles.md +8 -5
  137. package/skills/testing/loader.md +8 -5
  138. package/skills/testing/middleware.md +8 -3
  139. package/skills/testing/predicates.md +120 -0
  140. package/skills/testing/render-handler.md +70 -22
  141. package/skills/testing/response-routes.md +11 -4
  142. package/skills/testing/reverse-and-types.md +20 -14
  143. package/skills/testing/server-actions.md +12 -3
  144. package/skills/testing/server-tree.md +9 -0
  145. package/skills/testing/setup.md +9 -5
  146. package/skills/theme/SKILL.md +106 -29
  147. package/skills/typesafety/SKILL.md +20 -9
  148. package/skills/typesafety/env-and-bindings.md +126 -47
  149. package/skills/typesafety/generated-files-and-cli.md +26 -14
  150. package/skills/typesafety/params-and-search.md +44 -19
  151. package/skills/typesafety/route-types.md +50 -14
  152. package/skills/use-cache/SKILL.md +200 -82
  153. package/skills/vercel/SKILL.md +21 -11
  154. package/skills/view-transitions/SKILL.md +41 -20
  155. package/src/__augment-tests__/augmented.check.ts +26 -1
  156. package/src/browser/index.ts +2 -2
  157. package/src/browser/navigation-bridge.ts +3 -4
  158. package/src/browser/navigation-transaction.ts +22 -2
  159. package/src/browser/partial-update.ts +35 -25
  160. package/src/browser/react/ScrollRestoration.tsx +6 -6
  161. package/src/browser/rsc-router.tsx +1 -1
  162. package/src/browser/scroll-restoration.ts +53 -6
  163. package/src/cache/cache-exec-scope.ts +37 -4
  164. package/src/cache/cache-key-utils.ts +1 -6
  165. package/src/cache/cache-runtime.ts +230 -61
  166. package/src/cache/cache-scope.ts +76 -12
  167. package/src/cache/document-cache.ts +15 -0
  168. package/src/cache/handle-capture.ts +90 -10
  169. package/src/cache/handle-snapshot.ts +132 -28
  170. package/src/cache/segment-codec.ts +26 -7
  171. package/src/cache/tag-invalidation.ts +4 -3
  172. package/src/cache/taint.ts +1 -1
  173. package/src/cache/types.ts +16 -3
  174. package/src/client.rsc.tsx +10 -9
  175. package/src/client.tsx +5 -4
  176. package/src/components/DefaultDocument.tsx +7 -2
  177. package/src/context-var.ts +6 -5
  178. package/src/escape-script.ts +1 -1
  179. package/src/handles/MetaTags.tsx +13 -11
  180. package/src/handles/Scripts.tsx +11 -10
  181. package/src/handles/meta.ts +2 -2
  182. package/src/handles/script.ts +17 -16
  183. package/src/href-client.ts +1 -1
  184. package/src/html.ts +12 -0
  185. package/src/index.rsc.ts +5 -0
  186. package/src/index.ts +1 -0
  187. package/src/loader-store.ts +7 -4
  188. package/src/prerender/build-shell-capture.ts +8 -11
  189. package/src/reverse.ts +44 -4
  190. package/src/route-definition/dsl-helpers.ts +117 -30
  191. package/src/route-definition/helpers-types.ts +33 -9
  192. package/src/route-definition/resolve-handler-use.ts +121 -6
  193. package/src/route-types.ts +0 -3
  194. package/src/router/debug-manifest.ts +10 -2
  195. package/src/router/error-handling.ts +51 -15
  196. package/src/router/handler-context.ts +34 -25
  197. package/src/router/intercept-resolution.ts +125 -39
  198. package/src/router/loader-resolution.ts +56 -11
  199. package/src/router/match-api.ts +12 -17
  200. package/src/router/match-middleware/background-revalidation.ts +161 -96
  201. package/src/router/match-middleware/cache-lookup.ts +93 -10
  202. package/src/router/match-middleware/cache-store.ts +17 -90
  203. package/src/router/middleware-types.ts +3 -5
  204. package/src/router/middleware.ts +52 -9
  205. package/src/router/prerender-match.ts +53 -19
  206. package/src/router/route-snapshot.ts +11 -1
  207. package/src/router/router-context.ts +2 -0
  208. package/src/router/router-interfaces.ts +25 -2
  209. package/src/router/router-options.ts +8 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -12
  211. package/src/router/segment-resolution/loader-cache.ts +205 -45
  212. package/src/router/segment-resolution/loader-mask.ts +12 -22
  213. package/src/router/segment-resolution/loader-snapshot.ts +6 -6
  214. package/src/router/segment-resolution/mask-nested.ts +7 -7
  215. package/src/router/segment-resolution/revalidation.ts +34 -6
  216. package/src/router/segment-wrappers.ts +4 -0
  217. package/src/rsc/render-pipeline.ts +3 -0
  218. package/src/rsc/response-route-handler.ts +11 -3
  219. package/src/rsc/rsc-rendering.ts +8 -45
  220. package/src/rsc/shell-capture-constants.ts +13 -0
  221. package/src/rsc/shell-capture.ts +111 -90
  222. package/src/rsc/ssr-setup.ts +14 -8
  223. package/src/rsc/types.ts +11 -0
  224. package/src/server/context.ts +88 -11
  225. package/src/server/cookie-store.ts +5 -4
  226. package/src/server/handle-store.ts +232 -7
  227. package/src/server/request-context.ts +35 -30
  228. package/src/ssr/index.tsx +40 -7
  229. package/src/static-handler.ts +4 -1
  230. package/src/testing/flight-runtime.d.ts +21 -0
  231. package/src/testing/generated-routes.ts +2 -2
  232. package/src/testing/internal/context.ts +1 -1
  233. package/src/testing/internal/flight-client-globals.ts +7 -5
  234. package/src/testing/render-route.tsx +85 -24
  235. package/src/testing/run-loader.ts +5 -1
  236. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  237. package/src/testing/vitest.ts +62 -1
  238. package/src/theme/ThemeScript.tsx +3 -3
  239. package/src/types/cache-types.ts +14 -10
  240. package/src/types/handler-context.ts +14 -0
  241. package/src/types/loader-types.ts +8 -6
  242. package/src/urls/response-types.ts +3 -23
  243. package/src/use-loader.tsx +12 -3
  244. package/src/vite/plugins/use-cache-transform.ts +118 -74
  245. package/src/vite/rango.ts +7 -3
@@ -17,19 +17,19 @@
17
17
  },
18
18
  {
19
19
  "name": "bundle-analysis",
20
- "description": "Audit a Rango app's production bundle for server-side code leaking into the client, dev/prod React duplication, oversized chunks, and inefficient client-reference grouping. Use when investigating bundle size growth, before a production deploy, or when the client/SSR/RSC output suddenly balloons.",
20
+ "description": "Audit a Rango app's production bundle for server-side code leaking into the client, dev/prod React duplication, oversized chunks, and poor client-chunk grouping. Use when investigating bundle size growth, before a production deploy, or when the client/SSR/RSC output suddenly balloons.",
21
21
  "argumentHint": "<app-dir>",
22
22
  "path": "skills/bundle-analysis/SKILL.md"
23
23
  },
24
24
  {
25
25
  "name": "cache-guide",
26
- "description": "When to use cache() DSL vs \"use cache\" directive — key differences and decision guide. Use when unsure which caching mechanism fits a given problem, comparing route/segment caching, function-level caching, and document-level caching, or asking \"which cache API should I use\".",
26
+ "description": "Decision guide for Rango's cache layers — \"use cache\", loader cache(), segment cache(), Prerender/Static, the ppr HTML shell, and document/CDN caching — with the cache() vs \"use cache\" comparison (keys, guards, SWR, nesting). Use when unsure which caching mechanism fits a problem, comparing layers, or asking \"which cache API should I use\".",
27
27
  "argumentHint": "",
28
28
  "path": "skills/cache-guide/SKILL.md"
29
29
  },
30
30
  {
31
31
  "name": "caching",
32
- "description": "Configure route/segment-subtree caching with memory, Cloudflare KV, or Vercel cache stores in @rangojs/router. Use when responses should be cached or revalidated, data is stale or not updating after code changes, or you are wiring up a cache store.",
32
+ "description": "Configure segment caching with the cache() DSL in @rangojs/router — TTL/SWR, tags and updateTag/revalidateTag, loader-level caching, and the Memory, Cloudflare (Cache API + optional KV), and Vercel Runtime Cache stores. Use when responses should be cached or revalidated, data is stale or not updating after changes, or you are wiring up a cache store.",
33
33
  "argumentHint": "[setup]",
34
34
  "path": "skills/caching/SKILL.md"
35
35
  },
@@ -53,7 +53,7 @@
53
53
  },
54
54
  {
55
55
  "name": "composability",
56
- "description": "Reusable composition patterns with globally importable route helpers in @rangojs/router. Use when sharing loaders, middleware, or handler logic across multiple route files, or avoiding copy-pasting the same route setup everywhere.",
56
+ "description": "Reusable composition patterns with globally importable route helpers and include() modules in @rangojs/router. Use when sharing loaders, middleware, or caching config across multiple route files, splitting routes into eager or code-split include() groups, or avoiding copy-pasting the same route setup everywhere.",
57
57
  "argumentHint": "pattern-name",
58
58
  "path": "skills/composability/SKILL.md"
59
59
  },
@@ -65,7 +65,7 @@
65
65
  },
66
66
  {
67
67
  "name": "debug-manifest",
68
- "description": "Debug and inspect route manifest structure. Use when routes aren't matching as expected, you need to see the generated route tree, or a path resolves to the wrong handler.",
68
+ "description": "Inspect the router's route manifest (route/layout tree, shortCodes, parent links, loader/middleware flags) and diff it between versions. Use when routes aren't matching as expected, you need to see the generated route tree, or a path resolves to the wrong handler.",
69
69
  "argumentHint": "",
70
70
  "path": "skills/debug-manifest/SKILL.md"
71
71
  },
@@ -137,13 +137,13 @@
137
137
  },
138
138
  {
139
139
  "name": "loader",
140
- "description": "Define data loaders for fetching data in routes with createLoader. Use when pages need per-request data that stays fresh, data should stream while the page renders, client components need reactive server data, a loader should throw notFound()/redirect(), set page meta/breadcrumbs from loader data (handle writes), or loader data must be guaranteed in the SSR'd document (ssr:false).",
140
+ "description": "Define data loaders with createLoader and register them on routes with loader(). Use when a route needs per-request data that stays fresh and streams while the page renders, a client component reads server data with useLoader, a loader should re-run after specific actions (revalidate), be cached (cache), be callable from the client (fetchable loaders), throw notFound()/redirect(), write page meta/breadcrumbs handles, or must be settled in the SSR'd document (ssr:false).",
141
141
  "argumentHint": "[loader]",
142
142
  "path": "skills/loader/SKILL.md"
143
143
  },
144
144
  {
145
145
  "name": "middleware",
146
- "description": "Define middleware for authentication, logging, and request processing in @rangojs/router. Use when gating routes behind auth checks, logging requests, or running shared logic before a handler runs.",
146
+ "description": "Define global (router.use) and route (middleware()) middleware in @rangojs/router — auth gates, logging, response headers, context variables, redirects. Use when gating routes behind auth checks, logging requests, setting headers or ctx variables before a handler runs, or deciding whether middleware wraps server actions.",
147
147
  "argumentHint": "[middleware-name]",
148
148
  "path": "skills/middleware/SKILL.md"
149
149
  },
@@ -173,7 +173,7 @@
173
173
  },
174
174
  {
175
175
  "name": "parallel",
176
- "description": "Define parallel routes for multi-column layouts, sidebars, and modal slots in @rangojs/router. Use when a layout needs multiple independently-loading regions (e.g. a sidebar and main panel), or rendering more than one route segment at the same URL.",
176
+ "description": "Define parallel routes (named slots) for multi-column layouts, sidebars, and UI-less metadata slots in @rangojs/router. Use when a layout needs multiple independently-loading regions (e.g. a sidebar and main panel), or rendering more than one route segment at the same URL.",
177
177
  "argumentHint": "[@slot-name]",
178
178
  "path": "skills/parallel/SKILL.md"
179
179
  },
@@ -185,7 +185,7 @@
185
185
  },
186
186
  {
187
187
  "name": "prerender",
188
- "description": "Pre-render route segments at build time with Prerender and Passthrough live fallback. Use when a page's content is mostly static and shouldn't render on every request, speeding up cold responses, or deciding which routes to prerender vs render live.",
188
+ "description": "Pre-render route segments at build time with Prerender (with getParams), Passthrough live fallback, Static segments, and Skip. Use when a page's content is mostly static and shouldn't render on every request, speeding up cold responses, reading build-only data (files, build env), or deciding which routes to prerender vs render live.",
189
189
  "argumentHint": "[passthrough]",
190
190
  "path": "skills/prerender/SKILL.md"
191
191
  },
@@ -203,19 +203,19 @@
203
203
  },
204
204
  {
205
205
  "name": "response-routes",
206
- "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.",
206
+ "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, asking how to return raw JSON instead of RSC from a route, or protecting a state-changing endpoint from CSRF.",
207
207
  "argumentHint": "[json|text|html|xml|md|image|stream]",
208
208
  "path": "skills/response-routes/SKILL.md"
209
209
  },
210
210
  {
211
211
  "name": "route",
212
- "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.",
212
+ "description": "Define routes with path() in @rangojs/router — URL params, catch-alls, typed search params, the handler context, handler-to-child data, and redirects. Use when creating a new page or route, or asking how to define a URL path and its handler.",
213
213
  "argumentHint": "[pattern]",
214
214
  "path": "skills/route/SKILL.md"
215
215
  },
216
216
  {
217
217
  "name": "router-setup",
218
- "description": "Create and configure the RSC router with createRouter. Use when bootstrapping a new Rango app, or configuring top-level router options like base path, cache store, or environment.",
218
+ "description": "Create and configure a Rango app with createRouter() and the rango() Vite plugin. Use when bootstrapping a new app, wiring vite.config, writing the Document or worker entry, or setting top-level router options (basename, cache store, prefetch, timeouts, origin check, telemetry, SSR streaming).",
219
219
  "argumentHint": "[option]",
220
220
  "path": "skills/router-setup/SKILL.md"
221
221
  },
@@ -227,7 +227,7 @@
227
227
  },
228
228
  {
229
229
  "name": "server-actions",
230
- "description": "Define and call server actions (`\"use server\"`) — forms, useActionState, useOptimistic, validation, error handling, redirects, revalidation. Use when handling a form submission on the server, calling a server function from a client component, or needing optimistic UI after a mutation.",
230
+ "description": "Define and call server actions (`\"use server\"`) — forms, useActionState, useOptimistic, validation, error handling, redirects, post-action revalidation, client-cache control (keepClientCache), and the default CSRF origin check. Use when handling a form submission on the server, calling a server function from a client component, choosing what re-renders after a mutation, needing optimistic UI after a mutation, or asking how actions are protected from CSRF.",
231
231
  "argumentHint": "[action]",
232
232
  "path": "skills/server-actions/SKILL.md"
233
233
  },
@@ -251,19 +251,19 @@
251
251
  },
252
252
  {
253
253
  "name": "testing",
254
- "description": "Test @rangojs/router apps — unit (loaders/middleware/reverse/components), integration (dispatch/Flight), and e2e (dev+prod parity, progressive enhancement). Use when writing a unit test for a loader or middleware, or asking how to test a route end-to-end in dev and production.",
254
+ "description": "Test @rangojs/router apps — unit (loaders, middleware, server actions, handles, transition/clientUrls predicates), client components (renderRoute), RSC/Flight (renderServerTree, renderHandler), integration (dispatch), and e2e (dev+prod parity, progressive enhancement). Use when writing a unit test for a loader, middleware, action, or handler, setting up vitest for a Rango app, or testing a route end-to-end in dev and production.",
255
255
  "argumentHint": "[layer]",
256
256
  "path": "skills/testing/SKILL.md"
257
257
  },
258
258
  {
259
259
  "name": "theme",
260
- "description": "Opt-in theme system with FOUC prevention for light/dark mode. Use when adding a light/dark mode toggle, or the page flashes the wrong theme on load (FOUC).",
260
+ "description": "Opt-in light/dark theme system with FOUC prevention, server-side theme access, and a useTheme() hook. Use when adding a light/dark mode toggle, reading or setting the theme in handlers or middleware, or when the page flashes the wrong theme on load (FOUC).",
261
261
  "argumentHint": "[setup]",
262
262
  "path": "skills/theme/SKILL.md"
263
263
  },
264
264
  {
265
265
  "name": "typesafety",
266
- "description": "Set up type-safe routes, params, and environment types in @rangojs/router. Use when route or search params aren't typed, TypeScript can't infer a loader's return type, or wiring up typed environment bindings.",
266
+ "description": "Set up type-safe route names, params, search params, response payloads, and environment types in @rangojs/router. Use when route or search params aren't typed, ctx.env or ctx.get() is untyped, TypeScript can't infer a loader's return type, or wiring RegisteredRoutes / the generated route map.",
267
267
  "argumentHint": "[setup]",
268
268
  "path": "skills/typesafety/SKILL.md"
269
269
  },
@@ -119,20 +119,27 @@ The async form works without a server wrapper module too:
119
119
  behavior, no startup win, uniform with code-split server groups.
120
120
 
121
121
  Route names compose through the include (`shop.index`, `shop.product`) and
122
- flow into the generated route map, so `href`/`reverse` and `Handler<"...">`
123
- typing work exactly as for server routes (`/typesafety`).
122
+ flow into the generated route map, so `href()`, `ctx.reverse()` and
123
+ `RouteParams<"shop.product">` typing work exactly as for server routes
124
+ (`/typesafety`). `rango generate` also writes a per-module `.gen.ts` for a
125
+ `clientUrls()` module, so components inside the group can link with
126
+ `useReverse(routes)` (`/links`).
124
127
 
125
128
  ## Helpers: what exists inside clientUrls()
126
129
 
127
- | Helper | Notes |
128
- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
129
- | `path()` | Options are `name`, `search`, `trailingSlash`, `ppr` (shell caching — see /ppr skill; loader routes need `loading()` or capture refuses); no response variants |
130
- | `layout()` | Must contain at least one `path()` |
131
- | `loader()` | `loader(Def, use?)` or `loader(Def, { ssr: false }, use?)` — see below |
132
- | `loading()` | Route-level boundary around the optimistic render; inline `<Suspense>` at read sites keeps the destination's chrome visible while only the reads wait |
133
- | `revalidate()` | Valid **inside a loader() use callback only**; runs in the browser |
134
- | `transition()` | Data-only ViewTransition animation config — no `when`; same-route navs in a group already hold previous content without it |
135
- | `intercept()` | Dot-local named target in the SAME definition; use may contain `loader()`/`loading()` |
130
+ | Helper | Notes |
131
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
132
+ | `path()` | Options are `name`, `search`, `trailingSlash`, `ppr` (shell caching — see `/ppr` → The loader lane rule); no response variants. Use callback may contain `loader()`, `loading()`, `transition()` |
133
+ | `layout()` | `layout(Component, () => [...])` — children are required and must contain at least one `path()`; may also hold `loader()`, `loading()`, `intercept()`, nested `layout()` |
134
+ | `loader()` | `loader(Def, use?)` or `loader(Def, { ssr: false }, use?)` — see below |
135
+ | `loading()` | Route-level boundary around the optimistic render; inline `<Suspense>` at read sites keeps the destination's chrome visible while only the reads wait |
136
+ | `revalidate()` | Valid **inside a loader() use callback only**; runs in the browser |
137
+ | `transition()` | Inside a `path()` use callback only (at most one). Data-only ViewTransition config — no `when`; same-route navs in a group already hold previous content without it |
138
+ | `intercept()` | Dot-local named target in the SAME definition; use may contain `loader()`/`loading()` |
139
+
140
+ At the top level of the builder only `path()`, `layout()` and `intercept()`
141
+ are accepted, and the builder must define at least one `path()`. Any helper
142
+ name not in this table throws (`clientUrls() has no route() helper`).
136
143
 
137
144
  `include`, `parallel`, `cache`, `middleware`, `errorBoundary`,
138
145
  `notFoundBoundary` **throw** — and that is a design position, not a gap.
@@ -201,16 +208,23 @@ browser — which is why the matcher (resolving an imported reference's
201
208
  import { addToCart, removeFromCart } from "./actions/cart";
202
209
  import * as CartActions from "./actions/cart";
203
210
 
204
- revalidate(({ isAction }) => isAction()); // any action
205
- revalidate(({ isAction }) => isAction(addToCart)); // one action
206
- revalidate(({ isAction }) => isAction(addToCart, removeFromCart)); // several
207
- revalidate(({ isAction }) => isAction(CartActions)); // import * as
208
- revalidate(({ isAction }) => isAction({ addToCart, removeFromCart })); // object
211
+ // After an action, re-run only for cart actions; on navigation, keep the default.
212
+ revalidate(({ isAction }) => (isAction() ? isAction(CartActions) : undefined));
213
+
214
+ // Matcher forms (each returns a boolean):
215
+ isAction(); // any action
216
+ isAction(addToCart); // one action
217
+ isAction(addToCart, removeFromCart); // several
218
+ isAction(CartActions); // import * as
219
+ isAction({ addToCart, removeFromCart }); // object
209
220
  ```
210
221
 
211
222
  Bare `isAction()` is "is this an action at all?". `actionId` stays as the
212
- string escape hatch. Return `isAction(refs) || undefined` to defer to the
213
- locked default on a non-match (same idiom as the server).
223
+ string escape hatch. A group loader's locked default already re-runs after
224
+ every action, so `isAction(refs) || undefined` changes nothing there; narrow
225
+ with the ternary above instead (same rule as a server-tree loader). A bare
226
+ `isAction(refs)` is a hard `false` on navigation too, so the loader stops
227
+ refetching when params change.
214
228
 
215
229
  Two scars worth copying:
216
230
 
@@ -226,10 +240,12 @@ Two scars worth copying:
226
240
  Everything a DSL loader can do in the server tree works here — the loader body
227
241
  is server code either way.
228
242
 
229
- **Authority signals.** A loader may `throw notFound()` or
243
+ **Authority signals.** A loader may call `notFound()` (it throws) or
230
244
  `throw redirect(url)`:
231
245
 
232
246
  ```ts
247
+ import { createLoader, notFound, redirect } from "@rangojs/router";
248
+
233
249
  export const ProductLoader = createLoader(async (ctx) => {
234
250
  const moved = LEGACY_SLUGS[ctx.params.slug];
235
251
  if (moved) throw redirect(`/shop/product/${moved}`);
@@ -265,7 +281,11 @@ document — `<head>` meta via a handle, or a real 404 status — flag it:
265
281
 
266
282
  ```ts
267
283
  path("/product/:slug", ProductPage, { name: "product" }, () => [
268
- loader(ProductLoader, { ssr: false }, () => [revalidate(productData)]),
284
+ loader(ProductLoader, { ssr: false }, () => [
285
+ revalidate(({ currentParams, nextParams, defaultShouldRevalidate }) =>
286
+ currentParams.slug !== nextParams.slug ? defaultShouldRevalidate : false,
287
+ ),
288
+ ]),
269
289
  loader(RelatedLoader), // untouched: still streams behind its boundary
270
290
  ]),
271
291
  ```
@@ -286,8 +306,8 @@ Constraints:
286
306
  construction. It throws a deadlock error naming the fix.
287
307
  - `intercept()` loaders reject the flag — intercepts render on client
288
308
  navigations only, so a document-render await can never apply.
289
- - It does not change PPR capture behavior: capture renders mask loaders and
290
- skip the await.
309
+ - Under a `ppr` group route the flag is the bake lane, exactly as on a server
310
+ route (`/ppr` → The loader lane rule).
291
311
 
292
312
  Every document load pays the flagged loader's latency before first byte —
293
313
  that is the point, but it is a real cost. Keep flagged loaders fast (existence
@@ -321,15 +341,17 @@ a hard load of the target URL renders the full route.
321
341
  - **Duplicate patterns or names throw** at definition time, per definition.
322
342
  - **`layout()` with no `path()` inside throws** — a client layout exists only
323
343
  to wrap routes.
324
- - **`loading()` vs inline Suspense:** same-route SEARCH navigations
325
- (filters, tabs) hold previous content by default — the canonical commit
326
- runs in a `startTransition` when no new segments mount, and the wrapping
327
- layout sees `useOutlet().pending === true` for the held window; a
328
- route-level `loading()` no longer re-flashes there. PARAM navigations
329
- still remount (fresh skeleton, fresh state) unless the route declares
330
- `transition()` — that opt-in drops the param from the segment key. Inline
331
- `<Suspense>` above each `useLoader` read is still the finer-grained tool
332
- when different reads on one route should wait independently.
344
+ - **`loading()` vs inline Suspense:** same-route navigations hold previous
345
+ content by default, for SEARCH changes (filters, tabs) and PARAM changes
346
+ alike — group route segments are keyed by the group (`cg:<group>`,
347
+ `segment-system.tsx`), not by route id + params, so the mounted instance
348
+ reconciles instead of remounting. The canonical commit runs in a
349
+ `startTransition` when no new segments mount, and the wrapping layout sees
350
+ `useOutlet().pending === true` for the held window; a route-level
351
+ `loading()` does not re-flash there. `transition()` is not needed for the
352
+ hold; it adds the view-transition animation. Inline `<Suspense>` above each
353
+ `useLoader` read is still the finer-grained tool when different reads on
354
+ one route should wait independently.
333
355
  - **Two parallel loaders with equal latency look "SSR'd" together.** Loaders
334
356
  kick off in parallel, so awaiting one (`ssr: false`) gives
335
357
  same-or-faster siblings time to settle coincidentally. Do not read "it was
@@ -5,30 +5,36 @@ description: Deploy and operate @rangojs/router on Cloudflare Workers with the C
5
5
 
6
6
  # Cloudflare Workers deployment
7
7
 
8
+ This skill covers running a Rango app on Cloudflare Workers: Vite and Wrangler
9
+ config, the Worker entry, typed bindings, caching, and deployment. The
10
+ references cover D1/KV setup, streaming and deploy diagnostics, and webhooks.
11
+ For router options in general see `/router-setup`.
12
+
8
13
  Use the Cloudflare Vite plugin as the runtime bridge. It runs the real Worker
9
14
  entry in workerd during `vite dev` and `vite preview`, supplies bindings, and
10
- emits the deployable Worker configuration during `vite build`.
15
+ emits the deployable Worker configuration during `vite build`. Unlike the Node
16
+ and Vercel presets, you own the Worker entry.
11
17
 
12
18
  ## Install and configure
13
19
 
14
20
  ```bash
15
21
  pnpm add @rangojs/router react react-dom
16
- pnpm add -D vite @vitejs/plugin-react @cloudflare/vite-plugin \
17
- @cloudflare/workers-types wrangler typescript
22
+ pnpm add -D vite @cloudflare/vite-plugin @cloudflare/workers-types wrangler typescript
23
+ # optional: @vitejs/plugin-react (e.g. for the React Compiler, see /react-compiler)
18
24
  ```
19
25
 
20
- Keep plugin order stable: React first, then Rango, then Cloudflare.
26
+ Rango needs Node 24+ for the Vite toolchain. Keep plugin order stable:
27
+ `react()` first when you use it, then `rango()`, then `cloudflare()`.
21
28
 
22
29
  ```typescript
23
30
  // vite.config.ts
24
31
  import { cloudflare } from "@cloudflare/vite-plugin";
25
- import react from "@vitejs/plugin-react";
26
32
  import { rango } from "@rangojs/router/vite";
27
33
  import { defineConfig } from "vite";
28
34
 
29
35
  export default defineConfig({
30
36
  plugins: [
31
- react(),
37
+ // react(), // optional; first when used
32
38
  rango({ preset: "cloudflare", buildEnv: "auto" }),
33
39
  cloudflare({
34
40
  configPath: "./wrangler.json",
@@ -38,8 +44,16 @@ export default defineConfig({
38
44
  });
39
45
  ```
40
46
 
41
- `buildEnv: "auto"` exposes build-safe bindings to static/prerender collection.
42
- Omit it when build rendering must not access bindings.
47
+ `viteEnvironment` must name the `rsc` environment with `ssr` as a child: the
48
+ Worker runs the RSC entry and loads SSR inside the same isolate.
49
+
50
+ `buildEnv: "auto"` gives `Prerender` and `Static` handlers a real `ctx.env` at
51
+ build time through Wrangler's `getPlatformProxy()` (local bindings). Omit it
52
+ (default `false`) when build-time rendering must not touch bindings; `ctx.env`
53
+ then throws during build rendering. See `/prerender`.
54
+
55
+ `wrangler.json` points `main` at the Worker entry and serves the client build
56
+ as static assets:
43
57
 
44
58
  ```json
45
59
  {
@@ -81,7 +95,13 @@ does not need `@types/node` just to type-check Rango:
81
95
  }
82
96
  ```
83
97
 
84
- ## Preserve the response stream
98
+ ## Worker entry
99
+
100
+ Pass both `env` and `ctx` to `router.fetch`. Bindings reach handlers as
101
+ `ctx.env`; the `ExecutionContext` backs `waitUntil`, `CFCacheStore`, and
102
+ tracing. A bare `export default { fetch: router.fetch }` loses both, because
103
+ Workers call `fetch(request, env, ctx)` while `router.fetch` takes
104
+ `(request, { env, ctx })`.
85
105
 
86
106
  ```typescript
87
107
  // src/worker.rsc.tsx
@@ -97,8 +117,10 @@ export default {
97
117
  ```
98
118
 
99
119
  Do not call `response.text()` or `response.arrayBuffer()` on the Rango response
100
- in a custom Worker entry. That drains RSC/SSR streaming. Rewrap
101
- `new Response(response.body, response)` when only headers must change.
120
+ in the Worker entry: that buffers the whole body and removes RSC/SSR streaming.
121
+ When only headers must change, rewrap the body stream instead
122
+ (`new Response(response.body, response)`); see
123
+ [references/streaming-and-deploy.md](references/streaming-and-deploy.md).
102
124
 
103
125
  ## Choose the next reference
104
126
 
@@ -114,6 +136,7 @@ in a custom Worker entry. That drains RSC/SSR streaming. Rewrap
114
136
  Use the Cache API as L1 and KV as optional cross-colo L2:
115
137
 
116
138
  ```typescript
139
+ import { createRouter } from "@rangojs/router";
117
140
  import { CFCacheStore } from "@rangojs/router/cache";
118
141
 
119
142
  export const router = createRouter<AppBindings>({
@@ -121,7 +144,7 @@ export const router = createRouter<AppBindings>({
121
144
  urls: urlpatterns,
122
145
  cache: (env, ctx) => ({
123
146
  store: new CFCacheStore({
124
- ctx,
147
+ ctx: ctx!, // typed optional on the factory; always present on Workers
125
148
  kv: env.CACHE_KV,
126
149
  defaults: { ttl: 60, swr: 300 },
127
150
  }),
@@ -129,12 +152,39 @@ export const router = createRouter<AppBindings>({
129
152
  });
130
153
  ```
131
154
 
132
- PPR shells use Cache API as the per-colo L1 and KV as the durable cross-colo
133
- L2; a KV hit promotes the coupled shell envelope back into L1. KV is optional:
134
- `CFCacheStore` without `kv` runs the shell family L1-only (edge-only ppr —
135
- each colo captures and serves its own shell). Tag eviction then needs purge
136
- mode (`tagPurge`); a tagged shell without either warns once and expires by
137
- ttl+swr only.
155
+ `ctx` is required: the store schedules non-blocking writes with
156
+ `ctx.waitUntil`, which is why the Worker entry must pass `{ env, ctx }` to
157
+ `router.fetch`.
158
+
159
+ Tag invalidation (`updateTag()` / `revalidateTag()`) stores its markers in the
160
+ same `kv` namespace. Without `kv`, configure purge mode instead:
161
+ `tagPurge: { zoneId, apiToken }` (an API token with `Zone.Cache Purge`, stored
162
+ as a Worker secret) evicts tagged Cache API entries through Cloudflare's
163
+ purge-by-tag API.
164
+
165
+ PPR shells use the Cache API as the per-colo L1 and KV as the durable
166
+ cross-colo L2; a KV hit promotes the shell back into L1. Without `kv` the shell
167
+ family runs L1-only (each colo captures and serves its own shell). A tagged
168
+ shell with neither `kv` nor `tagPurge` logs a warning once and expires only by
169
+ ttl+swr. See `/caching` and `/ppr`.
170
+
171
+ ## Tracing on Workers
172
+
173
+ `createCloudflareTracing()` from `@rangojs/router/cloudflare` emits the
174
+ router's phases as native Workers custom spans next to the automatic KV/D1/fetch
175
+ spans. It reads the tracer from the `ctx` passed to `router.fetch`, and does
176
+ nothing when Workers tracing is not enabled for the Worker. See
177
+ `/observability`.
178
+
179
+ ```typescript
180
+ import { createCloudflareTracing } from "@rangojs/router/cloudflare";
181
+
182
+ export const router = createRouter<AppBindings>({
183
+ document: Document,
184
+ urls: urlpatterns,
185
+ tracing: createCloudflareTracing(),
186
+ });
187
+ ```
138
188
 
139
189
  ## Commands
140
190
 
@@ -149,5 +199,7 @@ ttl+swr only.
149
199
  }
150
200
  ```
151
201
 
152
- Use `vite dev` and `vite preview` for local RSC work. Reserve Wrangler for
153
- resource provisioning, migrations, secrets, and deployment.
202
+ Always deploy the output of `vite build` (`dist/rsc/wrangler.json`; a plain
203
+ `wrangler deploy` right after a build follows the plugin's redirect to it),
204
+ never the source config. Use `vite dev` and `vite preview` for local RSC work. Reserve
205
+ Wrangler for resource provisioning, migrations, secrets, and deployment.
@@ -1,5 +1,8 @@
1
1
  # D1, KV, and local parity
2
2
 
3
+ Provisioning D1/KV bindings, applying migrations, loading local secrets, and
4
+ testing that `vite dev` and `vite preview` behave like the deployed Worker.
5
+
3
6
  ## Contents
4
7
 
5
8
  - [Provision bindings](#provision-bindings)
@@ -50,9 +53,11 @@ therefore visible to both `vite dev` and `vite preview`; treat the internal
50
53
  `.wrangler/state` layout as an implementation detail.
51
54
 
52
55
  Add a real handler probe while setting up the binding, then remove it after the
53
- first production-shaped test:
56
+ first production-shaped test. `path.json` is a response route (`/response-routes`):
57
+ return a value and it is serialized as JSON.
54
58
 
55
59
  ```typescript
60
+ // inside urls(({ path }) => [ ... ])
56
61
  path.json("/__db-check", async (ctx) => {
57
62
  const row = await ctx.env.DB.prepare(
58
63
  "SELECT COUNT(*) AS n FROM users",
@@ -65,9 +70,17 @@ path.json("/__db-check", async (ctx) => {
65
70
 
66
71
  ## Use bindings
67
72
 
68
- Handlers, loaders, and middleware all receive the same typed bindings:
73
+ Handlers, loaders, and middleware all receive the same typed bindings on
74
+ `ctx.env`:
69
75
 
70
76
  ```typescript
77
+ import {
78
+ cookies,
79
+ createLoader,
80
+ redirect,
81
+ type Middleware,
82
+ } from "@rangojs/router";
83
+
71
84
  export const UserLoader = createLoader(async (ctx) =>
72
85
  ctx.env.DB.prepare("SELECT * FROM users WHERE id = ?")
73
86
  .bind(ctx.params.id)
@@ -1,5 +1,9 @@
1
1
  # Streaming, preview, and deployment
2
2
 
3
+ Keeping responses streamed through a custom Worker entry, choosing local
4
+ tooling, route-owned CORS, deploying the built Worker, and isolating
5
+ dev/preview differences.
6
+
3
7
  ## Contents
4
8
 
5
9
  - [Keep streamed responses intact](#keep-streamed-responses-intact)
@@ -80,9 +84,12 @@ pnpm exec vite build
80
84
  pnpm exec wrangler deploy -c dist/rsc/wrangler.json
81
85
  ```
82
86
 
83
- Do not deploy the source `main` from the root config after building. Wrangler's
84
- bundler cannot reconstruct Rango's Vite virtual modules. If the RSC environment
85
- has a custom name, use its matching `dist/<name>/wrangler.json` path.
87
+ `vite build` also writes a `.wrangler/deploy/config.json` redirect, so a plain
88
+ `wrangler deploy` run after the build picks up the same generated config. Never
89
+ deploy the source `main` (for example `wrangler deploy -c wrangler.json`, or a
90
+ deploy with no fresh build): Wrangler's bundler cannot reconstruct Rango's Vite
91
+ virtual modules. If the RSC environment has a custom name, use its matching
92
+ `dist/<name>/wrangler.json` path.
86
93
 
87
94
  Before deployment:
88
95
 
@@ -1,5 +1,9 @@
1
1
  # Webhooks and WebCrypto on Workers
2
2
 
3
+ Verifying signed webhooks against the raw request body, testing them offline,
4
+ porting Node crypto to WebCrypto, and keeping large SDKs out of the Worker's
5
+ startup path.
6
+
3
7
  ## Contents
4
8
 
5
9
  - [Read the original request body](#read-the-original-request-body)
@@ -136,10 +140,13 @@ provider behavior when upgrading the SDK major.
136
140
 
137
141
  ## Keep SDKs off the cold path
138
142
 
139
- Put a large, rarely used webhook/billing group behind an async include:
143
+ Put a large, rarely used webhook/billing group behind an async include with
144
+ its own URL prefix. The chunk is imported on the first request that reaches
145
+ that prefix, not at Worker startup (`/composability`):
140
146
 
141
147
  ```typescript
142
- include("/", () => import("./urls/billing.js"), { name: "billing" });
148
+ // urls/billing.ts: `export default urls(({ path }) => [ ... ])`
149
+ include("/api/billing", () => import("./urls/billing.js"), { name: "billing" });
143
150
  ```
144
151
 
145
152
  Measure a before/after production build before claiming an SDK's incremental
@@ -5,6 +5,11 @@ description: Compare Rango with Next.js App Router, TanStack Start, and Waku. Us
5
5
 
6
6
  # Compare Rango with other React frameworks
7
7
 
8
+ This skill positions Rango against Next.js App Router, TanStack Start, and Waku
9
+ on capabilities and architecture. Use it for "why Rango" questions, adoption
10
+ memos, and fact-checking a comparative claim. For the mechanics of actually
11
+ porting an app, use `/migrate-nextjs` or `/migrate-react-router`.
12
+
8
13
  Use the canonical [framework comparison](references/framework-comparison.md) as
9
14
  the factual baseline. Read the sections relevant to the question; read the full
10
15
  reference when producing an overall evaluation or editing the comparison itself.