@rangojs/router 0.15.1 → 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 (249) 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 +52 -0
  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 +36 -10
  32. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  33. package/dist/types/route-types.d.ts +3 -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/thenable-status.d.ts +9 -0
  67. package/dist/types/types/cache-types.d.ts +14 -10
  68. package/dist/types/types/handler-context.d.ts +14 -0
  69. package/dist/types/types/loader-types.d.ts +8 -6
  70. package/dist/types/urls/response-types.d.ts +3 -12
  71. package/dist/types/use-loader.d.ts +12 -3
  72. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  73. package/dist/types/vite/rango.d.ts +1 -1
  74. package/dist/vite/index.js +884 -768
  75. package/package.json +4 -4
  76. package/skills/api-client/SKILL.md +22 -10
  77. package/skills/breadcrumbs/SKILL.md +65 -22
  78. package/skills/bundle-analysis/SKILL.md +22 -22
  79. package/skills/cache-guide/SKILL.md +207 -97
  80. package/skills/caching/SKILL.md +295 -93
  81. package/skills/catalog.json +16 -16
  82. package/skills/client-urls/SKILL.md +53 -31
  83. package/skills/cloudflare/SKILL.md +72 -20
  84. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  85. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  86. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  87. package/skills/comparison/SKILL.md +5 -0
  88. package/skills/comparison/references/framework-comparison.md +39 -33
  89. package/skills/composability/SKILL.md +38 -29
  90. package/skills/css/SKILL.md +10 -13
  91. package/skills/debug-manifest/SKILL.md +74 -31
  92. package/skills/defer-hydration/SKILL.md +2 -1
  93. package/skills/deployment-caching/SKILL.md +6 -1
  94. package/skills/document-cache/SKILL.md +92 -28
  95. package/skills/fonts/SKILL.md +16 -8
  96. package/skills/handler-use/SKILL.md +53 -17
  97. package/skills/hooks/SKILL.md +28 -20
  98. package/skills/hooks/data.md +43 -1
  99. package/skills/hooks/handle-and-actions.md +31 -16
  100. package/skills/hooks/navigation.md +39 -10
  101. package/skills/hooks/outlets.md +13 -6
  102. package/skills/hooks/state.md +37 -15
  103. package/skills/hooks/urls.md +7 -5
  104. package/skills/host-router/SKILL.md +64 -12
  105. package/skills/i18n/SKILL.md +60 -34
  106. package/skills/intercept/SKILL.md +161 -26
  107. package/skills/layout/SKILL.md +106 -50
  108. package/skills/links/SKILL.md +51 -19
  109. package/skills/loader/SKILL.md +242 -106
  110. package/skills/middleware/SKILL.md +119 -23
  111. package/skills/migrate-nextjs/SKILL.md +100 -45
  112. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  113. package/skills/migrate-react-router/SKILL.md +49 -38
  114. package/skills/migrate-react-router/component-migration.md +25 -6
  115. package/skills/migrate-react-router/data-and-actions.md +18 -5
  116. package/skills/migrate-react-router/route-mapping.md +12 -6
  117. package/skills/mime-routes/SKILL.md +12 -7
  118. package/skills/observability/SKILL.md +28 -14
  119. package/skills/parallel/SKILL.md +75 -39
  120. package/skills/ppr/SKILL.md +345 -150
  121. package/skills/prerender/SKILL.md +189 -100
  122. package/skills/rango/SKILL.md +65 -34
  123. package/skills/react-compiler/SKILL.md +6 -5
  124. package/skills/response-routes/SKILL.md +133 -25
  125. package/skills/route/SKILL.md +165 -60
  126. package/skills/router-setup/SKILL.md +418 -240
  127. package/skills/scripts/SKILL.md +31 -20
  128. package/skills/server-actions/SKILL.md +220 -59
  129. package/skills/shell-manifest/SKILL.md +12 -7
  130. package/skills/streams-and-websockets/SKILL.md +32 -14
  131. package/skills/tailwind/SKILL.md +37 -33
  132. package/skills/testing/SKILL.md +68 -32
  133. package/skills/testing/cache-prerender.md +55 -29
  134. package/skills/testing/client-components.md +59 -6
  135. package/skills/testing/e2e-parity.md +12 -2
  136. package/skills/testing/flight.md +19 -11
  137. package/skills/testing/handles.md +8 -5
  138. package/skills/testing/loader.md +8 -5
  139. package/skills/testing/middleware.md +8 -3
  140. package/skills/testing/predicates.md +120 -0
  141. package/skills/testing/render-handler.md +70 -22
  142. package/skills/testing/response-routes.md +11 -4
  143. package/skills/testing/reverse-and-types.md +20 -14
  144. package/skills/testing/server-actions.md +12 -3
  145. package/skills/testing/server-tree.md +9 -0
  146. package/skills/testing/setup.md +9 -5
  147. package/skills/theme/SKILL.md +106 -29
  148. package/skills/typesafety/SKILL.md +20 -9
  149. package/skills/typesafety/env-and-bindings.md +126 -47
  150. package/skills/typesafety/generated-files-and-cli.md +26 -14
  151. package/skills/typesafety/params-and-search.md +44 -19
  152. package/skills/typesafety/route-types.md +50 -14
  153. package/skills/use-cache/SKILL.md +200 -82
  154. package/skills/vercel/SKILL.md +21 -11
  155. package/skills/view-transitions/SKILL.md +41 -20
  156. package/src/__augment-tests__/augmented.check.ts +26 -1
  157. package/src/browser/index.ts +2 -2
  158. package/src/browser/navigation-bridge.ts +3 -4
  159. package/src/browser/navigation-transaction.ts +22 -2
  160. package/src/browser/partial-update.ts +75 -80
  161. package/src/browser/react/ScrollRestoration.tsx +6 -6
  162. package/src/browser/rsc-router.tsx +1 -1
  163. package/src/browser/scroll-restoration.ts +53 -6
  164. package/src/cache/cache-exec-scope.ts +37 -4
  165. package/src/cache/cache-key-utils.ts +1 -6
  166. package/src/cache/cache-runtime.ts +230 -61
  167. package/src/cache/cache-scope.ts +76 -12
  168. package/src/cache/document-cache.ts +15 -0
  169. package/src/cache/handle-capture.ts +90 -10
  170. package/src/cache/handle-snapshot.ts +132 -28
  171. package/src/cache/segment-codec.ts +26 -7
  172. package/src/cache/tag-invalidation.ts +4 -3
  173. package/src/cache/taint.ts +1 -1
  174. package/src/cache/types.ts +16 -3
  175. package/src/client.rsc.tsx +10 -9
  176. package/src/client.tsx +5 -4
  177. package/src/components/DefaultDocument.tsx +7 -2
  178. package/src/context-var.ts +6 -5
  179. package/src/escape-script.ts +1 -1
  180. package/src/handles/MetaTags.tsx +13 -11
  181. package/src/handles/Scripts.tsx +11 -10
  182. package/src/handles/meta.ts +2 -2
  183. package/src/handles/script.ts +17 -16
  184. package/src/href-client.ts +1 -1
  185. package/src/html.ts +12 -0
  186. package/src/index.rsc.ts +5 -0
  187. package/src/index.ts +1 -0
  188. package/src/loader-store.ts +106 -0
  189. package/src/prerender/build-shell-capture.ts +8 -11
  190. package/src/reverse.ts +44 -4
  191. package/src/route-definition/dsl-helpers.ts +134 -42
  192. package/src/route-definition/helpers-types.ts +36 -10
  193. package/src/route-definition/resolve-handler-use.ts +121 -6
  194. package/src/route-types.ts +2 -3
  195. package/src/router/debug-manifest.ts +10 -2
  196. package/src/router/error-handling.ts +51 -15
  197. package/src/router/handler-context.ts +34 -25
  198. package/src/router/intercept-resolution.ts +125 -39
  199. package/src/router/loader-resolution.ts +56 -11
  200. package/src/router/match-api.ts +12 -17
  201. package/src/router/match-middleware/background-revalidation.ts +161 -96
  202. package/src/router/match-middleware/cache-lookup.ts +93 -10
  203. package/src/router/match-middleware/cache-store.ts +17 -90
  204. package/src/router/middleware-types.ts +3 -5
  205. package/src/router/middleware.ts +52 -9
  206. package/src/router/prerender-match.ts +53 -19
  207. package/src/router/route-snapshot.ts +11 -1
  208. package/src/router/router-context.ts +2 -0
  209. package/src/router/router-interfaces.ts +25 -2
  210. package/src/router/router-options.ts +8 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -12
  212. package/src/router/segment-resolution/loader-cache.ts +205 -45
  213. package/src/router/segment-resolution/loader-mask.ts +12 -22
  214. package/src/router/segment-resolution/loader-snapshot.ts +6 -6
  215. package/src/router/segment-resolution/mask-nested.ts +7 -7
  216. package/src/router/segment-resolution/revalidation.ts +34 -6
  217. package/src/router/segment-wrappers.ts +4 -0
  218. package/src/rsc/render-pipeline.ts +3 -0
  219. package/src/rsc/response-route-handler.ts +11 -3
  220. package/src/rsc/rsc-rendering.ts +8 -45
  221. package/src/rsc/shell-capture-constants.ts +13 -0
  222. package/src/rsc/shell-capture.ts +111 -90
  223. package/src/rsc/ssr-setup.ts +14 -8
  224. package/src/rsc/types.ts +11 -0
  225. package/src/server/context.ts +88 -11
  226. package/src/server/cookie-store.ts +5 -4
  227. package/src/server/handle-store.ts +232 -7
  228. package/src/server/request-context.ts +35 -30
  229. package/src/ssr/index.tsx +40 -7
  230. package/src/ssr-suspension-warning.ts +2 -13
  231. package/src/static-handler.ts +4 -1
  232. package/src/testing/flight-runtime.d.ts +21 -0
  233. package/src/testing/generated-routes.ts +2 -2
  234. package/src/testing/internal/context.ts +1 -1
  235. package/src/testing/internal/flight-client-globals.ts +7 -5
  236. package/src/testing/render-route.tsx +85 -24
  237. package/src/testing/run-loader.ts +5 -1
  238. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  239. package/src/testing/vitest.ts +62 -1
  240. package/src/theme/ThemeProvider.tsx +14 -12
  241. package/src/theme/ThemeScript.tsx +3 -3
  242. package/src/thenable-status.ts +16 -0
  243. package/src/types/cache-types.ts +14 -10
  244. package/src/types/handler-context.ts +14 -0
  245. package/src/types/loader-types.ts +8 -6
  246. package/src/urls/response-types.ts +3 -23
  247. package/src/use-loader.tsx +24 -4
  248. package/src/vite/plugins/use-cache-transform.ts +118 -74
  249. package/src/vite/rango.ts +7 -3
