@rangojs/router 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/README.md +255 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/cookie-name.d.ts +6 -0
  4. package/dist/types/browser/history-state.d.ts +16 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  6. package/dist/types/browser/partial-update.d.ts +11 -0
  7. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  8. package/dist/types/browser/rsc-router.d.ts +1 -1
  9. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  10. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  11. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  12. package/dist/types/cache/cache-policy.d.ts +17 -0
  13. package/dist/types/cache/cache-scope.d.ts +132 -19
  14. package/dist/types/cache/cache-tag.d.ts +92 -7
  15. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  18. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  19. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  20. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  21. package/dist/types/cache/handle-capture.d.ts +41 -3
  22. package/dist/types/cache/handle-snapshot.d.ts +54 -13
  23. package/dist/types/cache/index.d.ts +1 -0
  24. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  25. package/dist/types/cache/segment-codec.d.ts +10 -3
  26. package/dist/types/cache/shell-memo.d.ts +178 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  28. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  29. package/dist/types/cache/taint.d.ts +1 -1
  30. package/dist/types/cache/types.d.ts +220 -44
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  32. package/dist/types/client.d.ts +4 -4
  33. package/dist/types/client.rsc.d.ts +4 -5
  34. package/dist/types/components/DefaultDocument.d.ts +5 -2
  35. package/dist/types/context-var.d.ts +6 -5
  36. package/dist/types/escape-script.d.ts +1 -1
  37. package/dist/types/handles/MetaTags.d.ts +5 -4
  38. package/dist/types/handles/Scripts.d.ts +8 -7
  39. package/dist/types/handles/meta.d.ts +2 -2
  40. package/dist/types/handles/script.d.ts +17 -16
  41. package/dist/types/href-client.d.ts +1 -1
  42. package/dist/types/html.d.ts +12 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +3 -2
  45. package/dist/types/loader-store.d.ts +7 -4
  46. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  47. package/dist/types/reverse.d.ts +36 -4
  48. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  49. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  50. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  51. package/dist/types/route-types.d.ts +1 -1
  52. package/dist/types/router/debug-manifest.d.ts +3 -2
  53. package/dist/types/router/error-handling.d.ts +20 -0
  54. package/dist/types/router/handler-context.d.ts +6 -0
  55. package/dist/types/router/instrument.d.ts +8 -0
  56. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  57. package/dist/types/router/middleware-types.d.ts +9 -3
  58. package/dist/types/router/router-context.d.ts +3 -1
  59. package/dist/types/router/router-interfaces.d.ts +24 -2
  60. package/dist/types/router/router-options.d.ts +8 -5
  61. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  62. package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
  63. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  64. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
  65. package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  67. package/dist/types/router/segment-wrappers.d.ts +1 -1
  68. package/dist/types/rsc/full-payload.d.ts +14 -0
  69. package/dist/types/rsc/handler.d.ts +1 -1
  70. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  71. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  72. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
  74. package/dist/types/rsc/shell-capture.d.ts +151 -19
  75. package/dist/types/rsc/shell-serve.d.ts +140 -17
  76. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  77. package/dist/types/rsc/types.d.ts +17 -0
  78. package/dist/types/server/context.d.ts +139 -15
  79. package/dist/types/server/cookie-store.d.ts +21 -0
  80. package/dist/types/server/handle-store.d.ts +26 -2
  81. package/dist/types/server/request-context.d.ts +182 -53
  82. package/dist/types/ssr/index.d.ts +24 -0
  83. package/dist/types/ssr/ssr-root.d.ts +9 -8
  84. package/dist/types/static-handler.d.ts +4 -1
  85. package/dist/types/testing/flight.entry.d.ts +7 -0
  86. package/dist/types/testing/generated-routes.d.ts +2 -2
  87. package/dist/types/testing/internal/context.d.ts +1 -1
  88. package/dist/types/testing/render-route.d.ts +32 -6
  89. package/dist/types/testing/run-loader.d.ts +18 -3
  90. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  91. package/dist/types/testing/shell-status.d.ts +23 -10
  92. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  93. package/dist/types/testing/vitest.d.ts +35 -1
  94. package/dist/types/theme/ThemeScript.d.ts +3 -3
  95. package/dist/types/types/cache-types.d.ts +14 -10
  96. package/dist/types/types/handler-context.d.ts +28 -1
  97. package/dist/types/types/loader-types.d.ts +8 -6
  98. package/dist/types/urls/pattern-types.d.ts +22 -18
  99. package/dist/types/urls/response-types.d.ts +3 -12
  100. package/dist/types/use-loader.d.ts +12 -3
  101. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  102. package/dist/types/vite/rango.d.ts +1 -1
  103. package/dist/types/vite/router-discovery.d.ts +29 -2
  104. package/dist/vite/index.js +1420 -1348
  105. package/package.json +2 -2
  106. package/skills/api-client/SKILL.md +22 -10
  107. package/skills/breadcrumbs/SKILL.md +75 -22
  108. package/skills/bundle-analysis/SKILL.md +22 -22
  109. package/skills/cache-guide/SKILL.md +306 -124
  110. package/skills/caching/SKILL.md +530 -98
  111. package/skills/catalog.json +16 -16
  112. package/skills/client-urls/SKILL.md +65 -38
  113. package/skills/cloudflare/SKILL.md +87 -20
  114. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  115. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  116. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  117. package/skills/comparison/SKILL.md +5 -0
  118. package/skills/comparison/references/framework-comparison.md +75 -57
  119. package/skills/composability/SKILL.md +38 -29
  120. package/skills/css/SKILL.md +10 -13
  121. package/skills/debug-manifest/SKILL.md +74 -31
  122. package/skills/defer-hydration/SKILL.md +52 -31
  123. package/skills/deployment-caching/SKILL.md +11 -3
  124. package/skills/document-cache/SKILL.md +98 -28
  125. package/skills/fonts/SKILL.md +16 -8
  126. package/skills/handler-use/SKILL.md +55 -17
  127. package/skills/hooks/SKILL.md +28 -20
  128. package/skills/hooks/data.md +23 -3
  129. package/skills/hooks/handle-and-actions.md +34 -17
  130. package/skills/hooks/navigation.md +39 -10
  131. package/skills/hooks/outlets.md +13 -6
  132. package/skills/hooks/state.md +39 -15
  133. package/skills/hooks/urls.md +7 -5
  134. package/skills/host-router/SKILL.md +64 -12
  135. package/skills/i18n/SKILL.md +60 -34
  136. package/skills/intercept/SKILL.md +161 -26
  137. package/skills/layout/SKILL.md +106 -50
  138. package/skills/links/SKILL.md +51 -19
  139. package/skills/loader/SKILL.md +386 -128
  140. package/skills/middleware/SKILL.md +129 -26
  141. package/skills/migrate-nextjs/SKILL.md +156 -70
  142. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  143. package/skills/migrate-react-router/SKILL.md +51 -39
  144. package/skills/migrate-react-router/component-migration.md +28 -7
  145. package/skills/migrate-react-router/data-and-actions.md +21 -6
  146. package/skills/migrate-react-router/route-mapping.md +19 -10
  147. package/skills/mime-routes/SKILL.md +12 -7
  148. package/skills/observability/SKILL.md +92 -14
  149. package/skills/parallel/SKILL.md +88 -47
  150. package/skills/ppr/SKILL.md +866 -379
  151. package/skills/prerender/SKILL.md +210 -108
  152. package/skills/rango/SKILL.md +115 -65
  153. package/skills/react-compiler/SKILL.md +6 -5
  154. package/skills/response-routes/SKILL.md +133 -25
  155. package/skills/route/SKILL.md +170 -60
  156. package/skills/router-setup/SKILL.md +420 -240
  157. package/skills/scripts/SKILL.md +39 -21
  158. package/skills/server-actions/SKILL.md +231 -59
  159. package/skills/shell-manifest/SKILL.md +28 -14
  160. package/skills/streams-and-websockets/SKILL.md +32 -14
  161. package/skills/tailwind/SKILL.md +37 -33
  162. package/skills/testing/SKILL.md +73 -33
  163. package/skills/testing/cache-prerender.md +60 -33
  164. package/skills/testing/client-components.md +59 -6
  165. package/skills/testing/e2e-parity.md +12 -2
  166. package/skills/testing/flight.md +19 -11
  167. package/skills/testing/handles.md +8 -5
  168. package/skills/testing/loader.md +26 -6
  169. package/skills/testing/middleware.md +8 -3
  170. package/skills/testing/predicates.md +120 -0
  171. package/skills/testing/render-handler.md +70 -22
  172. package/skills/testing/response-routes.md +11 -4
  173. package/skills/testing/reverse-and-types.md +20 -14
  174. package/skills/testing/serve-shell-request.md +188 -0
  175. package/skills/testing/server-actions.md +12 -3
  176. package/skills/testing/server-tree.md +9 -0
  177. package/skills/testing/setup.md +9 -5
  178. package/skills/theme/SKILL.md +155 -30
  179. package/skills/typesafety/SKILL.md +20 -9
  180. package/skills/typesafety/env-and-bindings.md +137 -50
  181. package/skills/typesafety/generated-files-and-cli.md +26 -14
  182. package/skills/typesafety/params-and-search.md +44 -19
  183. package/skills/typesafety/route-types.md +50 -14
  184. package/skills/use-cache/SKILL.md +224 -83
  185. package/skills/vercel/SKILL.md +35 -13
  186. package/skills/view-transitions/SKILL.md +42 -21
  187. package/src/__augment-tests__/augmented.check.ts +26 -1
  188. package/src/browser/cookie-name.ts +23 -0
  189. package/src/browser/history-state.ts +23 -0
  190. package/src/browser/index.ts +2 -2
  191. package/src/browser/navigation-bridge.ts +3 -4
  192. package/src/browser/navigation-transaction.ts +22 -2
  193. package/src/browser/partial-update.ts +35 -25
  194. package/src/browser/react/ScrollRestoration.tsx +6 -6
  195. package/src/browser/rsc-router.tsx +6 -1
  196. package/src/browser/scroll-restoration.ts +53 -6
  197. package/src/cache/cache-exec-scope.ts +37 -4
  198. package/src/cache/cache-key-utils.ts +23 -8
  199. package/src/cache/cache-policy.ts +22 -0
  200. package/src/cache/cache-runtime.ts +264 -68
  201. package/src/cache/cache-scope.ts +419 -43
  202. package/src/cache/cache-tag.ts +375 -11
  203. package/src/cache/cf/cf-base64.ts +36 -4
  204. package/src/cache/cf/cf-cache-constants.ts +12 -0
  205. package/src/cache/cf/cf-cache-store.ts +990 -348
  206. package/src/cache/cf/cf-cache-types.ts +32 -4
  207. package/src/cache/cf/cf-shell-frame.ts +258 -0
  208. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  209. package/src/cache/document-cache.ts +15 -0
  210. package/src/cache/handle-capture.ts +90 -10
  211. package/src/cache/handle-snapshot.ts +133 -28
  212. package/src/cache/index.ts +2 -0
  213. package/src/cache/isolate-tag-memo.ts +308 -0
  214. package/src/cache/segment-codec.ts +26 -7
  215. package/src/cache/shell-memo.ts +341 -0
  216. package/src/cache/shell-snapshot.ts +217 -27
  217. package/src/cache/tag-invalidation.ts +108 -22
  218. package/src/cache/taint.ts +1 -1
  219. package/src/cache/types.ts +225 -44
  220. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  221. package/src/client.rsc.tsx +10 -9
  222. package/src/client.tsx +5 -4
  223. package/src/components/DefaultDocument.tsx +7 -2
  224. package/src/context-var.ts +6 -5
  225. package/src/escape-script.ts +1 -1
  226. package/src/handles/MetaTags.tsx +13 -11
  227. package/src/handles/Scripts.tsx +11 -10
  228. package/src/handles/meta.ts +2 -2
  229. package/src/handles/script.ts +17 -16
  230. package/src/href-client.ts +1 -1
  231. package/src/html.ts +12 -0
  232. package/src/index.rsc.ts +5 -0
  233. package/src/index.ts +1 -0
  234. package/src/loader-store.ts +7 -4
  235. package/src/prerender/build-shell-capture.ts +73 -22
  236. package/src/reverse.ts +44 -4
  237. package/src/route-definition/dsl-helpers.ts +121 -33
  238. package/src/route-definition/helpers-types.ts +33 -9
  239. package/src/route-definition/resolve-handler-use.ts +121 -6
  240. package/src/route-types.ts +0 -3
  241. package/src/router/debug-manifest.ts +10 -2
  242. package/src/router/error-handling.ts +51 -15
  243. package/src/router/handler-context.ts +71 -28
  244. package/src/router/instrument.ts +54 -2
  245. package/src/router/intercept-resolution.ts +141 -43
  246. package/src/router/loader-resolution.ts +115 -39
  247. package/src/router/match-api.ts +40 -26
  248. package/src/router/match-handlers.ts +5 -0
  249. package/src/router/match-middleware/background-revalidation.ts +161 -96
  250. package/src/router/match-middleware/cache-lookup.ts +110 -10
  251. package/src/router/match-middleware/cache-store.ts +48 -101
  252. package/src/router/middleware-types.ts +9 -6
  253. package/src/router/middleware.ts +75 -13
  254. package/src/router/prerender-match.ts +59 -19
  255. package/src/router/route-snapshot.ts +11 -1
  256. package/src/router/router-context.ts +2 -0
  257. package/src/router/router-interfaces.ts +25 -2
  258. package/src/router/router-options.ts +8 -5
  259. package/src/router/segment-resolution/fresh.ts +62 -21
  260. package/src/router/segment-resolution/helpers.ts +4 -2
  261. package/src/router/segment-resolution/loader-cache.ts +407 -76
  262. package/src/router/segment-resolution/loader-mask.ts +12 -22
  263. package/src/router/segment-resolution/loader-snapshot.ts +208 -56
  264. package/src/router/segment-resolution/mask-nested.ts +138 -44
  265. package/src/router/segment-resolution/revalidation.ts +61 -16
  266. package/src/router/segment-wrappers.ts +4 -0
  267. package/src/rsc/full-payload.ts +25 -1
  268. package/src/rsc/handler.ts +14 -2
  269. package/src/rsc/progressive-enhancement.ts +3 -2
  270. package/src/rsc/render-pipeline.ts +3 -0
  271. package/src/rsc/response-cache-serve.ts +9 -6
  272. package/src/rsc/response-route-handler.ts +11 -3
  273. package/src/rsc/rsc-rendering.ts +608 -225
  274. package/src/rsc/shell-build-manifest.ts +27 -9
  275. package/src/rsc/shell-capture-constants.ts +73 -6
  276. package/src/rsc/shell-capture.ts +1068 -296
  277. package/src/rsc/shell-serve.ts +316 -34
  278. package/src/rsc/ssr-setup.ts +14 -8
  279. package/src/rsc/types.ts +18 -0
  280. package/src/server/context.ts +300 -32
  281. package/src/server/cookie-store.ts +87 -61
  282. package/src/server/handle-store.ts +232 -7
  283. package/src/server/request-context.ts +297 -67
  284. package/src/ssr/index.tsx +136 -44
  285. package/src/ssr/ssr-root.tsx +9 -8
  286. package/src/static-handler.ts +4 -1
  287. package/src/testing/flight-runtime.d.ts +21 -0
  288. package/src/testing/flight.entry.ts +14 -0
  289. package/src/testing/generated-routes.ts +2 -2
  290. package/src/testing/internal/context.ts +1 -1
  291. package/src/testing/internal/flight-client-globals.ts +7 -5
  292. package/src/testing/render-route.tsx +85 -24
  293. package/src/testing/run-loader.ts +68 -6
  294. package/src/testing/serve-shell-request.ts +378 -0
  295. package/src/testing/shell-status.ts +34 -12
  296. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  297. package/src/testing/vitest.ts +62 -1
  298. package/src/theme/ThemeProvider.tsx +7 -6
  299. package/src/theme/ThemeScript.tsx +3 -3
  300. package/src/types/cache-types.ts +14 -10
  301. package/src/types/handler-context.ts +28 -1
  302. package/src/types/loader-types.ts +8 -6
  303. package/src/urls/pattern-types.ts +22 -18
  304. package/src/urls/response-types.ts +3 -23
  305. package/src/use-loader.tsx +12 -3
  306. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  307. package/src/vite/plugins/use-cache-transform.ts +118 -74
  308. package/src/vite/rango.ts +7 -3
  309. package/src/vite/router-discovery.ts +14 -4
