@rangojs/router 0.17.0 → 0.19.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 (293) hide show
  1. package/README.md +3 -2
  2. package/dist/types/browser/cookie-name.d.ts +6 -0
  3. package/dist/types/browser/event-controller.d.ts +3 -3
  4. package/dist/types/browser/history-state.d.ts +18 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +20 -2
  6. package/dist/types/browser/partial-update.d.ts +16 -1
  7. package/dist/types/browser/prefetch/cache.d.ts +6 -6
  8. package/dist/types/browser/react/Link.d.ts +19 -9
  9. package/dist/types/browser/react/context.d.ts +2 -2
  10. package/dist/types/browser/react/location-state-shared.d.ts +95 -56
  11. package/dist/types/browser/react/location-state.d.ts +1 -1
  12. package/dist/types/browser/transition-when.d.ts +80 -0
  13. package/dist/types/browser/types.d.ts +104 -9
  14. package/dist/types/build/generate-manifest.d.ts +8 -0
  15. package/dist/types/cache/background-task.d.ts +24 -2
  16. package/dist/types/cache/cache-exec-scope.d.ts +31 -0
  17. package/dist/types/cache/cache-key-utils.d.ts +48 -2
  18. package/dist/types/cache/cache-policy.d.ts +18 -1
  19. package/dist/types/cache/cache-runtime.d.ts +7 -0
  20. package/dist/types/cache/cache-scope.d.ts +192 -26
  21. package/dist/types/cache/cache-tag.d.ts +116 -11
  22. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  23. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  24. package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
  25. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  26. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  27. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
  28. package/dist/types/cache/document-cache.d.ts +10 -0
  29. package/dist/types/cache/handle-snapshot.d.ts +41 -8
  30. package/dist/types/cache/index.d.ts +1 -0
  31. package/dist/types/cache/invalidation-order.d.ts +23 -0
  32. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  33. package/dist/types/cache/request-tag-mask.d.ts +54 -0
  34. package/dist/types/cache/shell-memo.d.ts +178 -0
  35. package/dist/types/cache/shell-snapshot.d.ts +141 -105
  36. package/dist/types/cache/tag-invalidation.d.ts +54 -3
  37. package/dist/types/cache/types.d.ts +267 -96
  38. package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
  39. package/dist/types/client-urls/navigation.d.ts +28 -2
  40. package/dist/types/client-urls/server-projection.d.ts +5 -2
  41. package/dist/types/client-urls/types.d.ts +5 -5
  42. package/dist/types/client.d.ts +1 -1
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +1 -1
  45. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  46. package/dist/types/route-definition/helpers-types.d.ts +11 -11
  47. package/dist/types/route-name.d.ts +7 -0
  48. package/dist/types/router/error-handling.d.ts +1 -1
  49. package/dist/types/router/instrument.d.ts +8 -0
  50. package/dist/types/router/intercept-resolution.d.ts +2 -0
  51. package/dist/types/router/match-context.d.ts +5 -0
  52. package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
  53. package/dist/types/router/middleware-types.d.ts +6 -1
  54. package/dist/types/router/router-options.d.ts +4 -1
  55. package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
  56. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
  57. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
  58. package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
  59. package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
  60. package/dist/types/router/transition-when.d.ts +24 -10
  61. package/dist/types/rsc/attach-transition-when.d.ts +18 -0
  62. package/dist/types/rsc/full-payload.d.ts +21 -0
  63. package/dist/types/rsc/handler.d.ts +1 -1
  64. package/dist/types/rsc/response-cache-serve.d.ts +15 -12
  65. package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
  66. package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
  67. package/dist/types/rsc/shell-capture.d.ts +202 -74
  68. package/dist/types/rsc/shell-serve.d.ts +144 -50
  69. package/dist/types/rsc/types.d.ts +9 -0
  70. package/dist/types/segment-system.d.ts +7 -0
  71. package/dist/types/server/context.d.ts +155 -33
  72. package/dist/types/server/cookie-store.d.ts +33 -0
  73. package/dist/types/server/handle-store.d.ts +52 -1
  74. package/dist/types/server/request-context.d.ts +210 -63
  75. package/dist/types/server/request-headers.d.ts +44 -0
  76. package/dist/types/server.d.ts +1 -0
  77. package/dist/types/ssr/index.d.ts +9 -0
  78. package/dist/types/ssr/ssr-root.d.ts +9 -8
  79. package/dist/types/testing/e2e/index.d.ts +1 -1
  80. package/dist/types/testing/flight.entry.d.ts +7 -0
  81. package/dist/types/testing/index.d.ts +3 -2
  82. package/dist/types/testing/internal/action-id.d.ts +7 -0
  83. package/dist/types/testing/location-state-key.d.ts +24 -0
  84. package/dist/types/testing/render-route.d.ts +17 -1
  85. package/dist/types/testing/run-loader.d.ts +18 -3
  86. package/dist/types/testing/run-transition-when.d.ts +44 -69
  87. package/dist/types/testing/serve-shell-request.d.ts +133 -0
  88. package/dist/types/testing/shell-status.d.ts +44 -19
  89. package/dist/types/transition-when-ref.d.ts +79 -0
  90. package/dist/types/types/cache-types.d.ts +15 -3
  91. package/dist/types/types/handler-context.d.ts +14 -1
  92. package/dist/types/types/index.d.ts +1 -1
  93. package/dist/types/types/segments.d.ts +86 -71
  94. package/dist/types/urls/path-helper-types.d.ts +3 -4
  95. package/dist/types/urls/pattern-types.d.ts +21 -18
  96. package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
  97. package/dist/types/vite/router-discovery.d.ts +29 -2
  98. package/dist/vite/index.js +1005 -105
  99. package/package.json +1 -1
  100. package/skills/breadcrumbs/SKILL.md +17 -7
  101. package/skills/cache-guide/SKILL.md +175 -76
  102. package/skills/caching/SKILL.md +361 -33
  103. package/skills/catalog.json +1 -1
  104. package/skills/client-urls/SKILL.md +12 -7
  105. package/skills/cloudflare/SKILL.md +15 -0
  106. package/skills/comparison/references/framework-comparison.md +52 -37
  107. package/skills/defer-hydration/SKILL.md +50 -30
  108. package/skills/deployment-caching/SKILL.md +5 -2
  109. package/skills/document-cache/SKILL.md +32 -2
  110. package/skills/handler-use/SKILL.md +2 -0
  111. package/skills/hooks/SKILL.md +3 -1
  112. package/skills/hooks/handle-and-actions.md +3 -1
  113. package/skills/hooks/navigation.md +23 -2
  114. package/skills/hooks/state.md +101 -7
  115. package/skills/intercept/SKILL.md +29 -12
  116. package/skills/loader/SKILL.md +197 -44
  117. package/skills/middleware/SKILL.md +34 -9
  118. package/skills/migrate-nextjs/SKILL.md +68 -37
  119. package/skills/migrate-react-router/SKILL.md +2 -1
  120. package/skills/migrate-react-router/component-migration.md +3 -1
  121. package/skills/migrate-react-router/data-and-actions.md +3 -1
  122. package/skills/migrate-react-router/route-mapping.md +7 -4
  123. package/skills/observability/SKILL.md +65 -0
  124. package/skills/parallel/SKILL.md +13 -8
  125. package/skills/ppr/SKILL.md +715 -371
  126. package/skills/prerender/SKILL.md +30 -17
  127. package/skills/rango/SKILL.md +72 -34
  128. package/skills/route/SKILL.md +14 -7
  129. package/skills/router-setup/SKILL.md +4 -2
  130. package/skills/scripts/SKILL.md +8 -1
  131. package/skills/server-actions/SKILL.md +11 -0
  132. package/skills/shell-manifest/SKILL.md +21 -12
  133. package/skills/testing/SKILL.md +49 -43
  134. package/skills/testing/cache-prerender.md +15 -14
  135. package/skills/testing/client-components.md +45 -3
  136. package/skills/testing/loader.md +19 -1
  137. package/skills/testing/middleware.md +1 -0
  138. package/skills/testing/predicates.md +49 -49
  139. package/skills/testing/render-handler.md +1 -0
  140. package/skills/testing/serve-shell-request.md +202 -0
  141. package/skills/testing/server-actions.md +9 -3
  142. package/skills/theme/SKILL.md +56 -4
  143. package/skills/typesafety/env-and-bindings.md +18 -2
  144. package/skills/use-cache/SKILL.md +91 -23
  145. package/skills/vercel/SKILL.md +14 -2
  146. package/skills/view-transitions/SKILL.md +150 -29
  147. package/src/__augment-tests__/augmented.check.ts +23 -0
  148. package/src/browser/cookie-name.ts +23 -0
  149. package/src/browser/event-controller.ts +4 -4
  150. package/src/browser/history-state.ts +75 -1
  151. package/src/browser/link-interceptor.ts +4 -0
  152. package/src/browser/navigation-bridge.ts +77 -18
  153. package/src/browser/navigation-client.ts +4 -3
  154. package/src/browser/navigation-store.ts +43 -1
  155. package/src/browser/navigation-transaction.ts +31 -11
  156. package/src/browser/partial-update.ts +146 -35
  157. package/src/browser/prefetch/cache.ts +6 -6
  158. package/src/browser/prefetch/fetch.ts +2 -2
  159. package/src/browser/react/Link.tsx +38 -33
  160. package/src/browser/react/NavigationProvider.tsx +2 -2
  161. package/src/browser/react/context.ts +2 -2
  162. package/src/browser/react/location-state-shared.ts +241 -49
  163. package/src/browser/react/location-state.ts +5 -2
  164. package/src/browser/rsc-router.tsx +9 -0
  165. package/src/browser/server-action-bridge.ts +84 -15
  166. package/src/browser/transition-when.ts +205 -0
  167. package/src/browser/types.ts +124 -10
  168. package/src/build/generate-manifest.ts +77 -1
  169. package/src/cache/background-task.ts +55 -2
  170. package/src/cache/cache-exec-scope.ts +48 -1
  171. package/src/cache/cache-key-utils.ts +62 -2
  172. package/src/cache/cache-policy.ts +31 -4
  173. package/src/cache/cache-runtime.ts +209 -129
  174. package/src/cache/cache-scope.ts +573 -69
  175. package/src/cache/cache-tag.ts +475 -19
  176. package/src/cache/cf/cf-base64.ts +36 -4
  177. package/src/cache/cf/cf-cache-constants.ts +12 -0
  178. package/src/cache/cf/cf-cache-store.ts +1094 -376
  179. package/src/cache/cf/cf-cache-types.ts +32 -4
  180. package/src/cache/cf/cf-shell-frame.ts +258 -0
  181. package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
  182. package/src/cache/document-cache.ts +53 -7
  183. package/src/cache/handle-snapshot.ts +53 -9
  184. package/src/cache/index.ts +2 -0
  185. package/src/cache/invalidation-order.ts +84 -0
  186. package/src/cache/isolate-tag-memo.ts +308 -0
  187. package/src/cache/request-tag-mask.ts +147 -0
  188. package/src/cache/shell-memo.ts +341 -0
  189. package/src/cache/shell-snapshot.ts +249 -277
  190. package/src/cache/tag-invalidation.ts +109 -18
  191. package/src/cache/types.ts +274 -102
  192. package/src/cache/vercel/vercel-cache-store.ts +561 -74
  193. package/src/client-urls/client-root.tsx +8 -4
  194. package/src/client-urls/client-urls.ts +8 -6
  195. package/src/client-urls/navigation.ts +55 -4
  196. package/src/client-urls/server-projection.ts +31 -10
  197. package/src/client-urls/types.ts +12 -5
  198. package/src/client.tsx +2 -0
  199. package/src/host/cookie-handler.ts +2 -1
  200. package/src/index.rsc.ts +3 -0
  201. package/src/index.ts +3 -0
  202. package/src/loader-redirect.tsx +7 -3
  203. package/src/prerender/build-shell-capture.ts +173 -110
  204. package/src/route-definition/dsl-helpers.ts +6 -3
  205. package/src/route-definition/helpers-types.ts +11 -11
  206. package/src/route-name.ts +12 -0
  207. package/src/router/content-negotiation.ts +4 -1
  208. package/src/router/error-handling.ts +5 -2
  209. package/src/router/handler-context.ts +42 -5
  210. package/src/router/instrument.ts +54 -2
  211. package/src/router/intercept-resolution.ts +30 -8
  212. package/src/router/loader-resolution.ts +89 -33
  213. package/src/router/logging.ts +5 -3
  214. package/src/router/match-api.ts +89 -42
  215. package/src/router/match-context.ts +5 -0
  216. package/src/router/match-handlers.ts +14 -29
  217. package/src/router/match-middleware/background-revalidation.ts +3 -5
  218. package/src/router/match-middleware/cache-lookup.ts +52 -19
  219. package/src/router/match-middleware/cache-store.ts +36 -10
  220. package/src/router/match-result.ts +1 -0
  221. package/src/router/middleware-types.ts +6 -1
  222. package/src/router/middleware.ts +20 -4
  223. package/src/router/navigation-snapshot.ts +6 -5
  224. package/src/router/prerender-match.ts +10 -0
  225. package/src/router/request-classification.ts +7 -4
  226. package/src/router/router-options.ts +4 -1
  227. package/src/router/segment-resolution/fresh.ts +18 -13
  228. package/src/router/segment-resolution/helpers.ts +4 -2
  229. package/src/router/segment-resolution/loader-cache.ts +366 -70
  230. package/src/router/segment-resolution/loader-mask.ts +3 -4
  231. package/src/router/segment-resolution/loader-snapshot.ts +215 -126
  232. package/src/router/segment-resolution/mask-nested.ts +161 -42
  233. package/src/router/segment-resolution/revalidation.ts +27 -13
  234. package/src/router/segment-resolution/view-transition-default.ts +6 -21
  235. package/src/router/telemetry.ts +6 -3
  236. package/src/router/transition-when.ts +34 -52
  237. package/src/router.ts +2 -1
  238. package/src/rsc/attach-transition-when.ts +47 -0
  239. package/src/rsc/full-payload.ts +47 -3
  240. package/src/rsc/handler.ts +24 -10
  241. package/src/rsc/loader-fetch.ts +2 -1
  242. package/src/rsc/manifest-init.ts +4 -0
  243. package/src/rsc/origin-guard.ts +8 -5
  244. package/src/rsc/progressive-enhancement.ts +10 -32
  245. package/src/rsc/response-cache-serve.ts +69 -78
  246. package/src/rsc/response-route-handler.ts +2 -2
  247. package/src/rsc/rsc-rendering.ts +712 -338
  248. package/src/rsc/server-action.ts +8 -20
  249. package/src/rsc/shell-build-manifest.ts +49 -29
  250. package/src/rsc/shell-capture-constants.ts +182 -12
  251. package/src/rsc/shell-capture.ts +1126 -612
  252. package/src/rsc/shell-serve.ts +331 -85
  253. package/src/rsc/ssr-setup.ts +3 -2
  254. package/src/rsc/types.ts +9 -0
  255. package/src/segment-system.tsx +40 -7
  256. package/src/server/context.ts +328 -72
  257. package/src/server/cookie-store.ts +184 -133
  258. package/src/server/handle-store.ts +245 -27
  259. package/src/server/request-context.ts +364 -100
  260. package/src/server/request-headers.ts +134 -0
  261. package/src/server.ts +4 -0
  262. package/src/ssr/index.tsx +96 -37
  263. package/src/ssr/ssr-root.tsx +9 -8
  264. package/src/testing/dispatch.ts +4 -7
  265. package/src/testing/e2e/index.ts +1 -0
  266. package/src/testing/flight.entry.ts +14 -0
  267. package/src/testing/index.ts +5 -0
  268. package/src/testing/internal/action-id.ts +25 -0
  269. package/src/testing/internal/context.ts +1 -1
  270. package/src/testing/location-state-key.ts +45 -0
  271. package/src/testing/render-handler.ts +1 -1
  272. package/src/testing/render-route.tsx +109 -16
  273. package/src/testing/run-client-revalidate.ts +3 -19
  274. package/src/testing/run-loader.ts +67 -5
  275. package/src/testing/run-transition-when.ts +109 -172
  276. package/src/testing/serve-shell-request.ts +381 -0
  277. package/src/testing/shell-status.ts +73 -47
  278. package/src/theme/ThemeProvider.tsx +7 -6
  279. package/src/transition-when-ref.ts +220 -0
  280. package/src/types/cache-types.ts +15 -3
  281. package/src/types/handler-context.ts +14 -1
  282. package/src/types/index.ts +3 -0
  283. package/src/types/segments.ts +94 -91
  284. package/src/urls/path-helper-types.ts +3 -4
  285. package/src/urls/pattern-types.ts +21 -18
  286. package/src/vite/discovery/discover-routers.ts +3 -0
  287. package/src/vite/discovery/shell-prerender-phase.ts +9 -2
  288. package/src/vite/discovery/virtual-module-codegen.ts +16 -6
  289. package/src/vite/plugins/transition-when-hoist.ts +1064 -0
  290. package/src/vite/rango.ts +2 -0
  291. package/src/vite/router-discovery.ts +89 -9
  292. package/dist/types/rsc/transition-gate.d.ts +0 -23
  293. package/src/rsc/transition-gate.ts +0 -86
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -103,13 +103,16 @@ Delivery follows the loader race model: a push that beats the handler barrier
103
103
  is in the SSR'd document; a push after a slow fetch streams and applies