@@ -1,15 +1,55 @@
1
1
  ---
2
2
  name: cache-guide
3
- 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".
3
+ 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".
4
4
  argument-hint:
5
5
  ---
6
6
 
7
- # cache() vs "use cache" — When to Use Which
8
-
9
- Both mechanisms share the same backing store and cache profiles, and both accept
10
- an optional `tags` field (honored by the built-in stores — invalidate with
11
- `updateTag`/`revalidateTag`; see "Two axes" below). They differ in scope, cache
12
- key, execution model, and runtime control.
7
+ # Choosing a cache layer
8
+
9
+ Start here when you are not sure which cache to use. The first section maps
10
+ every layer; the rest of the page compares the two you will reach for most,
11
+ the `cache()` DSL and the `"use cache"` directive.
12
+
13
+ ## The layers at a glance
14
+
15
+ Each layer stores a more "cooked" artifact than the one above it, and keeps less
16
+ of the request live on a hit. The runtime layers share the app-level store
17
+ (`createRouter({ cache })`) and one tag system (`cacheTag` / `updateTag` /
18
+ `revalidateTag`); `Prerender()`/`Static()` output is built into the server
19
+ bundle instead.
20
+
21
+ | Layer | Declared with | Stores | Still runs on a hit | Skill |
22
+ | -------------------- | ---------------------------------------------- | ------------------------------------- | ---------------------------------------------------- | --------------------- |
23
+ | Function / component | `"use cache"` | one function's return value | everything around the call | `/use-cache` |
24
+ | Loader data | `loader(L, () => [cache({...})])` | one loader's result | other loaders, handlers, rendering | `/caching`, `/loader` |
25
+ | Segments, runtime | `cache({...}, () => [...])` | rendered Flight segments of a subtree | middleware, segments above it, loaders, HTML render | `/caching` |
26
+ | Segments, build time | `Prerender()` / `Static()` | Flight segments rendered at build | middleware, loaders, HTML render | `/prerender` |
27
+ | HTML shell | `ppr` path option | HTML prelude + React postponed state | middleware, handlers, loaders; only the holes resume | `/ppr` |
28
+ | Whole response (app) | `createDocumentCacheMiddleware()` + `s-maxage` | final response in the app store | middleware above it; nothing below | `/document-cache` |
29
+ | Whole response (CDN) | `Cache-Control: s-maxage` read by the platform | final response outside the app | nothing — the app is not invoked | `/deployment-caching` |
30
+
31
+ Quick rules:
32
+
33
+ - Expensive **data** used in several places → `"use cache"` on the fetch.
34
+ - Expensive **rendering** of a route subtree → `cache()`.
35
+ - Content fixed at **build time** (known params, files on disk) → `Prerender()`.
36
+ - **Instant first byte** of HTML while parts stay per-request → `ppr`.
37
+ - **Whole response** public and identical for everyone → `/document-cache` or
38
+ CDN caching (`/deployment-caching`).
39
+ - A value that must be fresh on **every** request → a loader (never cached
40
+ unless you opt in).
41
+
42
+ The layers compose: `"use cache"` inside a `cache()` subtree, a cached loader
43
+ under a `cache()` boundary, `cache()` segments replayed inside a `ppr` shell
44
+ capture, or `Prerender()` + `ppr` for a shell baked at build time.
45
+
46
+ ## cache() vs "use cache"
47
+
48
+ Both mechanisms use the same app-level store, and both can be tagged
49
+ (`cache({ tags })`; profile `tags` or runtime `cacheTag()` for `"use cache"`) and
50
+ invalidated with `updateTag`/`revalidateTag` — see "Two axes" below. Named
51
+ cache profiles apply to `"use cache"` only. They differ in scope, cache key,
52
+ execution model, and runtime control.
13
53
 
