@rangojs/router 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +254 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-scope.d.ts +34 -5
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +53 -13
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +15 -3
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +3 -2
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
- package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
- package/dist/types/rsc/shell-capture.d.ts +5 -4
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +10 -0
- package/dist/types/server/context.d.ts +33 -3
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +24 -21
- package/dist/types/ssr/index.d.ts +15 -0
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/vite/index.js +1381 -1346
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +65 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +207 -97
- package/skills/caching/SKILL.md +295 -93
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +53 -31
- package/skills/cloudflare/SKILL.md +72 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +39 -33
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +2 -1
- package/skills/deployment-caching/SKILL.md +6 -1
- package/skills/document-cache/SKILL.md +92 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +53 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +31 -16
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +37 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +242 -106
- package/skills/middleware/SKILL.md +119 -23
- package/skills/migrate-nextjs/SKILL.md +100 -45
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +49 -38
- package/skills/migrate-react-router/component-migration.md +25 -6
- package/skills/migrate-react-router/data-and-actions.md +18 -5
- package/skills/migrate-react-router/route-mapping.md +12 -6
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +28 -14
- package/skills/parallel/SKILL.md +75 -39
- package/skills/ppr/SKILL.md +345 -150
- package/skills/prerender/SKILL.md +189 -100
- package/skills/rango/SKILL.md +65 -34
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +165 -60
- package/skills/router-setup/SKILL.md +418 -240
- package/skills/scripts/SKILL.md +31 -20
- package/skills/server-actions/SKILL.md +220 -59
- package/skills/shell-manifest/SKILL.md +12 -7
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +68 -32
- package/skills/testing/cache-prerender.md +55 -29
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +8 -5
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +106 -29
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +126 -47
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +200 -82
- package/skills/vercel/SKILL.md +21 -11
- package/skills/view-transitions/SKILL.md +41 -20
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +1 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +1 -6
- package/src/cache/cache-runtime.ts +230 -61
- package/src/cache/cache-scope.ts +76 -12
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +132 -28
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/tag-invalidation.ts +4 -3
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +16 -3
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +117 -30
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +34 -25
- package/src/router/intercept-resolution.ts +125 -39
- package/src/router/loader-resolution.ts +56 -11
- package/src/router/match-api.ts +12 -17
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +93 -10
- package/src/router/match-middleware/cache-store.ts +17 -90
- package/src/router/middleware-types.ts +3 -5
- package/src/router/middleware.ts +52 -9
- package/src/router/prerender-match.ts +53 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +44 -12
- package/src/router/segment-resolution/loader-cache.ts +205 -45
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +6 -6
- package/src/router/segment-resolution/mask-nested.ts +7 -7
- package/src/router/segment-resolution/revalidation.ts +34 -6
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +8 -45
- package/src/rsc/shell-capture-constants.ts +13 -0
- package/src/rsc/shell-capture.ts +111 -90
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +11 -0
- package/src/server/context.ts +88 -11
- package/src/server/cookie-store.ts +5 -4
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +35 -30
- package/src/ssr/index.tsx +40 -7
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +8 -6
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
|
@@ -1,15 +1,55 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cache-guide
|
|
3
|
-
description:
|
|
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
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
`
|
|
12
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
72
|
-
`
|
|
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.
|
|
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.)
|
|
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
|
|
114
|
-
data to user B" bug. The guarantee is precise and
|
|
115
|
-
"Context Variable Cache Safety" below for exactly
|
|
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`
|
|
143
|
-
document/response,
|
|
144
|
-
supports SWR for response
|
|
145
|
-
entries
|
|
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
|
|
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.
|
|
158
|
-
| **Handle data** |
|
|
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}:
|
|
165
|
-
`doc
|
|
166
|
-
for full document loads, client navigations, and intercept
|
|
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
|
-
|
|
169
|
-
|
|
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
|
|
175
|
-
|
|
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
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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()
|
|
207
|
-
ctx.
|
|
208
|
-
|
|
209
|
-
cache
|
|
210
|
-
|
|
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.
|
|
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.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
331
|
-
otherwise be serialized into the stored
|
|
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.
|
|
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
|
|
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)
|
|
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
|
|
400
|
-
cache"` functions block the same request-scoped reads
|
|
401
|
-
|
|
402
|
-
|
|
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
|
|
411
|
-
where
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
###
|
|
497
|
+
### cache() among a path's children
|
|
425
498
|
|
|
426
|
-
|
|
427
|
-
|
|
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
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
|
460
|
-
ReviewsPanel, and RelatedProducts are
|
|
461
|
-
served from cache on hit. This is useful when the root layout
|
|
462
|
-
request-specific data (user session, theme) but the product content
|
|
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
|
|
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)
|