@@ -6,14 +6,21 @@ argument-hint: [profile-name]
6
6
 
7
7
  # "use cache" Directive
8
8
 
9
- Function-level caching for async server functions and RSC components. Caches
10
- return values with TTL + stale-while-revalidate. Complementary to the route-level
11
- `cache()` DSL and build-time `Static()`/`Prerender()`.
9
+ Function-level caching for async server functions and RSC components. Put the
10
+ directive in a function (or at the top of a module) and its return value is
11
+ cached with a TTL and a stale-while-revalidate window, keyed by its arguments.
12
+ Use it when one data fetch or one component is expensive and is reused across
13
+ routes or requests; the code around the call keeps running.
14
+
15
+ It complements the route-level `cache()` DSL (a whole segment subtree) and
16
+ build-time `Static()`/`Prerender()`. `"use cache"` needs an app-level store
17
+ (`createRouter({ cache })`); without one the function runs uncached.
12
18
 
13
19
  ## Not this skill if…
14
20
 
15
21
  - You want to cache a whole route or a rendered subtree of segments — that is
16
22
  the segment-level `cache()` DSL: see `/caching`.
23
+ - You want a value fresh on every request — use a loader (see `/loader`).
17
24
  - You are unsure which cache layer you need — start at `/cache-guide`.