14
54
  ## Two axes — do not conflate
15
55
 
@@ -22,8 +62,8 @@ caching:
22
62
  Entries expire by **TTL/SWR** and can be tagged (`cache({ tags })` or runtime
23
63
  `cacheTag(...tags)` — inside `"use cache"` it tags that entry; called during a
24
64
  request render outside `"use cache"` it tags the document/shell artifact).
25
- Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`)
26
- index by tag; invalidate on demand with `updateTag(...tags)` (awaitable,
65
+ All built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`,
66
+ `VercelCacheStore`) index by tag; invalidate on demand with `updateTag(...tags)` (awaitable,
27
67
  read-your-own-writes) or `revalidateTag(...tags)` (background, non-blocking).
28
68
  Both hard-purge; the difference is awaitability, not stale-serving.
29
69
  2. **Client-update selection** — _should this segment re-run and stream to the
@@ -36,7 +76,7 @@ update, and `revalidate()` never reads, writes, or expires a cached value. If yo
36
76
  know React Router, `revalidate()` is `shouldRevalidate`, not `Cache-Control`. See
37
77
  `/rango` → "Coming from another framework" for the cross-framework mapping.
38
78
 
39
- ## Fast choice
79
+ ## Fast choice: cache() or "use cache"
40
80
 
41
81
  Read this first; use the rest of the page when the choice has edge cases.