104
104
  client-side (`useHandle` re-renders when it lands). To guarantee document
105
105
  delivery, register the loader as `loader(Def, { ssr: false })` —
106
- see `/loader` → "Writing Handles from Loaders". Under `cache()` loader crumbs
107
- are not stored with the cached segments; the loader re-pushes them on every
108
- hit, so they appear once even without an `href`. A loader with its own
109
- `cache()` keeps its crumbs on its own hits too: they are replayed from the
110
- loader's cache entry. A crumb from a loader it awaits appears once even when
111
- a sibling loader or the handler reads that loader too; that live run's crumb
112
- replaces the cached one in the trail.
106
+ see `/loader` → "Writing Handles from Loaders". On a `ppr` route the race is
107
+ decided at capture: a loader without `ssr: false` never runs there, so its
108
+ crumb is not in the shell and applies client-side on every HIT; with
109
+ `ssr: false` the crumb bakes into the shell (`/ppr` → "On a shell HIT").
110
+ Under `cache()` loader crumbs are not stored with the cached segments; the
111
+ loader re-pushes them on every hit, so they appear once even without an `href`.
112
+ A loader with its own `cache()` keeps its crumbs on its own hits too: they are
113
+ replayed from the loader's cache entry. A crumb from a loader it awaits appears
114
+ once even when a sibling loader or the handler reads that loader too; that live
115
+ run's crumb replaces the cached one in the trail.
113
116
 