18
25
 
19
26
  ## Basic Usage
@@ -56,6 +63,16 @@ export async function ProductCard({ id }: { id: string }) {
56
63
  }
57
64
  ```
58
65
 
66
+ ### Results that are not cached
67
+
68
+ A result Flight cannot encode cleanly is not cached: an async child component
69
+ that throws, a promise in the result or in a handle push that rejects, a
70
+ function or a class instance. Storing it would bake the failure into every hit
71
+ until expiry. Instead the write is skipped and reported to `onError` (phase
72
+ `"cache"`, category `cache-write`, or `stale-revalidation` for a background
73
+ refresh): a miss still returns the live result, the next call runs the
74
+ function again, and a stale entry keeps serving.
75
+
59
76
  ## Named Cache Profiles
60
77
 
61
78
  Define profiles in createRouter. Profile names map to `"use cache: <name>"` in
@@ -85,11 +102,17 @@ createRouter({
85
102
  mutation-related cached data; incidental TTL staleness on an ordinary action
86
103
  stays SWR so the action is not turned into a synchronous cache-refresh barrier.
87
104
  For strong read-your-own-writes after a mutation, prefer `updateTag()` (a hard
88
- purge, so the action's own re-render is a fresh foreground miss).
105
+ purge, so the action's own re-render is a fresh foreground miss). Only
106
+ `"use cache"` honors this flag; `cache()` boundaries and cached loaders ignore
107
+ it.
108
+ - `default` is built in as `{ ttl: 900, swr: 1800 }`; defining `default`
109
+ overrides it.
110
+ - Profile names must match `[a-zA-Z0-9_-]+`, and `ttl`/`swr` must be finite and
111
+ non-negative; `createRouter()` throws otherwise.
89
112
  - Unknown profile names throw at runtime, on the first invocation of the cached
90
113
  function (the Vite transform does not validate names at build/boot). The error
91
- is actionable -- it names the missing profile and shows the `createRouter({
92
- cacheProfiles: { ... } })` entry to add.
114
+ names the missing profile and shows the `createRouter({ cacheProfiles })` entry
115
+ to add.
93
116
 
94
117
  ## Cache Key
95
118
 
@@ -97,8 +120,20 @@ cacheProfiles: { ... } })` entry to add.
97
120
  use-cache:{functionId}:{serializedArgs}