42
82
 
@@ -62,20 +102,22 @@ protection the framework already gives you (or assume one it deliberately
62
102
  doesn't).
63
103
 
64
104
  There are two guard models to keep separate. Both block response side effects
65
- (`ctx.header()`, cookie writes) that would be lost on a hit; they differ in what
105
+ (`ctx.headers.set()`, cookie writes) that would be lost on a hit; they differ in what
66
106
  else they allow:
67
107
 
68
108
  - **`cache()` boundary guard** (route-level) — fires while the handler runs on a
69
109
  miss. `cookies()` and `headers()` throw (request-scoped data would be baked into
70
110
  the shared cached shell), `ctx.get(nonCacheableVar)` throws (a tainted value
71
- would be baked in), and response side effects (`ctx.header()`, `ctx.setCookie()`,
72
- `ctx.setStatus()`, `ctx.onResponse()`) throw. `ctx.set()` of a cacheable var is
111
+ would be baked in), and response side effects (`ctx.headers.set()`,
112
+ `setCookie()`, `setStatus()`, `onResponse()`) throw. `ctx.set()` of a cacheable var is
73
113
  **allowed** — children are cached too and can read it. **Loaders are exempt**
74
114
  (they always run fresh) — read request data inside a loader.
75
115
  - **`"use cache"` exec-guard** (function-level) — the same request-scoped APIs
76
116
  throw inside the cached function (`cookies()`, `headers()`, `ctx.set()`,
77
- `ctx.header()`); additionally, tainted `ctx`/`env`/`req` args are excluded from
78
- the cache key.
117
+ `ctx.headers.set()` and other response writes); additionally, tainted
118
+ `ctx`/`env`/`req` args are excluded from the cache key. The guard runs only on
119
+ the cached path: with no item-capable store configured the function runs
120
+ uncached and nothing throws.
79
121
 
80
122
  ### Cross-deploy safety: version-segmented store keys
81
123
 
@@ -94,7 +136,9 @@ deliberately: accept it (correctness over hit-rate), or split the policy — let
94
136
  the render/edge cache auto-version while a separate data store gets a stable
95
137
  `version` so its entries survive deploys. (Per-process stores like
96
138
  `MemorySegmentCacheStore` are cold on every restart anyway; this matters for
97
- persistent stores.) See `/caching` for store setup.
139
+ persistent stores.) `VercelCacheStore` does not version automatically: pass a
140
+ per-deploy `getCache({ namespace })` or its `version` option (see `/vercel`).
141
+ See `/caching` for store setup.
98
142
 
99
143
  ### Client cache: forward/back is mutation-aware
100
144
 
@@ -110,9 +154,10 @@ solved on the partial-render axis.
110
154
 
111
155
  `createVar({ cache: false })` (or a `ctx.set(var, v, { cache: false })` write)
112
156
  taints a value as request-scoped; reading it **directly** with `ctx.get()` inside
113
- a `cache()` boundary throws — the guard against the catastrophic "serve user A's
114
- data to user B" bug. The guarantee is precise and intentionally narrow — see
115
- "Context Variable Cache Safety" below for exactly what it does and does not catch.
157
+ a `cache()` boundary or a `"use cache"` function throws — the guard against the
158
+ catastrophic "serve user A's data to user B" bug. The guarantee is precise and
159
+ intentionally narrow — see "Context Variable Cache Safety" below for exactly
160
+ what it does and does not catch.
116
161
 
117
162
  ## Stale-while-revalidate
118
163
 
@@ -139,40 +184,52 @@ recompute for a merely-aging entry.
139
184
  SWR softens normal TTL expiry, **not** a cross-deploy cold cache — a new build
140
185
  has no stale entry to serve (see version-segmented store keys above).
141
186
 
142
- Store support is layer-specific. `CFCacheStore` supports SWR for segment,
143
- document/response, and `"use cache"` item entries. `MemorySegmentCacheStore`
144
- supports SWR for response and `"use cache"` item entries, but its route-segment
145
- entries expire at TTL and never background-revalidate. Use the memory store for
187
+ Store support is layer-specific. `CFCacheStore` and `VercelCacheStore` support
188
+ SWR for segment, document/response, item (`"use cache"` and cached loaders), and
189
+ PPR shell entries. `MemorySegmentCacheStore` supports SWR for response, item,
190
+ and shell entries, but its route-segment entries expire at `ttl` (the `swr`
191
+ window is dropped) and never background-revalidate. Use the memory store for
146
192
  local/dev behavior, not as proof that segment SWR is active.
147
193
 
148
194
  ## Key Differences
149
195
 