114
117
  ## Async Content
115
118
 
@@ -134,6 +137,13 @@ path("/product/:id", async (ctx) => {
134
137
  Async content is a `Promise<ReactNode>`. Resolve it in your component
135
138
  with React's `use()` hook wrapped in `<Suspense>`.
136
139
 
140
+ A crumb the HANDLER pushes is handler output. In a route `cache()` entry and
141
+ in a `ppr` shell it is stored resolved: the cache write, or the shell capture
142
+ (within `ppr.captureTimeout`), waits for a `content` promise and for a
143
+ `.defer()` slot (below), and every HIT replays the crumb as it was resolved
144
+ then, without running the handler. For per-request crumb content, push it
145
+ from a loader without `ssr: false` (see "Pushing from Loaders").
146
+
137
147
  ### Deferred content (decide now, resolve from a deep component)
138
148
 
139
149
  When the handler should DECIDE to push a crumb early but the value is produced
@@ -18,15 +18,15 @@ of the request live on a hit. The runtime layers share the app-level store
18
18
  `revalidateTag`); `Prerender()`/`Static()` output is built into the server
19
19
  bundle instead.
20
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` |
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 + the handler layer | middleware and live loaders (no handler); 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
30
 
31
31
  Quick rules:
32
32
 
@@ -37,7 +37,10 @@ Quick rules:
37
37
  - **Whole response** public and identical for everyone → `/document-cache` or
38
38
  CDN caching (`/deployment-caching`).
39
39
  - A value that must be fresh on **every** request → a loader (never cached
40
- unless you opt in).
40
+ unless you opt in), read with `useLoader()`; on a `ppr` route, keep it off
41
+ `ssr: false`. A handler inside a `cache()` or `ppr` boundary that awaits
42
+ `ctx.use(Loader)` renders the value it got on the miss, and every hit
43
+ replays that output.
41
44
 
42
45
  The layers compose: `"use cache"` inside a `cache()` subtree, a cached loader
43
46
  under a `cache()` boundary, `cache()` segments replayed inside a `ppr` shell
@@ -53,25 +56,38 @@ execution model, and runtime control.
53
56
 
54
57
  ## Two axes — do not conflate
55
58
 
56
- Everything on this page is **axis 1: stored-value freshness** — _is a cached
57
- value still good?_ There is a second, orthogonal axis it is easy to mistake for
59
+ Everything on this page is **stored-value freshness** — _is a cached value
60
+ still good?_ There is a second, orthogonal concern it is easy to mistake for
58
61
  caching:
59
62
 
60
- 1. **Stored-value freshness** — _is a cached value still good?_
61
- → `"use cache"` (fn/component), `cache()` (segment), loader `cache()` (loader data).
62
- Entries expire by **TTL/SWR** and can be tagged (`cache({ tags })` or runtime
63
- `cacheTag(...tags)` — inside `"use cache"` it tags that entry; called during a
64
- request render outside `"use cache"` it tags the document/shell artifact).
65
- All built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`,
66
- `VercelCacheStore`) index by tag; invalidate on demand with `updateTag(...tags)` (awaitable,
67
- read-your-own-writes) or `revalidateTag(...tags)` (background, non-blocking).
68
- Both hard-purge; the difference is awaitability, not stale-serving.
63
+ 1. **Stored-value freshness** — _is a cached value still good?_ → `"use cache"`
64
+ (fn/component), `cache()` (segment), loader `cache()` (loader data). Entries
65
+ expire by **TTL/SWR** and can be tagged (`cache({ tags })` or runtime
66
+ `cacheTag(...tags)` — inside `"use cache"` it tags that entry; called during
67
+ a request render outside `"use cache"` it tags what the render stores: the
68
+ enclosing route `cache()` entry, a cached loader's own entry when called in
69
+ its body, and the `ppr` shell or document-cache entry built from them; on a
70
+ `ppr` route it tags the shell only when shell material calls it). All
71
+ built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`,
72
+ `VercelCacheStore`) index by tag; invalidate on demand with
73
+ `updateTag(...tags)` (awaitable) or `revalidateTag(...tags)` (background,
74
+ non-blocking). Both evict rather than mark stale, and the request that calls
75
+ either reads its own writes; `updateTag()` also waits for the durable write,
76
+ `revalidateTag()` leaves it in the background. `CFCacheStore` and
77
+ `VercelCacheStore` serve PPR shell reads through per-isolate memos: the
78
+ invalidating user skips them via the fresh-reads cookie, other users see the
79
+ invalidation once the memo refreshes and, on `CFCacheStore` with KV, once KV
80
+ propagates the marker to their colo (`/caching` → "Tag-Based Invalidation"
81
+ and "The fresh-reads cookie").
69
82
  2. **Client-update selection** — _should this segment re-run and stream to the
70
83
  client on this navigation/action?_
71
84
  → `revalidate()`. Covered in `/loader` and `/route`, **not here**.
72
85
 
73
86
  They are orthogonal and compose: a segment selected by `revalidate()` still
74
- consults its cache (hit → no recompute); a cache bust does **not** force a client
87
+ consults its cache (hit → no recompute), except that a server action's
88
+ revalidation render skips the route `cache()` lookup and re-renders those
89
+ segments fresh (a loader's own `cache()` and `"use cache"` are still read); a
90
+ cache bust does **not** force a client
75
91
  update, and `revalidate()` never reads, writes, or expires a cached value. If you
76
92
  know React Router, `revalidate()` is `shouldRevalidate`, not `Cache-Control`. See
77
93
  `/rango` → "Coming from another framework" for the cross-framework mapping.
@@ -107,18 +123,47 @@ else they allow:
107
123
 
108
124
  - **`cache()` boundary guard** (route-level) — fires while the handler runs on a
109
125
  miss. `cookies()` and `headers()` throw (request-scoped data would be baked into
110
- the shared cached shell), `ctx.get(nonCacheableVar)` throws (a tainted value
126
+ the shared cached shell), and so do the raw reads `ctx.request.headers` and
127
+ `getRequestContext().cookie()` / `.cookies()` (#976);
128
+ `ctx.get(nonCacheableVar)` throws (a tainted value
111
129
  would be baked in), and response side effects (`ctx.headers.set()`,
112
130
  `setCookie()`, `setStatus()`, `onResponse()`) throw. `ctx.set()` of a cacheable var is
113
131
  **allowed** — children are cached too and can read it. **Loaders are exempt**
114
- (they always run fresh) — read request data inside a loader.
132
+ (a route `cache()` does not store their values) — read request data inside a
133
+ loader.
134
+ - **Loader `cache()` guard** (a loader bound with its own `cache()`) — its value
135
+ is stored under a key that names no user, so a miss whose body read
136
+ `cookies()`, `headers()`, `ctx.request.headers`, the theme or a
137
+ non-cacheable `ctx.get()` fails
138
+ unless the binding has a `key()` or its store a `keyGenerator`. Either one
139
+ switches the check off, so put what the body reads in it (`/loader` →
140
+ "Cache Key").
115
141
  - **`"use cache"` exec-guard** (function-level) — the same request-scoped APIs
116
- throw inside the cached function (`cookies()`, `headers()`, `ctx.set()`,
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
142
+ throw inside the cached function (`cookies()`, `headers()`,
143
+ `ctx.request.headers`, `ctx.set()`, `ctx.headers.set()` and other response
144
+ writes); additionally, tainted `ctx`/`env`/`req` args are excluded from the
145
+ cache key (a `Request` argument keys by its URL only). The guard runs only
146
+ on the cached path: with no item-capable store configured the function runs
120
147
  uncached and nothing throws.
121
148
 
149
+ All three guards leave a cache's own callbacks alone: a `key()`, a store
150
+ `keyGenerator`, a `condition()` and a `tags()` function may read `cookies()`,
151
+ `headers()` or `ctx.request.headers`, because the value picks or labels the
152
+ entry rather than being rendered into it. So does `onError`, which only
153
+ observes. A `"use cache"` function or loader they call is guarded as usual.
154
+
155
+ `ctx.request.clone()` is guarded like `ctx.request.headers`. The remaining gap:
156
+ `fetch(ctx.request)` and `new Request(ctx.request)` don't throw and aren't
157
+ guarded. A fetch forwards the visitor's `Cookie` and `Authorization`, so its
158
+ response is per visitor; don't make one in a cached body.
159
+
160
+ The `ppr` shell capture has its own, stricter guard: `cookies()`, `headers()`,
161
+ `ctx.request.headers`, a theme read, a `{ cache: false }` variable read, and
162
+ `ctx.dynamic()` refuse the
163
+ capture anywhere it waits — handlers, promises they pass or push, async server
164
+ components, `ssr: false` loaders, and loaders a handler awaits (no loader
165
+ exemption there). The route then serves uncached; see `/ppr`.
166
+
122
167
  ### Cross-deploy safety: version-segmented store keys
123
168
 
124
169
  `CFCacheStore` prefixes every **physical** store key (the CF Cache API URL and
@@ -193,17 +238,17 @@ local/dev behavior, not as proof that segment SWR is active.
193
238
 
194
239
  ## Key Differences
195
240
 
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 |
241
+ | | `cache()` DSL | `"use cache"` directive |
242
+ | -------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------- |
243
+ | **Scope** | Route segment tree (handler + children + parallels) | Single function return value |
244
+ | **Defined at** | Route definition site (`urls.ts`) | Inside function body or at file top |
245
+ | **Cache key** | Request type + host + pathname + params + search | Function identity + serialized non-tainted args |
246
+ | **Execution on hit** | All-or-nothing: entire handler skipped | Partial: function body skipped, calling code runs |
247
+ | **Runtime control** | `condition` to disable, custom `key` function | None — if the directive is present, it caches |
248
+ | **Side effects** | Response side effects throw inside the boundary | `ctx.headers.set()`, `ctx.set()`, etc. throw |
249
+ | **Handle data** | Handler pushes replayed; loader pushes re-run live | Captured and replayed when it receives `ctx` |
250
+ | **Loaders** | Always fresh — excluded from cache, opt-in per loader | Can be used inside loaders |
251
+ | **Nesting** | Inner TTLs override; `key()` partitions compose | Compose by calling cached functions from uncached; inner tags reach the outer entry |
207
252
 
208
253
  ### cache() Cache Key
209
254
 
@@ -216,8 +261,18 @@ honors `createRouter({ cache: { searchParams } })`.
216
261
 
217
262
  A custom `key` function replaces the whole default key (e.g., to key by user role
218
263
  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).
264
+ filter. It runs once per request and may read `cookies()`. Its result is
265
+ stored namespaced (`key:` plus its URI encoding), so no value it returns, raw
266
+ request input included, names another route's record. A nested `cache()`
267
+ keys its records within the enclosing `key()` partition (`/caching` → "Keys
268
+ nest"). Normalize the result to the values you serve: each distinct value is
269
+ a record. On a `ppr` route, `key()` (and a store `keyGenerator` that returns
270
+ a non-default key) also partitions the shell: one shell per value, and a
271
+ partitioned request never reads a build-time shell. Keep the values to a
272
+ small set. `condition` can disable caching entirely at runtime (e.g., skip
273
+ for authenticated users), for every `cache()` nested under it too, and an
274
+ outer `cache()`'s `tags` tag the nested records (`/caching` → "Conditions
275
+ and tags inherit").
221
276
 
222
277
  ### "use cache" Cache Key
223
278
 
@@ -267,8 +322,9 @@ MISS → function body runs, return value + handle data cached
267
322
 
268
323
  Runtime guards throw if you call `cookies()`, `headers()`, `ctx.set()`,
269
324
  `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