98
121
  ```
99
122
 
100
- - `functionId` -- stable ID from Vite transform (module path + export name).
101
- - `serializedArgs` -- key-generating arguments serialized via RSC `encodeReply()`.
123
+ - `functionId` -- stable ID from Vite transform (module path + export name in
124
+ dev, a hash of both in production builds).
125
+ - `serializedArgs` -- key-generating arguments: a stable JSON encoding when every
126
+ argument is JSON-safe, otherwise serialized via RSC `encodeReply()`.
127
+
128
+ React elements (`header`, `children` slots) and client or server references
129
+ are left out of the key: the first call's rendered slot is part of the cached
130
+ output, and a hit returns it for any slot passed.
131
+
132
+ If any other argument cannot be serialized, top-level or nested (a function, a
133
+ class instance such as a database client, a symbol), the call runs uncached and
134
+ warns once per function in dev. Flight's encoder writes the same placeholder
135
+ for every such value, so keying on it would serve one caller's result to every
136
+ other caller. Pass the serializable values the function needs.
102
137
 
103
138
  When there are no key-generating arguments, the key has no trailing colon -- it is
104
139
  just `use-cache:{functionId}`.
@@ -107,6 +142,11 @@ Different functions always produce different cache keys, even for the same route
107
142
  This is important for intercepted routes -- the path handler and intercept handler
108
143
  each have their own `functionId` and therefore their own cache entries.
109
144
 
145
+ The key is only what the function declares: its id and arguments. A call made
146
+ inside a route `cache()` with a `key()` does not inherit that key, so a value
147
+ the route partitions by (a tier header, a session) must be passed in as an
148
+ argument. The same holds for a loader's own `cache()` (`/loader` → "Cache Key").
149
+
110
150
  ### Route context is folded into the key
111
151
 
112
152
  The tainted `ctx` object is excluded from arg serialization (see below), but
@@ -117,16 +157,48 @@ params excluded). The same cached function called with `ctx` on different routes
117
157
  param combinations, hosts, response types, or query variants therefore produces
118
158
  distinct cache entries -- not one shared entry.
119
159
 
120
- ## Tainted Arguments (ctx, env, req)
121
-
122
- Request-scoped objects are branded with `Symbol.for('rango:nocache')` at creation.
123
- When detected:
160
+ ## Request-Scoped Arguments (ctx, Request, env)
161
+
162
+ Three request-scoped arguments are kept out of the serialized arguments:
163
+
164
+ - **`ctx`** is branded with `Symbol.for('rango:nocache')` at creation. Its route
165
+ fields are folded into the key, and its handle pushes are captured and
166
+ replayed (below). Every ctx is branded: handler, loader, middleware,
167
+ response-route and the request context. A loader ctx keys exactly like the
168
+ handler ctx of the same request. A middleware ctx keys by host, pathname,
169
+ its params and search, plus the route name once the route is matched (a
170
+ global middleware before `next()` has none yet). A ctx carrying a request
171
+ body (a fetchable loader called with `method: "POST"` and a `body` or form
172
+ data) runs the call uncached and warns in dev: the body is not in the key.
173
+ The method is not in the key either.
174
+ - **A `Request`** (such as `ctx.request`) keys by its URL: host, pathname and
175
+ the user-facing sorted search params, with the same internal-param exclusion
176
+ and `cache.searchParams` filter as the URL-keyed tiers. Headers, cookies and
177
+ the method are not in the key; read what you need outside and pass the values.
178
+ - **The request's `env`** (the object `ctx.env` returns) is left out of the key:
179
+ it is fixed per deployment. A binding passed on its own (`env.DB`) is a class
180
+ instance and runs uncached; pass `env` instead.
181
+
182
+ When a `ctx` is detected:
124
183
 
125
184
  1. **Excluded from cache key** -- request-scoped, not meaningful for keying.
126
185
  (The route-identifying fields read off `ctx` are still folded in -- see
127
186
  "Route context is folded into the key" above.)
128
- 2. **Handle data captured on miss** -- side effects via `ctx.use(Handle)` are recorded.
129
- 3. **Handle data replayed on hit** -- restored into the current request's HandleStore.
187
+ 2. **Handle data captured on miss** -- side effects via `ctx.use(Handle)` are recorded:
188
+ the function's own pushes (and those of cached functions it calls), not the
189
+ handler's or loaders' pushes into the same request.
190
+ 3. **Handle data replayed on hit** -- appended to the calling segment (for a
191
+ loader ctx, the segment that declares the loader), after what the handler
192
+ and loaders already pushed, as if the body had run (a layout and its page
193
+ calling the same function each get their copy). A
194
+ stale hit's background refresh records its pushes into the refreshed entry only.
195
+ 4. **A loader the function reads shows its pushes once** -- pushes from a loader
196
+ read with `ctx.use(Loader)` inside the function are recorded under that
197
+ loader. Loaders stay live, so if the handler or a DSL loader also reads it,
198
+ that live run's pushes are the ones on the page: a live run before the hit
199
+ makes the replay skip them, and one after it replaces the replayed values in
200
+ place. A loader read only inside the function is replayed once, also when
201
+ a layout and its page both call the function.
130
202
 
131
203
  ```typescript