150
- | | `cache()` DSL | `"use cache"` directive |
151
- | -------------------- | ----------------------------------------------------- | -------------------------------------------------- |
152
- | **Scope** | Route segment tree (handler + children + parallels) | Single function return value |
153
- | **Defined at** | Route definition site (`urls.ts`) | Inside function body or at file top |
154
- | **Cache key** | Request type + pathname + params (+ optional custom) | Function identity + serialized non-tainted args |
155
- | **Execution on hit** | All-or-nothing: entire handler skipped | Partial: function body skipped, calling code runs |
156
- | **Runtime control** | `condition` to disable, custom `key` function | None — if the directive is present, it caches |
157
- | **Side effects** | Response side effects throw inside the boundary | `ctx.header()`, `ctx.set()`, etc. throw at runtime |
158
- | **Handle data** | Captured and replayed | Captured and replayed |
159
- | **Loaders** | Always fresh — excluded from cache, opt-in per loader | Can be used inside loaders |
160
- | **Nesting** | Nest `cache()` boundaries with different TTLs | Compose by calling cached functions from uncached |
196
+ | | `cache()` DSL | `"use cache"` directive |
197
+ | -------------------- | ----------------------------------------------------- | ------------------------------------------------- |
198
+ | **Scope** | Route segment tree (handler + children + parallels) | Single function return value |
199
+ | **Defined at** | Route definition site (`urls.ts`) | Inside function body or at file top |
200
+ | **Cache key** | Request type + host + pathname + params + search | Function identity + serialized non-tainted args |
201
+ | **Execution on hit** | All-or-nothing: entire handler skipped | Partial: function body skipped, calling code runs |
202
+ | **Runtime control** | `condition` to disable, custom `key` function | None — if the directive is present, it caches |
203
+ | **Side effects** | Response side effects throw inside the boundary | `ctx.headers.set()`, `ctx.set()`, etc. throw |
204
+ | **Handle data** | Handler pushes replayed; loader pushes re-run live | Captured and replayed when it receives `ctx` |
205
+ | **Loaders** | Always fresh — excluded from cache, opt-in per loader | Can be used inside loaders |
206
+ | **Nesting** | Nest `cache()` boundaries with different TTLs | Compose by calling cached functions from uncached |
161
207
 
162
208
  ### cache() Cache Key
163
209
 
164
- The key is `{requestType}:{pathname}:{params}` where requestType is one of
165
- `doc:`, `partial:`, or `intercept:`. This means the same URL cached separately
166
- for full document loads, client navigations, and intercept navigations.
210
+ The default key is `{requestType}:{host}{pathname}[:params][?search]`, where
211
+ requestType is `doc`, `partial`, or `intercept`. The same URL is therefore cached
212
+ separately for full document loads, client navigations, and intercept
213
+ navigations. The host keeps tenants apart when one deployment serves several
214
+ domains; the search part is sorted, excludes the router's internal params, and
215
+ honors `createRouter({ cache: { searchParams } })`.
167
216
 
168
- Custom `key` functions can segment the cache further (e.g., by user role or locale).
169
- `condition` can disable caching entirely at runtime (e.g., skip for authenticated users).
217
+ A custom `key` function replaces the whole default key (e.g., to key by user role
218
+ or locale); it also bypasses the store's `keyGenerator` and the search-param
219
+ filter. `condition` can disable caching entirely at runtime (e.g., skip for
220
+ authenticated users).
170
221
 
171
222
  ### "use cache" Cache Key
172
223
 
173
224
  The key is `use-cache:{functionId}:{serializedArgs}` where functionId is a stable
174
- ID from the Vite transform (module path + export name) and args are serialized via
175
- RSC `encodeReply()`. Tainted arguments (ctx, env, req) are excluded.
225
+ ID from the Vite transform (module path + export name) and args are serialized
226
+ (stable JSON when every arg is JSON-safe, RSC `encodeReply()` otherwise).
227
+ Request-scoped arguments are excluded: route fields read off `ctx` (handler,
228
+ loader or middleware: host, route name, pathname, params, search) and a
229
+ `Request`'s URL are folded in, and `env`
230
+ is left out. React element slots stay out of the key; any other argument that
231
+ cannot be serialized (a function, a class instance) runs the call uncached. See
232
+ `/use-cache`.
176
233
 
177
234
  ## Execution Model
178
235
 
@@ -180,17 +237,22 @@ This is the most important distinction.
180
237
 
181
238
  ### cache() — all-or-nothing
182
239
 
183
- On cache hit, the cache-lookup middleware short-circuits the entire pipeline.
184
- No handler code runs. On miss, all handlers execute normally and segments are
185
- stored.
240
+ On cache hit, the cache-lookup middleware short-circuits segment resolution for
241
+ the boundary: no handler inside it runs. Segments above the boundary are not in
242
+ the entry and resolve as on an uncached render. On miss, all handlers execute
243
+ normally and the boundary's segments are stored.
186
244
 
187
245
  ```
188
- HIT → cached segments served, loaders resolved fresh, no handler runs
189
- MISS → all handlers run, segments cached, response built normally
246
+ HIT → segments above the boundary resolved, cached segments served, loaders resolved fresh, no handler in the boundary runs
247
+ MISS → all handlers run, the boundary's segments cached, response built normally
190
248
  ```
191
249
 