325
+ `onResponse()`), `ctx.setTheme()`, or `ctx.setLocationState()`, or read
326
+ `ctx.request.headers`, inside a `"use cache"` function. `cookies()`,
327
+ `headers()` and `ctx.request.headers` are blocked because
272
328
  per-request data is not in the cache key. Side-effect methods are blocked because
273
329
  their effects are lost on hit. Use `ctx.use(Handle)` instead for data — handle
274
330
  data is captured and replayed.
@@ -294,6 +350,11 @@ export const urlpatterns = urls(({ path, cache }) => [
294
350
  ]);
295
351
  ```
296
352
 
353
+ On a `ppr` route, `cache(false)` or a `condition()` that returns false also
354
+ means no shell: that route (or that request) renders like a cache miss, with
355
+ no shell served or captured. A `ppr` route cannot opt out of a layout's
356
+ `cache()` and still get a shell.
357
+
297
358
  ## When to Use "use cache"
298
359
 
299
360
  Use the `"use cache"` directive when:
@@ -364,8 +425,20 @@ does **not** keep a value fresher than its parent:
364
425
  So `"use cache: short"` (60s) inside `cache({ ttl: 600 })` yields ~600s freshness
365
426
  on hits, **not** 60s. This is not a bug: setting `cache({ ttl: 600 })` declares
366
427
  "this subtree may be ~600s stale." **If a value must be fresher than its
367
- enclosing segment, put it in a loader** (always live). `debugPerformance` prints
368
- cache hits per layer, so the actual per-request behavior is observable.
428
+ enclosing segment, put it in a loader** read with `useLoader()` (without
429
+ `ssr: false` on a `ppr` route). `debugPerformance` prints cache hits per
430
+ layer, so the actual per-request behavior is observable.
431
+
432
+ Keys and tags cross the nesting too:
433
+
434
+ - An inner `cache()` without `key` keys its records within the outer `key()`
435
+ partition; one with its own `key` composes both (`/caching` → "Keys nest").
436
+ - Tags that `cacheTag()` calls and `"use cache"` reads record inside a
437
+ `cache()` subtree are stored on that `cache()` entry, so `updateTag()` of one
438
+ of them evicts it (and a `ppr` shell or document built from it) instead of
439
+ waiting for it to expire.
440
+ - A `ppr` shell never outlives the route `cache()` entry it was captured
441
+ from: its ttl and ttl + swr are capped to what that entry has left.
369
442
 
370
443
  ## Headers and Cookies
371
444
 
@@ -380,7 +453,12 @@ Neither mechanism caches response headers or cookies.
380
453
  you need headers or cookies on every response, set them in middleware, a live
381
454
  segment outside the cache boundary, or a loader.
382
455
  - **"use cache"**: `cookies()` and `headers()` throw inside the cached function
383
- (both reads and writes), and so do `ctx.headers` mutations. Move them outside.
456
+ (both reads and writes), and so do `ctx.request.headers` reads and
457
+ `ctx.headers` mutations. Move them outside.
458
+ - **`ctx.theme`** (handler and middleware) and **`getRequestContext().theme`**
459
+ are the visitor's theme cookie, so a read throws in both scopes, like
460
+ `cookies()`. Read it with `useTheme()` in a client component, or in a
461
+ loader (see the `/theme` skill).
384
462
 
385
463
  ```typescript