132
204
  export async function getProductData(ctx) {
@@ -136,6 +208,17 @@ export async function getProductData(ctx) {
136
208
  return await db.query("SELECT * FROM products");
137
209
  }
138
210
  // On hit: return value restored, breadcrumb replayed.
211
+
212
+ export async function getProduct(ctx) {
213
+ "use cache";
214
+ const category = await ctx.use(CategoryLoader); // pushes a category crumb
215
+ return db.product(ctx.params.id, category.id);
216
+ }
217
+ // Handler: `await getProduct(ctx); await ctx.use(CategoryLoader);`
218
+ // On hit: one category crumb, from the handler's live CategoryLoader run.
219
+
220
+ export const ProductLoader = createLoader(async (ctx) => getProduct(ctx));
221
+ // A loader ctx keys like the handler ctx: one entry per route, params and query.
139
222
  ```
140
223
 
141
224
  ## Request-Scoped Guards
@@ -153,27 +236,57 @@ const locale = cookies().get("locale")?.value ?? "en";
153
236
  const data = await getCachedData(locale); // locale is now in the cache key
154
237
  ```
155
238
 
156
- The guard does not reach into LOADER bodies consumed inside the cached
157
- function (`await ctx.use(loader)`): loaders always run fresh, so their reads
158
- are exempt — but the CONSUMED VALUE is captured into the shared cache entry
159
- like any other computed data. Same rule across `cache()` and the PPR shell:
239
+ `ctx.get()` of a **non-cacheable variable** (`createVar({ cache: false })`, or
240
+ a value written with `ctx.set(key, value, { cache: false })`) throws the same
241
+ way, whether it goes through `getRequestContext().get()` or a `ctx` passed in
242
+ (handler, loader, middleware or response-route).
243
+ The key does not include the value, so the first caller's value would be
244
+ served to later callers. Read it before the call and pass it in:
245
+
246
+ ```typescript
247
+ const Tenant = createVar<string>({ cache: false });
248
+
249
+ async function getNav(tenant: string) {
250
+ "use cache";
251
+ return loadNav(tenant); // tenant is part of the key
252
+ }
253
+
254
+ // in a handler or loader, outside the cached function
255
+ const nav = await getNav(ctx.get(Tenant)!);
256
+ ```
257
+
258
+ Ordinary (cacheable) variables stay readable. Calling the cached function from
259
+ a loader does not exempt it: the loader re-runs on every request, but the
260
+ cached body does not.
261
+
262
+ The non-cacheable variable guard does not reach into LOADER bodies consumed
263
+ inside the cached function (`await ctx.use(loader)`): loaders always run
264
+ fresh, so their variable reads are exempt — but the CONSUMED VALUE is captured
265
+ into the shared cache entry like any other computed data. (`cookies()` and
266
+ `headers()` still throw there: that guard follows the cached body's whole
267
+ async chain, loaders included.) Same rule across `cache()` and the PPR shell:
160
268
  handler/cached-scope consumption = baked copy, client-side `useLoader` = live
161
- (the consumption-lane rule, `/rango` → Invariants).
269
+ (the consumption-lane rule, `/rango` → Invariants). The PPR shell is stricter
270
+ about the loader's own reads: during a capture, `cookies()`, `headers()`, and a
271
+ `{ cache: false }` variable read inside a handler-consumed loader refuse the
272
+ capture.
162
273
 
163
274
  ### Side-Effect Guards
164
275
 
165
276
  These ctx methods **throw** inside a `"use cache"` function because their effects
166
277
  are lost on cache hit (the function body is skipped):
167
278
 
168
- - `ctx.set()` for passing values to children
169
- - `ctx.header()`
279
+ - `ctx.set()` for passing values to children (handler and middleware ctx)
280
+ - `ctx.headers.set()` and the other mutating `Headers` methods (handler and
281
+ middleware ctx), and a middleware's `ctx.header()`
282
+ - cookie writes and the request-context writers `header()`, `setCookie()`,
283
+ `deleteCookie()`, `setStatus()`, `onResponse()`
170
284
  - `ctx.setTheme()`
171
285
  - `ctx.setLocationState()`
172
- - `ctx.onResponse()`
173
286
 
174
- `ctx.get()` is **not** exec-guarded inside `"use cache"` -- it is a read, so it is
175
- safe. (It only throws when reading a non-cacheable variable inside the separate
176
- route-level `cache()` DSL boundary.)
287
+ `ctx.get()` is a read, not a side effect: it stays allowed for ordinary
288
+ variables and throws only for a non-cacheable one (see "Read Guards" above).
289
+ The same non-cacheable read throws inside a route-level `cache()` boundary.
177
290
 
178
291
  The error message recommends two alternatives:
179
292
 
@@ -257,9 +370,8 @@ On cache miss, the function executes and the result is serialized inline (blocki
257
370
  The cache **store write** (`setItem`) is deferred to `waitUntil` and does NOT block
258
371
  the response.
259
372
 
260
- On stale hit, stale data is returned immediately. Background revalidation (re-execute
261
-
262
- - store) runs entirely inside `waitUntil`.
373
+ On stale hit, stale data is returned immediately. Background revalidation
374
+ (re-execute + store write) runs entirely inside `waitUntil`.
263
375
 
264
376
  | Phase | Blocks response? |
265
377
  | ------------------------------------ | ---------------- |
@@ -356,75 +468,104 @@ decrypt; without it they fail until the entry expires or revalidates.
356
468
 
357
469
  ## Vite Transform
358
470
 
359
- The `rango:use-cache` Vite plugin detects the directive and wraps exports with
360
- `registerCachedFunction()`:
471
+ The `@rangojs/router:use-cache` Vite plugin (part of `rango()`) detects the directive and wraps each cached
472
+ function with `registerCachedFunction(fn, functionId, profileName)` from
473
+ `@rangojs/router/cache-runtime`. You never call it yourself. Simplified output:
361
474
 
362
475
  ```typescript
363
- // Input
364
- "use cache"
476
+ // Input (file-level directive)
477
+ "use cache";
365
478
  export async function getProducts() { ... }
366
479
 
367
- // Output
368
- import { registerCachedFunction } from '@rangojs/router/cache-runtime';
480
+ // Output (simplified)
481
+ import { registerCachedFunction } from "@rangojs/router/cache-runtime";
369
482
  export const getProducts = registerCachedFunction(
370
483
  async function getProducts() { ... },
371
- "src/data/products.ts#getProducts",
372
- "default"
484
+ "src/data/products.ts#getProducts", // a hash of this in production builds
485
+ "default",
373
486
  );
374
487
  ```
375
488
 
376
- Function-level directives are hoisted:
489
+ A function-level directive (`"use cache: short"` inside the body) is hoisted out
490
+ of the function and wrapped the same way, with the profile name from the
491
+ directive.
377
492
 
378
- ```typescript
379
- // Input
380
- export async function getProducts() {
381
- "use cache: short";
382
- return await db.query("...");
383
- }
493
+ File-level rules:
384
494
 
385
- // Output
386
- const __rango_cached_getProducts = registerCachedFunction(
387
- async function getProducts() {
388
- return await db.query("...");
389
- },
390
- "src/data/products.ts#getProducts",
391
- "short",
392
- );
393
- export async function getProducts() {
394
- return __rango_cached_getProducts();
395
- }
396
- ```
495
+ - Only exports that are statically confirmed functions are wrapped
496
+ (`export async function foo() {}`, `export const foo = async () => {}`). Any
497
+ other export (a constant, or a factory call like `makeCached(fn)`) fails the
498
+ build — move it to another module.
499
+ - The default export of a file named `layout.tsx` or `template.tsx` (any
500
+ `.ts/.tsx/.js/.jsx` extension) is not wrapped: it receives `children`, which
501
+ cannot be part of a cache key.
502
+ - Functions carrying their own `"use server"` directive are server actions, not
503
+ cached functions, and are left alone.
397
504
 
398
505
  ## Backing Store
399
506
 
400
- Writes to the same `SegmentCacheStore` as `cache()` DSL, `Static()`, and `Prerender()`.
401
- One store, one configuration.
402
-
403
- Cache entries (and `cacheProfiles`) can be tagged via `cache({ tags })` or runtime
404
- `cacheTag(...tags)`. `cacheTag` has two forms: inside a `"use cache"` function it
405
- tags that entry; called during a request render outside `"use cache"` it tags the
406
- request's document/shell artifact (rides `_requestTags`) instead of throwing. The
407
- built-in
408
- `MemorySegmentCacheStore` and `CFCacheStore` index by tag. Invalidate on demand
409
- with `updateTag(...tags)` (awaitable, read-your-own-writes; for server actions) or
410
- `revalidateTag(...tags)` (background, non-blocking; for route handlers/webhooks).
411
- Both hard-purge; the difference is awaitability, not stale-serving. For
412
- `CFCacheStore`, distributed invalidation needs a `kv` namespace (markers live in
413
- that same namespace). The separate `revalidate()` export is the client-update axis
414
- (which segments re-render on a navigation or action), not a cache bust.
507
+ `"use cache"` reads and writes the app-level store from `createRouter({ cache })`
508
+ — the same store `cache()` boundaries and cached loaders use. One store, one
509
+ configuration. (`Static()`/`Prerender()` output is not in this store; it is
510
+ built into the server bundle, see `/prerender`.)
511
+
512
+ Entries are tagged by the profile's `tags` plus any runtime `cacheTag(...tags)`
513
+ calls in the function body. `cacheTag` has two forms: inside a `"use cache"`
514
+ function it tags that entry; called during a request render outside
515
+ `"use cache"` it tags what the render stores: the enclosing route `cache()`
516
+ entry, a cached loader's own entry when called in its body, and the `ppr`
517
+ shell or document-cache entry built from them (on a `ppr` route, only when
518
+ shell material calls it). See `/caching` → "Tag-Based Invalidation". All
519
+ built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`,
520
+ `VercelCacheStore`) index by tag. Invalidate on demand with
521
+ `updateTag(...tags)` (awaitable; for server actions) or
522
+ `revalidateTag(...tags)` (background, non-blocking; for route
523
+ handlers/webhooks). Both evict rather than mark stale, and the request that
524
+ calls either reads its own writes; `updateTag()` also waits for the durable
525
+ write. PPR shell reads on `CFCacheStore`/`VercelCacheStore` go through
526
+ per-isolate memos, which the invalidating user skips via the fresh-reads
527
+ cookie. For `CFCacheStore`, cross-colo invalidation needs a `kv` namespace
528
+ (markers live in that same namespace) or `tagPurge`. The separate
529
+ `revalidate()` export is the client-update control (which segments re-render
530
+ on a navigation or action), not a cache bust.
531
+
532
+ A route `cache()` entry stores the tags of the `"use cache"` reads inside it,
533
+ so `updateTag()` of one evicts the enclosing entry and any `ppr` shell or
534
+ document built from it. A loader's own `cache()` entry does the same for the
535
+ reads in its body. The exception is a `"use cache"` function called inside
536
+ another `"use cache"` function: its tags do not reach the enclosing entry, so
537
+ invalidating one of them neither evicts the outer entry nor keeps this request
538
+ from joining or storing an outer call that started before the invalidation
539
+ ([#980](https://github.com/rangojs/rango/issues/980)).
415
540
 
416
541
  ## Interaction with Other Caching
417
542
 
418
- | Mechanism | Granularity | When | Use case |
419
- | -------------------- | ------------------ | ---------- | ----------------------------------------------- |
420
- | `"use cache"` | Function/component | Runtime | Cache individual data fetches or components |
421
- | `cache()` DSL | Route segment | Runtime | Cache entire route subtrees with children |
422
- | `cache({ ttl })` DSL | Route segment | Runtime | Cache a route subtree with explicit options |
423
- | `Static()` | Route segment | Build-time | Render once, never re-render |
424
- | `Prerender()` | Route segment | Build-time | Pre-render known params, optional live fallback |
425
-
426
- ## Dev Mode
427
-
428
- In development, the Vite transform still wraps functions, but the cache store is
429
- a `MemorySegmentCacheStore` that works locally. Functions cache normally in dev
430
- for testing cache behavior.
543
+ | Mechanism | Granularity | When | Use case |
544
+ | ---------------- | ------------------ | ---------- | ----------------------------------------------------- |
545
+ | `"use cache"` | Function/component | Runtime | Cache individual data fetches or components |
546
+ | loader `cache()` | One loader's data | Runtime | Cache a loader result while the page stays live |
547
+ | `cache()` DSL | Route segment tree | Runtime | Cache entire route subtrees with children |
548
+ | `ppr` | HTML shell | Runtime | Serve a cached HTML shell, resume live holes (`/ppr`) |
549
+ | `Static()` | One segment | Build-time | Render once at build, no params |
550
+ | `Prerender()` | Route segment tree | Build-time | Pre-render known params, optional live fallback |
551
+
552
+ Inside a `ppr` shell, a `"use cache"` value that renders as shell material is
553
+ pinned at capture time for the life of that shell (see `/ppr` → Pitfalls). A
554
+ hole's own read (in a loader without `ssr: false` under `loading()` or an
555
+ inline `<Suspense>`) returns the current entry, not the shell's copy, unless a
556
+ loader the capture ran (an `ssr: false` loader, or one a handler awaits) read
557
+ the same entry.
558
+
559
+ ## Dev Mode and tests
560
+
561
+ The transform runs in dev exactly as in production, but caching only happens
562
+ when the router has a store: there is no implicit dev store. Configure one (a
563
+ `MemorySegmentCacheStore` is the usual dev/test choice) to see cache hits
564
+ locally.
565
+
566
+ With no item-capable store configured, a `"use cache"` function simply runs on
567
+ every call. The request-scoped guards (`cookies()`, `ctx.set()`, …) are not
568
+ active on that uncached path, so a guard violation only shows up once a store is
569
+ configured — test with a store. Under Vitest, `runLoader`, `renderHandler`, and
570
+ the other `@rangojs/router/testing` helpers take `{ cacheStore, cacheProfiles }`
571
+ and warn when a cached function ran without one (see `/testing`).
@@ -6,7 +6,11 @@ argument-hint:
6
6
 
7
7
  # Vercel deployment
8
8
 
9
- The `vercel` preset builds like the `node` preset (Vercel runs Node Functions, not Workers): rango owns the RSC entry, folds `process.env.NODE_ENV` for the SSR/RSC build, and after `vite build` assembles a `.vercel/output` directory (Build Output API v3) from `dist/` — a single streaming Node Function plus the static client assets.
9
+ This skill covers deploying a Rango app to Vercel Functions: the `vercel`
10
+ preset, function settings, the Runtime Cache store, host routers, tracing, and
11
+ local validation. For router options in general see `/router-setup`.
12
+
13
+ The `vercel` preset builds like the `node` preset (Vercel runs Node Functions, not Workers): rango owns the RSC entry (it imports the named `router` export of your `createRouter()` module), folds `process.env.NODE_ENV` for the SSR/RSC build, and after `vite build` assembles a `.vercel/output` directory (Build Output API v3) from `dist/`: a single streaming Node Function plus the static client assets.
10
14
 
11
15
  ## Deployment boundary
12
16
 
@@ -30,27 +34,27 @@ checklist before adding shared-cache headers.
30
34
  ## Setup
31
35
 
32
36
  ```bash
33
- npm install @vercel/functions
37
+ pnpm add @vercel/functions
34
38
  ```
35
39
 
36
40
  ```typescript
37
41
  // vite.config.ts
38
42
  import { defineConfig } from "vite";
39
- import react from "@vitejs/plugin-react";
40
43
  import { rango } from "@rangojs/router/vite";
41
44
 
42
45
  export default defineConfig({
43
- plugins: [react(), rango({ preset: "vercel" })],
46
+ plugins: [rango({ preset: "vercel" })], // add react() first if you use @vitejs/plugin-react
44
47
  });
45
48
  ```
46
49
 
47
50
  `@vercel/functions` is required: it backs the generated function launcher (`waitUntil`) and `VercelCacheStore`. The build fails with a clear error if it is missing.
48
51
 
49
- `vite build` produces `.vercel/output`; deploy with the Vercel CLI (`vercel deploy --prebuilt`) or via Git integration.
52
+ `vite build` produces `.vercel/output`; deploy with the Vercel CLI (`vercel deploy --prebuilt`) or via Git integration. The generated routing serves files first (hashed assets under the assets directory get `Cache-Control: public, max-age=31536000, immutable`) and sends everything else to the function.
50
53
 
51
54
  ## Function configuration
52
55
 
53
- Per-function knobs go under `vercel` and are written into `.vc-config.json`:
56
+ Per-function knobs go under `vercel` and are written into `.vc-config.json`
57
+ (`functionName` also sets the `config.json` route):
54
58
 
55
59
  ```typescript
56
60
  rango({
@@ -60,11 +64,15 @@ rango({
60
64
  maxDuration: 30, // seconds, default
61
65
  memory: 1024, // MB (platform default when omitted)
62
66
  regions: ["fra1"], // pin regions (platform default when omitted)
63
- functionName: "index", // the <name>.func dir + config.json route
67
+ functionName: "index", // the <name>.func dir + config.json route (default)
64
68
  },
65
69
  });
66
70
  ```
67
71
 
72
+ `runtime` must be a `nodejs*` runtime. The preset emits a Node serverless
73
+ function, so the build fails on any other value; the Edge runtime is not
74
+ supported.
75
+
68
76
  ## Runtime Cache
69
77
 
70
78
  `VercelCacheStore` wraps the Vercel Runtime Cache for segment, item, response,
@@ -72,6 +80,7 @@ and PPR shell families. Locally (no `process.env.VERCEL`) fall back to an
72
80
  in-memory store so dev/preview work without the platform:
73
81
 
74
82
  ```typescript
83
+ import { createRouter } from "@rangojs/router";
75
84
  import {
76
85
  MemorySegmentCacheStore,
77
86
  VercelCacheStore,
@@ -80,12 +89,24 @@ import { getCache, waitUntil } from "@vercel/functions";
80
89
 
81
90
  const defaults = { ttl: 60, swr: 300 };
82
91
  const memoryStore = new MemorySegmentCacheStore({ defaults });
92
+ // One handle per process: getCache() resolves the platform cache on every
93
+ // call, and VercelCacheStore keeps its PPR shell and tag-marker memos per
94
+ // handle. After updateTag(), the same process misses at once, the rest of the
95
+ // region once its marker memo refreshes (memo.markerMaxStaleMs, default 2000),
96
+ // and the mutating user at once (the fresh-reads cookie skips the memos).
97
+ // Another region serves a memoized shell until memo.shellMs (default 2000)
98
+ // passes, since the tag markers are regional and only expireTag is global. A
99
+ // platform expireTag issued outside rango writes no marker, so every memo
100
+ // serves the shell until its window passes.
101
+ const runtimeCache = process.env.VERCEL
102
+ ? getCache({ namespace: process.env.VERCEL_DEPLOYMENT_ID })
103
+ : undefined;
83
104
 
84
105
  function resolveCache() {
85
- if (process.env.VERCEL) {
106
+ if (runtimeCache) {
86
107
  return {
87
108
  store: new VercelCacheStore({
88
- cache: getCache({ namespace: process.env.VERCEL_DEPLOYMENT_ID }),
109
+ cache: runtimeCache,
89
110
  waitUntil,
90
111
  defaults,
91
112
  }),
@@ -97,7 +118,7 @@ function resolveCache() {
97
118
  export const router = createRouter({ cache: resolveCache }).routes(/* ... */);
98
119
  ```
99
120
 
100
- The cache factory receives `(env, ctx)`; on Vercel `env` is `process.env` and `ctx` is `{ waitUntil }`.
121
+ The cache factory receives `(env, ctx)`: on Vercel `env` is `process.env` and `ctx` is `{ waitUntil }`; off-platform `ctx` is `undefined`. Cache semantics and tag invalidation: `/caching`, `/cache-guide`.
101
122
 
102
123
  ## Host routers (multi-app)
103
124
 
@@ -121,7 +142,7 @@ hostRouter.host(["."]).lazy(() => import("./apps/site/handler.js"));
121
142
  export default hostRouter; // the instance
122
143
  ```
123
144
 
124
- `{ env, ctx }` is threaded unchanged from the function to each matched sub-app's handler and its `cache(env, ctx)` factory. See the `host-router` skill for sub-app structure and routing patterns.
145
+ `{ env, ctx }` is threaded unchanged from the function to each matched sub-app's handler and its `cache(env, ctx)` factory. See `/host-router` for sub-app structure and routing patterns.
125
146
 
126
147
  ## Tracing (custom spans)
127
148
 
@@ -138,12 +159,13 @@ registerOTel({ serviceName: "my-app" });
138
159
  export const tracing = createVercelTracing();
139
160
 
140
161
  // router.tsx — importing `tracing` runs instrumentation.ts (and registerOTel)
162
+ import { createRouter } from "@rangojs/router";
141
163
  import { tracing } from "./instrumentation.js";
142
164
  export const router = createRouter({ tracing }).routes(/* ... */);
143
165
  ```
144
166
 
145
- `createVercelTracing(opts?)` takes `{ enabled, spans, tracerName, tracer }` — same phase set as `createCloudflareTracing` (`rango.request/middleware/action/loader/render/ssr`). Caveats: Node-runtime only (Vercel custom spans are unsupported on Edge); `registerOTel()` must run before the first request; `@vercel/otel` is what unlocks Vercel Session Tracing + Trace Drains. The deploy bundles `@vercel/otel` and its `@opentelemetry/*` peers into the function (no `node_modules` at runtime), so they must be installed. See `examples/vercel-basic` for a worked hybrid setup and the `observability` skill for the cross-platform tracing model.
167
+ `createVercelTracing(opts?)` takes `{ enabled, spans, tracerName, tracer }` (`tracerName` defaults to `"rango"`; `tracer` overrides the global tracer) and emits the same phases as `createCloudflareTracing`: `rango.request`, `rango.middleware`, `rango.action`, `rango.loader`, `rango.handler`, `rango.render`, `rango.ssr`, `rango.response`, and `rango.background`. Caveats: Node-runtime only (Vercel custom spans are unsupported on Edge); `registerOTel()` must run before the first request; `@vercel/otel` is what unlocks Vercel Session Tracing + Trace Drains. The deploy bundles `@vercel/otel` and its `@opentelemetry/*` peers into the function (no `node_modules` at runtime), so they must be installed. The Rango repository's `examples/vercel-basic` (not shipped in this package) has a worked setup; `/observability` covers the cross-platform tracing model.
146
168
 
147
169
  ## Local validation without deploying
148
170
 
149
- `vite preview` serves the static client assets only. To preview the RSC **function**, serve the assembled `.vercel/output` behind filesystem-then-function routing — `examples/vercel-basic/scripts/preview.mjs` does this (and `pnpm preview:vercel` runs it). For a faithful deploy test (isolated filesystem, ESM, self-contained bundle), `examples/vercel-basic/scripts/smoke.mjs` serves it from a temp dir outside the repo. Both share `scripts/serve-vercel-output.mjs`.
171
+ `vite preview` does not run the assembled function. To exercise it locally, serve `.vercel/output` with filesystem-then-function routing (static files first, everything else to `functions/<name>.func/index.mjs`). The Rango repository's `examples/vercel-basic` (not shipped in this package) has two scripts for this: `scripts/preview.mjs` (`pnpm preview:vercel`) serves it in place, and `scripts/smoke.mjs` copies it to a temp dir outside the repo for a faithful deploy test (isolated filesystem, ESM, self-contained bundle). Both share `scripts/serve-vercel-output.mjs`.