192
- Headers, cookies, and ctx.set() calls inside handlers naturally don't execute on
193
- hit. There is no partial execution, so no runtime guards are needed.
250
+ `ctx.set()` calls are safe: every handler that could read the value is inside the
251
+ same cached unit, so a hit replays a consistent subtree. Response side effects
252
+ and request-scoped reads are not safe — a write would reach only MISS responses
253
+ and a `cookies()` read would be baked into the shared entry — so the boundary
254
+ guard throws on them even on a miss (see "Correctness & invalidation" above and
255
+ "Headers and Cookies" below).
194
256
 
195
257
  ### "use cache" — partial execution
196
258
 
@@ -203,11 +265,13 @@ HIT → function body skipped, calling code runs, handle data replayed
203
265
  MISS → function body runs, return value + handle data cached
204
266
  ```
205
267
 
206
- Runtime guards throw if you call cookies(), headers(), ctx.header(), ctx.set(),
207
- ctx.onResponse(), ctx.setTheme(), or ctx.setLocationState() inside a "use cache"
208
- function. cookies() and headers() are blocked because per-request data is not in the
209
- cache key. Side-effect methods are blocked because their effects are lost on hit.
210
- Use ctx.use(Handle) instead for data — handle data is captured and replayed.
268
+ Runtime guards throw if you call `cookies()`, `headers()`, `ctx.set()`,
269
+ `ctx.headers.set()` (or any response write: cookie writes, `setStatus()`,
270
+ `onResponse()`), `ctx.setTheme()`, or `ctx.setLocationState()` inside a
271
+ `"use cache"` function. `cookies()` and `headers()` are blocked because
272
+ per-request data is not in the cache key. Side-effect methods are blocked because
273
+ their effects are lost on hit. Use `ctx.use(Handle)` instead for data — handle
274
+ data is captured and replayed.
211
275
 
212
276
  ## When to Use cache()
213
277
 
@@ -250,10 +314,11 @@ async function getProductData(slug: string) {
250
314
  return await db.query("SELECT * FROM products WHERE slug = ?", [slug]);
251
315
  }
252
316
 
253
- // Handler calls cached function, sets headers outside
317
+ // Handler calls cached function, sets headers outside it
318
+ // (this route is not inside a cache() boundary or a ppr route)
254
319
  async function ProductPage(ctx) {
255
320
  const data = await getProductData(ctx.params.slug);
256
- ctx.header("X-Product", data.id);
321
+ ctx.headers.set("X-Product", data.id);
257
322
  return <Product data={data} />;
258
323
  }
259
324
  ```
@@ -307,13 +372,15 @@ cache hits per layer, so the actual per-request behavior is observable.
307
372
  Neither mechanism caches response headers or cookies.
308
373
 
309
374
  - **cache()**: Response-level side effects throw inside the cache boundary even
310
- on a miss: `ctx.header()`, `ctx.setCookie()`, `ctx.deleteCookie()`,
311
- `ctx.setStatus()`, `ctx.onResponse()`, and direct `ctx.headers` mutation. On a
312
- hit the handler would be skipped, so allowing the write on a miss would produce
313
- inconsistent responses. If you need headers or cookies on every response, set
314
- them in middleware or a live segment outside the cache boundary.
315
- - **"use cache"**: cookies() and headers() throw inside the cached function
316
- (both reads and writes). ctx.header() also throws. Move them outside.
375
+ on a miss: `ctx.headers` mutation (`ctx.headers.set()` etc.), `cookies()`
376
+ (read or write), and the request-context writers `header()`, `setCookie()`,
377
+ `deleteCookie()`, `setStatus()`, `onResponse()`. On a hit the handler would be
378
+ skipped, so allowing the write on a miss would produce inconsistent responses.
379
+ Registered loaders are exempt (they run on every request, hits included). If
380
+ you need headers or cookies on every response, set them in middleware, a live
381
+ segment outside the cache boundary, or a loader.
382
+ - **"use cache"**: `cookies()` and `headers()` throw inside the cached function
383
+ (both reads and writes), and so do `ctx.headers` mutations. Move them outside.
317
384
 
318
385
  ```typescript
319
386
  // Set headers that must appear on every response in middleware
@@ -327,8 +394,9 @@ middleware(async (ctx, next) => {
327
394
 
328
395
  Context variables created with `createVar()` are cacheable by default and can
329
396
  be read freely inside cached scopes. A non-cacheable var throws when read
330
- **directly** with `ctx.get()` inside a `cache()` boundary — where the value would
331
- otherwise be serialized into the stored segment.
397
+ **directly** with `ctx.get()` inside a `cache()` boundary or a `"use cache"`
398
+ function — where the value would otherwise be serialized into the stored
399
+ segment or entry without being part of its key.
332
400
 
333
401
  There are two ways to mark a value as non-cacheable:
334
402
 
@@ -351,7 +419,7 @@ specifies `cache: false`, the value is non-cacheable.
351
419
  | `ctx.get(cacheableVar)` | Allowed |
352
420
  | `ctx.get(nonCacheableVar)` | Throws (would be baked in) |
353
421
  | `ctx.set(var, value)` (cacheable) | Allowed |
354
- | `ctx.header()` / cookie writes | Throws (response side effect would be lost on hit) |
422
+ | `ctx.headers.set()` / cookie writes | Throws (response side effect would be lost on hit) |
355
423
  | Any of the above **inside a loader** | Allowed (loaders always run fresh) |
356
424
 
357
425
  (Both scopes block the same request-scoped APIs — `cookies()`, `headers()`,
@@ -359,16 +427,20 @@ response side effects, and non-cacheable `ctx.get()` — because each would leak
359
427
  per-request data into a shared cache entry. The `cache()` boundary tracks the
360
428
  scope via `isInsideCacheScope()`; `"use cache"` uses the exec guard and also
361
429
  excludes tainted `ctx`/`env`/`req` args from the cache key. Loaders are exempt in
362
- both — see "Headers and Cookies" and the precise guarantee below.)
430
+ both — see "Headers and Cookies" and the precise guarantee below. Under `"use
431
+ cache"` the exemption covers a loader body the cached function consumes
432
+ (`await ctx.use(Loader)`), not a cached function a loader calls: the loader
433
+ re-runs on every request, the cached body does not.)
363
434
 