386
464
  // Set headers that must appear on every response in middleware
@@ -413,24 +491,27 @@ specifies `cache: false`, the value is non-cacheable.
413
491
 
414
492
  **Behavior inside a `cache()` boundary:**
415
493
 
416
- | Operation | Inside a `cache()` boundary |
417
- | ----------------------------------------- | ------------------------------------------------------ |
418
- | `cookies()` / `headers()` (read or write) | Throws (request-scoped, would poison the shared entry) |
419
- | `ctx.get(cacheableVar)` | Allowed |
420
- | `ctx.get(nonCacheableVar)` | Throws (would be baked in) |
421
- | `ctx.set(var, value)` (cacheable) | Allowed |
422
- | `ctx.headers.set()` / cookie writes | Throws (response side effect would be lost on hit) |
423
- | Any of the above **inside a loader** | Allowed (loaders always run fresh) |
494
+ | Operation | Inside a `cache()` boundary |
495
+ | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
496
+ | `cookies()` / `headers()` (read or write) | Throws (request-scoped, would poison the shared entry) |
497
+ | `ctx.request.headers`, `getRequestContext().cookie()` / `.cookies()` | Throws, like `headers()` / `cookies()` (#976) |
498
+ | `ctx.theme`, `getRequestContext().theme` | Throws, like `cookies()` (#971) |
499
+ | `ctx.get(cacheableVar)` | Allowed |
500
+ | `ctx.get(nonCacheableVar)` | Throws (would be baked in) |
501
+ | `ctx.set(var, value)` (cacheable) | Allowed |
502
+ | `ctx.headers.set()` / cookie writes | Throws (response side effect would be lost on hit) |
503
+ | Any of the above **inside a loader** | Allowed (loaders always run fresh) |
504
+ | The reads above in a loader with its own `cache()` | The miss fails without a `key()`; any `key()` allows them, so it must include them |
424
505
 
425
506
  (Both scopes block the same request-scoped APIs — `cookies()`, `headers()`,
426
507
  response side effects, and non-cacheable `ctx.get()` — because each would leak
427
508
  per-request data into a shared cache entry. The `cache()` boundary tracks the
428
509
  scope via `isInsideCacheScope()`; `"use cache"` uses the exec guard and also
429
- excludes tainted `ctx`/`env`/`req` args from the cache key. Loaders are exempt in
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.)
510
+ excludes tainted `ctx`/`env`/`req` args from the cache key. Loaders are exempt
511
+ under `cache()` — see "Headers and Cookies" and the precise guarantee below.
512
+ Under `"use cache"` nothing is exempt: a loader body the cached function
513
+ consumes (`await ctx.use(Loader)`) is part of the cached result, and a cached
514
+ function a loader calls does not re-run with the loader.)
434
515
 
435
516
  Write is dumb — `ctx.set()` stores the cache metadata but does not enforce.
436
517
  Enforcement happens at read time (`ctx.get()`), where ALS detects the cache
@@ -470,13 +551,18 @@ breeds the false confidence that makes the derived leak _more_ likely. The guard
470
551
  is deliberately non-propagating (propagation would cost a wrapper per derivation
471
552
  on the hot path), and it is scoped to the `cache()` segment boundary and the
472
553
  `"use cache"` body. `"use cache"` functions block the same request-scoped reads
473
- (`cookies()` / `headers()` and non-cacheable `ctx.get()` throw inside them) and
554
+ (`cookies()` / `headers()`, `ctx.request.headers` and non-cacheable `ctx.get()` throw inside them) and
474
555
  additionally exclude tainted `ctx`/`env`/`req` args from the cache key — pass a
475
556
  non-cacheable value in as an argument so it becomes part of the key. The pattern that stays safe is also the natural one:
476
557
  **read tainted context at the point of use, in the path that needs it (a loader or
477
558
  live segment) — never extract user data into a plain value and cache that.**
478
- Loaders are exempt because they run outside the cache scope and resolve fresh
479
- every request.
559
+ Loaders are exempt from the `cache()` guard because they run outside the cache
560
+ scope and resolve fresh every request. A loader with its own `cache()` does not:
561
+ its value is stored, so it falls under the loader `cache()` guard above. Nor
562
+ does a loader awaited inside a `"use cache"` body: its value is part of what the
563
+ function returns, so the same reads throw there when the cached function is
564
+ the first to read it in the request (an already-started loader's value is
565
+ stored unchecked).
480
566
 
481
567
  ## Loaders Are Always Fresh
482
568
 
@@ -489,8 +575,16 @@ fresh on every request. This is enforced at two levels:
489
575
  loaders after yielding its cached UI segments (the loaders above the boundary
490
576
  run with their live segments), ensuring fresh data regardless of cache state.
491
577
 
492
- This means `cache()` gives you cached UI + fresh data by default. To also cache
493
- a loader's data, explicitly opt in with `loader(Fn, () => [cache({...})])`.
578
+ This means `cache()` gives you cached UI + fresh data by default, where the
579
+ data is read outside the cached output: `useLoader()` in a client component.
580
+ A handler inside the boundary that awaits `ctx.use(Loader)` renders the value
581
+ it got on the miss, and every hit replays that output. On a `ppr` route an
582
+ `ssr: false` loader is the other exception: it bakes into the shell (`/ppr`).
583
+ To also cache a loader's data, explicitly opt in with
584
+ `loader(Fn, () => [cache({...})])`.
585
+ That entry is keyed by loader, host, path and params, not by the route's
586
+ `key()`, so give it a `key()` that includes any request data its body reads;
587
+ without one the miss fails.
494
588
 
495
589
  ## cache() Placement Patterns
496
590
 
@@ -513,16 +607,16 @@ path("/dashboard", DashboardPage, { name: "dashboard" }, () => [
513
607
  ```
514
608
 
515
609
  On hit: DashboardPage, DashboardSidebar, StatsPanel, and ActivityFeed are all
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:
610
+ served from cache, and DashboardLoader runs fresh. On miss: all handlers run and
611
+ the path's segments are cached together. DashboardPage runs inside the boundary,
612
+ so the guards apply to it: a header write there throws. The wrapper form
613
+ `cache({ ttl: 300 }, () => [layout(DashboardSidebar)])` inside a path does the
614
+ same, and `cache(false)` there opts the path out of an enclosing `cache()` (on a
615
+ `ppr` route, out of the shell too). The same form caches a response route:
522
616
  `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.
617
+ A `cache()` inside a `layout()` or `middleware()` wrapper in the path caches the
618
+ path too: `layout(DashboardSidebar, () => [cache({ ttl: 300 })])` is the same
619
+ unit as the example above, not the sidebar alone.
526
620
 
527
621
  ### Bare cache() among a layout's children
528
622
 
@@ -566,8 +660,8 @@ RootLayout renders fresh every request, and a header it writes lands on every
566
660
  response. ProductPage, ReviewsPanel, and RelatedProducts are inside the cache
567
661
  boundary and served from cache on hit. This is useful when the root layout
568
662
  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.
663
+ is cacheable. On a `ppr` route the shell bakes RootLayout too: a shell HIT runs
664
+ no layout, and the shell expires no later than this `cache()` entry.
571
665
 
572
666
  ### Loader-level caching
573
667
 
@@ -589,7 +683,12 @@ This attaches the cache config directly to the loader entry. The loader's
589
683
  data is cached independently from the route's segment cache, together with
590
684
  the handle pushes its body made (replayed on every hit). Loader caching
591
685
  supports custom keys, tags, SWR, conditional bypass, and per-loader store
592
- overrides — see `/loader` for the full reference.
686
+ overrides — see `/loader` for the full reference. A body that reads
687
+ `cookies()`, `headers()`, `ctx.request.headers` or a non-cacheable variable needs a `key()`, which
688
+ must include the value: without one the miss fails, and any `key()` switches
689
+ that check off. The entry also carries the tags its body recorded
690
+ (`cacheTag()` calls, `"use cache"` reads, and those of the loaders it reads
691
+ with `ctx.use()`), so `updateTag()` of any of them drops it.
593
692
 
594
693
  ## See Also
595
694