364
435
  Write is dumb — `ctx.set()` stores the cache metadata but does not enforce.
365
436
  Enforcement happens at read time (`ctx.get()`), where ALS detects the cache
366
437
  scope and rejects non-cacheable reads.
367
438
 
368
- ### The guarantee is precise — a direct read inside `cache()`, not propagating
439
+ ### The guarantee is precise — a direct read inside a cache scope, not propagating
369
440
 
370
441
  The guard fires on a **direct** `ctx.get(taintedVar)` **inside a `cache()`
371
- boundary** (the scope `isInsideCacheScope` detects). The taint lives on the
442
+ boundary** (the scope `isInsideCacheScope` detects) **or a `"use cache"`
443
+ function body**. The taint lives on the
372
444
  variable; a value **derived** from it and read **outside** the boundary is not
373
445
  tracked:
374
446
 
@@ -396,10 +468,11 @@ layout((ctx) => {
396
468
  So do **not** read this as "you can't cache user data" — that overstates it and
397
469
  breeds the false confidence that makes the derived leak _more_ likely. The guard
398
470
  is deliberately non-propagating (propagation would cost a wrapper per derivation
399
- on the hot path), and it is scoped to the `cache()` segment boundary. `"use
400
- cache"` functions block the same request-scoped reads (`cookies()` / `headers()`
401
- throw inside them) and additionally exclude tainted `ctx`/`env`/`req` args from
402
- the cache key. The pattern that stays safe is also the natural one:
471
+ on the hot path), and it is scoped to the `cache()` segment boundary and the
472
+ `"use cache"` body. `"use cache"` functions block the same request-scoped reads
473
+ (`cookies()` / `headers()` and non-cacheable `ctx.get()` throw inside them) and
474
+ additionally exclude tainted `ctx`/`env`/`req` args from the cache key — pass a
475
+ non-cacheable value in as an argument so it becomes part of the key. The pattern that stays safe is also the natural one:
403
476
  **read tainted context at the point of use, in the path that needs it (a loader or
404
477
  live segment) — never extract user data into a plain value and cache that.**
405
478
  Loaders are exempt because they run outside the cache scope and resolve fresh
@@ -407,24 +480,26 @@ every request.
407
480
 
408
481
  ## Loaders Are Always Fresh
409
482
 
410
- Loaders are **never cached** by route-level `cache()`. Even on a full cache hit
411
- where all UI segments are served from cache, loaders are re-resolved fresh on
412
- every request. This is enforced at two levels:
483
+ Loaders are **never cached** by route-level `cache()`. Even on a cache hit
484
+ where the boundary's UI segments are served from cache, loaders are re-resolved
485
+ fresh on every request. This is enforced at two levels:
413
486
 
414
- 1. **Storage**: `cacheRoute()` filters out loader segments before serialization
415
- (`segments.filter(s => s.type !== "loader")`).
416
- 2. **Retrieval**: On cache hit, `resolveLoadersOnly()` runs after yielding cached
417
- UI segments, ensuring fresh data regardless of cache state.
487
+ 1. **Storage**: `cacheRoute()` filters out loader segments before serialization.
488
+ 2. **Retrieval**: On cache hit, `resolveLoadersOnly()` runs the boundary's
489
+ loaders after yielding its cached UI segments (the loaders above the boundary
490
+ run with their live segments), ensuring fresh data regardless of cache state.
418
491
 
419
492
  This means `cache()` gives you cached UI + fresh data by default. To also cache
420
493
  a loader's data, explicitly opt in with `loader(Fn, () => [cache({...})])`.
421
494
 
422
495
  ## cache() Placement Patterns
423
496
 
424
- ### Wrapping children of a path
497
+ ### cache() among a path's children
425
498
 
426
- An orphan `cache()` inside a path's children becomes the parent for all
427
- subsequent siblings. Everything below the cache boundary is cached as one unit:
499
+ A `cache()` among a path's children caches that path. The path's handler and
500
+ its own layouts and parallels are one cached unit, wherever the `cache()` sits
501
+ in the list. Every other item still attaches to the path, and the segments
502
+ above the path stay live:
428
503
 
429
504
  ```typescript
430
505
  path("/dashboard", DashboardPage, { name: "dashboard" }, () => [
@@ -433,34 +508,66 @@ path("/dashboard", DashboardPage, { name: "dashboard" }, () => [
433
508
  parallel("@stats", StatsPanel),
434
509
  parallel("@activity", ActivityFeed),
435
510
  ]),
511
+ loader(DashboardLoader),
436
512
  ]),
437
513
  ```
438
514
 
439
515
  On hit: DashboardPage, DashboardSidebar, StatsPanel, and ActivityFeed are all
440
- served from cache. On miss: all handlers run, all segments cached together.
516
+ served from cache, and DashboardLoader runs fresh. On miss: all handlers run
517
+ and the path's segments are cached together. DashboardPage runs inside the
518
+ boundary, so the guards apply to it: a header write there throws. The wrapper
519
+ form `cache({ ttl: 300 }, () => [layout(DashboardSidebar)])` inside a path
520
+ does the same, and `cache(false)` there opts the path out of an enclosing
521
+ `cache()`. The same form caches a response route:
522
+ `path.json("/api/feed", handler, { name: "feed" }, () => [cache({ ttl: 60 })])`.
523
+ A `cache()` inside a `layout()` or `middleware()` wrapper in the path caches
524
+ the path too: `layout(DashboardSidebar, () => [cache({ ttl: 300 })])` is the
525
+ same unit as the example above, not the sidebar alone.
526
+
527
+ ### Bare cache() among a layout's children
528
+
529
+ A `cache()` with no children callback covers the routes after it. The
530
+ `layout()`, `parallel()`, `loader()` and `middleware()` items after it still
531
+ wrap every route of the layout, including the routes before the `cache()`;
532
+ they are cached only with the routes after it:
533
+
534
+ ```typescript
535
+ layout(<AppShell />, () => [
536
+ path("/a", PageA, { name: "a" }), // PromoBanner renders live on /a
537
+ cache({ ttl: 60 }),
538
+ layout(<PromoBanner />),
539
+ path("/b", PageB, { name: "b" }), // PromoBanner cached with /b
540
+ ]),
541
+ ```
542
+
543
+ With no route after the `cache()`, nothing is cached. To cache
544
+ `<PromoBanner />` with PageA too, put the `cache()` before PageA's `path()`.
545
+ A `cache()` among PageA's own children does not cover it: the banner belongs
546
+ to the layout, above that path's boundary.
441
547
 
442
548
  ### Uncached layout with cached children
443
549
 
444
550
  The cache boundary only covers what's inside it. Parent segments above the
445
- boundary are not cached and always re-render:
551
+ boundary are not cached and always re-render, cache hits included:
446
552
 
447
553
  ```typescript
448
554
  layout(RootLayout, () => [
449
555
  // RootLayout is NOT cached — runs every request
450
- path("/products/:slug", ProductPage, { name: "product" }, () => [
451
- cache({ ttl: 300 }),
452
- layout(ProductSidebar),
453
- parallel("@reviews", ReviewsPanel),
454
- parallel("@related", RelatedProducts),
556
+ cache({ ttl: 300 }, () => [
557
+ path("/products/:slug", ProductPage, { name: "product" }, () => [
558
+ parallel("@reviews", ReviewsPanel),
559
+ parallel("@related", RelatedProducts),
560
+ ]),
455
561
  ]),
456
562
  ]),
457
563
  ```
458
564
 
459
- RootLayout renders fresh every request. ProductPage, ProductSidebar,
460
- ReviewsPanel, and RelatedProducts are all inside the cache boundary and
461
- served from cache on hit. This is useful when the root layout depends on
462
- request-specific data (user session, theme) but the product content is
463
- cacheable.
565
+ RootLayout renders fresh every request, and a header it writes lands on every
566
+ response. ProductPage, ReviewsPanel, and RelatedProducts are inside the cache
567
+ boundary and served from cache on hit. This is useful when the root layout
568
+ depends on request-specific data (user session, theme) but the product content
569
+ is cacheable. On a `ppr` route the whole chain bakes into the shell, so there
570
+ `cache()` covers RootLayout too.
464
571
 
465
572
  ### Loader-level caching
466
573
 
@@ -479,14 +586,17 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
479
586
  ```
480
587
 
481
588
  This attaches the cache config directly to the loader entry. The loader's
482
- data is cached independently from the route's segment cache. Loader caching
589
+ data is cached independently from the route's segment cache, together with
590
+ the handle pushes its body made (replayed on every hit). Loader caching
483
591
  supports custom keys, tags, SWR, conditional bypass, and per-loader store
484
592
  overrides — see `/loader` for the full reference.
485
593
 
486
594
  ## See Also
487
595
 
488
- - `/caching` — cache() DSL setup, stores, nested boundaries
596
+ - `/caching` — cache() DSL setup, stores, tags, nested boundaries
489
597
  - `/use-cache` — "use cache" directive details, profiles, transforms, guards
598
+ - `/loader` — loader-level caching and the loader context
599
+ - `/prerender` — build-time segments with `Prerender()`/`Static()`
600
+ - `/ppr` — PPR shell caching: cached HTML shell + live holes (different layer)
490
601
  - `/document-cache` — store-backed complete-response middleware
491
602
  - `/deployment-caching` — in-function versus external CDN cache boundaries
492
- - `/ppr` — PPR shell caching: cached HTML shell + live loader holes (different layer)