@rangojs/router 0.18.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.
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +2 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/cache-key-utils.d.ts +44 -13
- package/dist/types/cache/cache-policy.d.ts +1 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +117 -35
- package/dist/types/cache/cache-tag.d.ts +24 -4
- package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
- package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +40 -8
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-snapshot.d.ts +88 -141
- package/dist/types/cache/tag-invalidation.d.ts +38 -14
- package/dist/types/cache/types.d.ts +68 -61
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
- package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +14 -7
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
- package/dist/types/rsc/shell-capture.d.ts +78 -81
- package/dist/types/rsc/shell-serve.d.ts +19 -48
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +89 -61
- package/dist/types/server/cookie-store.d.ts +30 -18
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +66 -45
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +7 -6
- package/dist/types/testing/shell-status.d.ts +34 -22
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +3 -4
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/vite/index.js +960 -97
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +76 -49
- package/skills/caching/SKILL.md +167 -69
- package/skills/client-urls/SKILL.md +1 -1
- package/skills/comparison/references/framework-comparison.md +8 -5
- package/skills/document-cache/SKILL.md +24 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +98 -6
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +52 -21
- package/skills/middleware/SKILL.md +18 -0
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/observability/SKILL.md +6 -5
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +129 -77
- package/skills/prerender/SKILL.md +3 -3
- package/skills/rango/SKILL.md +25 -6
- package/skills/route/SKILL.md +2 -0
- package/skills/testing/SKILL.md +27 -25
- package/skills/testing/cache-prerender.md +9 -9
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +3 -2
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +14 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +8 -4
- package/skills/typesafety/env-and-bindings.md +8 -0
- package/skills/use-cache/SKILL.md +64 -19
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +52 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +4 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +48 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +197 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +105 -13
- package/src/cache/cf/cf-cache-store.ts +152 -76
- package/src/cache/cf/cf-shell-frame.ts +2 -2
- package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +52 -9
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-snapshot.ts +130 -348
- package/src/cache/tag-invalidation.ts +57 -51
- package/src/cache/types.ts +71 -67
- package/src/cache/vercel/vercel-cache-store.ts +61 -58
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +155 -146
- package/src/route-definition/dsl-helpers.ts +2 -0
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +10 -7
- package/src/router/intercept-resolution.ts +11 -1
- package/src/router/loader-resolution.ts +28 -3
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +62 -34
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +9 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +35 -19
- package/src/router/match-middleware/cache-store.ts +15 -9
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +4 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +0 -4
- package/src/router/segment-resolution/loader-cache.ts +150 -25
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +15 -78
- package/src/router/segment-resolution/mask-nested.ts +47 -22
- package/src/router/segment-resolution/revalidation.ts +0 -3
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +30 -10
- package/src/rsc/handler.ts +12 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +7 -30
- package/src/rsc/response-cache-serve.ts +68 -80
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +206 -252
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +22 -20
- package/src/rsc/shell-capture-constants.ts +136 -20
- package/src/rsc/shell-capture.ts +352 -589
- package/src/rsc/shell-serve.ts +73 -109
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +2 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +189 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +123 -84
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +10 -7
- package/src/testing/shell-status.ts +60 -56
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +3 -4
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +0 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +75 -5
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
package/package.json
CHANGED
|
@@ -123,7 +123,9 @@ else they allow:
|
|
|
123
123
|
|
|
124
124
|
- **`cache()` boundary guard** (route-level) — fires while the handler runs on a
|
|
125
125
|
miss. `cookies()` and `headers()` throw (request-scoped data would be baked into
|
|
126
|
-
the shared cached shell), `ctx.
|
|
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
|
|
127
129
|
would be baked in), and response side effects (`ctx.headers.set()`,
|
|
128
130
|
`setCookie()`, `setStatus()`, `onResponse()`) throw. `ctx.set()` of a cacheable var is
|
|
129
131
|
**allowed** — children are cached too and can read it. **Loaders are exempt**
|
|
@@ -131,19 +133,33 @@ else they allow:
|
|
|
131
133
|
loader.
|
|
132
134
|
- **Loader `cache()` guard** (a loader bound with its own `cache()`) — its value
|
|
133
135
|
is stored under a key that names no user, so a miss whose body read
|
|
134
|
-
`cookies()`, `headers()`, the theme or a
|
|
136
|
+
`cookies()`, `headers()`, `ctx.request.headers`, the theme or a
|
|
137
|
+
non-cacheable `ctx.get()` fails
|
|
135
138
|
unless the binding has a `key()` or its store a `keyGenerator`. Either one
|
|
136
139
|
switches the check off, so put what the body reads in it (`/loader` →
|
|
137
140
|
"Cache Key").
|
|
138
141
|
- **`"use cache"` exec-guard** (function-level) — the same request-scoped APIs
|
|
139
|
-
throw inside the cached function (`cookies()`, `headers()`,
|
|
140
|
-
`ctx.headers.set()` and other response
|
|
141
|
-
`ctx`/`env`/`req` args are excluded from the
|
|
142
|
-
|
|
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
|
|
143
147
|
uncached and nothing throws.
|
|
144
148
|
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
147
163
|
capture anywhere it waits — handlers, promises they pass or push, async server
|
|
148
164
|
components, `ssr: false` loaders, and loaders a handler awaits (no loader
|
|
149
165
|
exemption there). The route then serves uncached; see `/ppr`.
|
|
@@ -222,17 +238,17 @@ local/dev behavior, not as proof that segment SWR is active.
|
|
|
222
238
|
|
|
223
239
|
## Key Differences
|
|
224
240
|
|
|
225
|
-
| | `cache()` DSL | `"use cache"` directive
|
|
226
|
-
| -------------------- | ----------------------------------------------------- |
|
|
227
|
-
| **Scope** | Route segment tree (handler + children + parallels) | Single function return value
|
|
228
|
-
| **Defined at** | Route definition site (`urls.ts`) | Inside function body or at file top
|
|
229
|
-
| **Cache key** | Request type + host + pathname + params + search | Function identity + serialized non-tainted args
|
|
230
|
-
| **Execution on hit** | All-or-nothing: entire handler skipped | Partial: function body skipped, calling code runs
|
|
231
|
-
| **Runtime control** | `condition` to disable, custom `key` function | None — if the directive is present, it caches
|
|
232
|
-
| **Side effects** | Response side effects throw inside the boundary | `ctx.headers.set()`, `ctx.set()`, etc. throw
|
|
233
|
-
| **Handle data** | Handler pushes replayed; loader pushes re-run live | Captured and replayed when it receives `ctx`
|
|
234
|
-
| **Loaders** | Always fresh — excluded from cache, opt-in per loader | Can be used inside loaders
|
|
235
|
-
| **Nesting** | Inner TTLs override; `key()` partitions compose | 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 |
|
|
236
252
|
|
|
237
253
|
### cache() Cache Key
|
|
238
254
|
|
|
@@ -245,14 +261,18 @@ honors `createRouter({ cache: { searchParams } })`.
|
|
|
245
261
|
|
|
246
262
|
A custom `key` function replaces the whole default key (e.g., to key by user role
|
|
247
263
|
or locale); it also bypasses the store's `keyGenerator` and the search-param
|
|
248
|
-
filter. It runs once per request and may read `cookies()`.
|
|
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()`
|
|
249
267
|
keys its records within the enclosing `key()` partition (`/caching` → "Keys
|
|
250
|
-
nest").
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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").
|
|
256
276
|
|
|
257
277
|
### "use cache" Cache Key
|
|
258
278
|
|
|
@@ -302,8 +322,9 @@ MISS → function body runs, return value + handle data cached
|
|
|
302
322
|
|
|
303
323
|
Runtime guards throw if you call `cookies()`, `headers()`, `ctx.set()`,
|
|
304
324
|
`ctx.headers.set()` (or any response write: cookie writes, `setStatus()`,
|
|
305
|
-
`onResponse()`), `ctx.setTheme()`, or `ctx.setLocationState()
|
|
306
|
-
`"use cache"` function. `cookies()
|
|
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
|
|
307
328
|
per-request data is not in the cache key. Side-effect methods are blocked because
|
|
308
329
|
their effects are lost on hit. Use `ctx.use(Handle)` instead for data — handle
|
|
309
330
|
data is captured and replayed.
|
|
@@ -432,7 +453,8 @@ Neither mechanism caches response headers or cookies.
|
|
|
432
453
|
you need headers or cookies on every response, set them in middleware, a live
|
|
433
454
|
segment outside the cache boundary, or a loader.
|
|
434
455
|
- **"use cache"**: `cookies()` and `headers()` throw inside the cached function
|
|
435
|
-
(both reads and writes), and so do `ctx.headers`
|
|
456
|
+
(both reads and writes), and so do `ctx.request.headers` reads and
|
|
457
|
+
`ctx.headers` mutations. Move them outside.
|
|
436
458
|
- **`ctx.theme`** (handler and middleware) and **`getRequestContext().theme`**
|
|
437
459
|
are the visitor's theme cookie, so a read throws in both scopes, like
|
|
438
460
|
`cookies()`. Read it with `useTheme()` in a client component, or in a
|
|
@@ -469,26 +491,27 @@ specifies `cache: false`, the value is non-cacheable.
|
|
|
469
491
|
|
|
470
492
|
**Behavior inside a `cache()` boundary:**
|
|
471
493
|
|
|
472
|
-
| Operation
|
|
473
|
-
|
|
|
474
|
-
| `cookies()` / `headers()` (read or write)
|
|
475
|
-
| `ctx.
|
|
476
|
-
| `ctx.
|
|
477
|
-
| `ctx.get(
|
|
478
|
-
| `ctx.
|
|
479
|
-
| `ctx.
|
|
480
|
-
|
|
|
481
|
-
|
|
|
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 |
|
|
482
505
|
|
|
483
506
|
(Both scopes block the same request-scoped APIs — `cookies()`, `headers()`,
|
|
484
507
|
response side effects, and non-cacheable `ctx.get()` — because each would leak
|
|
485
508
|
per-request data into a shared cache entry. The `cache()` boundary tracks the
|
|
486
509
|
scope via `isInsideCacheScope()`; `"use cache"` uses the exec guard and also
|
|
487
|
-
excludes tainted `ctx`/`env`/`req` args from the cache key. Loaders are exempt
|
|
488
|
-
|
|
489
|
-
cache"`
|
|
490
|
-
(`await ctx.use(Loader)`)
|
|
491
|
-
|
|
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.)
|
|
492
515
|
|
|
493
516
|
Write is dumb — `ctx.set()` stores the cache metadata but does not enforce.
|
|
494
517
|
Enforcement happens at read time (`ctx.get()`), where ALS detects the cache
|
|
@@ -528,14 +551,18 @@ breeds the false confidence that makes the derived leak _more_ likely. The guard
|
|
|
528
551
|
is deliberately non-propagating (propagation would cost a wrapper per derivation
|
|
529
552
|
on the hot path), and it is scoped to the `cache()` segment boundary and the
|
|
530
553
|
`"use cache"` body. `"use cache"` functions block the same request-scoped reads
|
|
531
|
-
(`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
|
|
532
555
|
additionally exclude tainted `ctx`/`env`/`req` args from the cache key — pass a
|
|
533
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:
|
|
534
557
|
**read tainted context at the point of use, in the path that needs it (a loader or
|
|
535
558
|
live segment) — never extract user data into a plain value and cache that.**
|
|
536
|
-
Loaders are exempt because they run outside the cache
|
|
537
|
-
every request. A loader with its own `cache()` does not:
|
|
538
|
-
so it falls under the loader `cache()` guard above.
|
|
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).
|
|
539
566
|
|
|
540
567
|
## Loaders Are Always Fresh
|
|
541
568
|
|
|
@@ -657,7 +684,7 @@ data is cached independently from the route's segment cache, together with
|
|
|
657
684
|
the handle pushes its body made (replayed on every hit). Loader caching
|
|
658
685
|
supports custom keys, tags, SWR, conditional bypass, and per-loader store
|
|
659
686
|
overrides — see `/loader` for the full reference. A body that reads
|
|
660
|
-
`cookies()`, `headers()` or a non-cacheable variable needs a `key()`, which
|
|
687
|
+
`cookies()`, `headers()`, `ctx.request.headers` or a non-cacheable variable needs a `key()`, which
|
|
661
688
|
must include the value: without one the miss fails, and any `key()` switches
|
|
662
689
|
that check off. The entry also carries the tags its body recorded
|
|
663
690
|
(`cacheTag()` calls, `"use cache"` reads, and those of the loaders it reads
|
package/skills/caching/SKILL.md
CHANGED
|
@@ -90,7 +90,8 @@ path, not only ProductChrome.
|
|
|
90
90
|
|
|
91
91
|
The consumer rule: **want it cached? render it inline. Want it live? put it in a
|
|
92
92
|
loader and read it with `useLoader()` in a client component.** Anything read
|
|
93
|
-
with `cookies()`, `headers()`, or a non-cacheable
|
|
93
|
+
with `cookies()`, `headers()`, `ctx.request.headers`, or a non-cacheable
|
|
94
|
+
variable belongs in a loader:
|
|
94
95
|
a route `cache()` does not store loader values, so a loader runs on every
|
|
95
96
|
request. A loader bound with its own `cache()` is stored, so without a `key()`
|
|
96
97
|
its miss fails; any `key()` switches that check off, so it must include the
|
|
@@ -167,14 +168,20 @@ cache(
|
|
|
167
168
|
— document requests, client navigations, and intercept navigations are cached
|
|
168
169
|
separately. The search part honors `cache.searchParams` (below).
|
|
169
170
|
- **`key`** is a full override: it bypasses the default key, the store's
|
|
170
|
-
`keyGenerator`, and the search-param filter.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
partition
|
|
175
|
-
|
|
171
|
+
`keyGenerator`, and the search-param filter. The result is stored
|
|
172
|
+
namespaced (`key:` plus its URI encoding), so whatever it returns, request
|
|
173
|
+
input included, never names another route's default-keyed record or a
|
|
174
|
+
nested partition's. A nested `cache()` keys its records within that
|
|
175
|
+
partition (see "Keys nest" under "Nested Cache Boundaries"). On a `ppr`
|
|
176
|
+
route the same key (or, without one, the store's `keyGenerator`)
|
|
177
|
+
partitions the PPR shell: each partition captures and serves its own shell
|
|
178
|
+
(`/ppr`, "Request-partitioned shells").
|
|
176
179
|
- **`condition`** returning `false` skips both the cache read and write for that
|
|
177
|
-
request (the boundary renders live).
|
|
180
|
+
request (the boundary renders live). It gates every `cache()` nested under
|
|
181
|
+
it too: an inner boundary caches only when every enclosing `condition()`
|
|
182
|
+
allows it.
|
|
183
|
+
- **`tags`** tag this boundary's records and those of every `cache()` nested
|
|
184
|
+
under it, so `updateTag()` of an outer tag evicts the inner records too.
|
|
178
185
|
- `cache(() => [...])` with no options uses the store defaults.
|
|
179
186
|
- `cache(false, () => [...])` disables caching for a subtree (see "Nested Cache
|
|
180
187
|
Boundaries").
|
|
@@ -230,8 +237,11 @@ function CampaignBanner() {
|
|
|
230
237
|
|
|
231
238
|
Each tag attaches to one entry, so know which entry you are tagging:
|
|
232
239
|
|
|
233
|
-
- Forms 1 and 2 tag the `cache()` segment entry
|
|
234
|
-
|
|
240
|
+
- Forms 1 and 2 tag the `cache()` segment entry, and the entries of every
|
|
241
|
+
`cache()` nested under it (see "Conditions and tags inherit").
|
|
242
|
+
- Form 3 tags the `"use cache"` entry (plus any profile `tags`), and every
|
|
243
|
+
`"use cache"` entry that calls that function: the outer entry bakes the
|
|
244
|
+
inner value, so it carries the inner tags.
|
|
235
245
|
- Form 4 tags the request's `ppr` shell entry (`/ppr`) or `/document-cache`
|
|
236
246
|
entry, with no `"use cache"` needed. On a route that is neither `ppr` nor
|
|
237
247
|
document-cached and has no `cache()`, nothing reads the tag — a silent
|
|
@@ -283,18 +293,21 @@ request, so an action that calls `revalidateTag()` and then renders gets fresh
|
|
|
283
293
|
`"use cache"` and `cache()` reads, as with `await updateTag()`. What
|
|
284
294
|
`revalidateTag()` does not wait for is the durable write (the KV marker, the
|
|
285
295
|
tag purge, Vercel's `expireTag`): other requests see the invalidation once it
|
|
286
|
-
lands.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
296
|
+
lands.
|
|
297
|
+
|
|
298
|
+
Work that started before the invalidation, in this request or another one,
|
|
299
|
+
is not stored after it. A `"use cache"` call, a stale entry's background
|
|
300
|
+
refresh, a loader's own `cache()`, a route `cache()` render or a
|
|
301
|
+
document-cache render that read its data before one of its tags was
|
|
302
|
+
invalidated still returns what it read, but its store write is skipped:
|
|
303
|
+
every store stamps an entry when it is written, so the old value would be
|
|
304
|
+
served as newer than the invalidation. The next read runs it again. A
|
|
305
|
+
`"use cache"` call already running when one of its tags is invalidated is
|
|
306
|
+
not joined by later calls either. Its tags include those of the
|
|
307
|
+
`"use cache"` functions it calls. Another isolate's invalidation is caught
|
|
308
|
+
through the store's markers (`CFCacheStore` with KV, `VercelCacheStore`); on
|
|
309
|
+
a KV-less `CFCacheStore` only this isolate's invalidations are, and ttl+swr
|
|
310
|
+
bounds the rest.
|
|
298
311
|
|
|
299
312
|
Both must run inside a request (action, handler, middleware, response route).
|
|
300
313
|
Called from a queue consumer or cron job, there is no request context, so they
|
|
@@ -428,8 +441,9 @@ does. See `/loader` for the full loader reference.
|
|
|
428
441
|
|
|
429
442
|
The entry's default key is the loader, host, path and params. It does not
|
|
430
443
|
inherit an enclosing `cache()` boundary's `key()` either, so it is shared
|
|
431
|
-
across users: a body that reads `cookies()`, `headers()`
|
|
432
|
-
variable fails on a miss unless the loader `cache()` has a
|
|
444
|
+
across users: a body that reads `cookies()`, `headers()`, `ctx.request.headers`
|
|
445
|
+
or a non-cacheable variable fails on a miss unless the loader `cache()` has a
|
|
446
|
+
`key()` or its store
|
|
433
447
|
a `keyGenerator`. Either one switches the check off, so it must itself include
|
|
434
448
|
the value (`/loader` → "Cache Key").
|
|
435
449
|
|
|
@@ -615,8 +629,10 @@ times out, or whose snapshot is truncated or corrupt (also evicted), cannot
|
|
|
615
629
|
replay the captured segment record, so it degrades: the entry is replaced and
|
|
616
630
|
recaptured and the page reloads once into a cache-miss render (`/ppr`). The
|
|
617
631
|
snapshot holds only what a HIT reads: every HIT replays the handler layer from
|
|
618
|
-
the captured segment record
|
|
619
|
-
|
|
632
|
+
the captured segment record and serves an `ssr: false` loader's baked
|
|
633
|
+
container, so no `"use cache"` or loader `cache()` item is stored in it. A
|
|
634
|
+
live hole, and any loader that runs on the HIT, reads those from the store
|
|
635
|
+
under their own ttl/swr and tags.
|
|
620
636
|
|
|
621
637
|
**Shell memo.** After a shell read, the same isolate serves that key's next
|
|
622
638
|
HITs from memory for `memo.shellMs` (default 2000; `{ shellMs: 0 }` turns it
|
|
@@ -785,22 +801,49 @@ cached handler is **frozen into the shared cache entry** and served to every
|
|
|
785
801
|
subsequent visitor. To stop one user's request-scoped data from leaking to
|
|
786
802
|
another, request-scoped APIs are guarded inside a cache scope:
|
|
787
803
|
|
|
788
|
-
| Inside a `cache()` boundary | Behavior
|
|
789
|
-
| ---------------------------------------------------------------------------------- |
|
|
790
|
-
| `cookies()` / `headers()` (read or write) | **throws** — request-scoped, would poison the entry
|
|
791
|
-
|
|
|
792
|
-
| `ctx.
|
|
793
|
-
| `ctx.
|
|
794
|
-
| `ctx.
|
|
795
|
-
|
|
|
804
|
+
| Inside a `cache()` boundary | Behavior |
|
|
805
|
+
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
806
|
+
| `cookies()` / `headers()` (read or write) | **throws** — request-scoped, would poison the entry |
|
|
807
|
+
| `ctx.request.headers`, `getRequestContext().cookie()` / `.cookies()` | **throws** on read, like `headers()` / `cookies()` |
|
|
808
|
+
| Response writes: `ctx.headers.set()`, `setCookie()`, `setStatus()`, `onResponse()` | **throws** — response side effects lost on a hit |
|
|
809
|
+
| `ctx.get(var)` where the var is `{ cache: false }` | **throws** on read |
|
|
810
|
+
| `ctx.theme`, `getRequestContext().theme` | **throws** — the theme is the visitor's cookie |
|
|
811
|
+
| `ctx.set(var, value)` for a cacheable var | allowed (children are cached too) |
|
|
812
|
+
| Any of the above **inside a registered loader** (`loader(...)`) | **allowed** — loaders always run fresh |
|
|
813
|
+
| Any read in the boundary's own `key()`, `keyGenerator`, `condition()` or `tags()` | **allowed** — it picks or labels the entry, never rendered |
|
|
796
814
|
|
|
797
815
|
A loader body invoked from a handler with `await ctx.use(Loader)` may read
|
|
798
|
-
`cookies()`/`headers()`, but its response writes still throw: on a hit the
|
|
816
|
+
`cookies()`/`headers()`/`ctx.request.headers`, but its response writes still throw: on a hit the
|
|
799
817
|
handler is skipped, so that loader never runs.
|
|
800
818
|
|
|
819
|
+
`ctx.request.clone()` is guarded the same way. `fetch(ctx.request)` and
|
|
820
|
+
`new Request(ctx.request)` don't throw, but the guard can't see through them:
|
|
821
|
+
a fetch forwards the visitor's `Cookie` and `Authorization`, so its response
|
|
822
|
+
is per visitor and must not be rendered inside the boundary. A handler that
|
|
823
|
+
must render a header its `key()` partitions by reads a copy middleware set:
|
|
824
|
+
|
|
825
|
+
```tsx
|
|
826
|
+
const Tier = createVar<string>();
|
|
827
|
+
const tierOf = (ctx: { request: Request }) =>
|
|
828
|
+
ctx.request.headers.get("x-tier") === "gold" ? "gold" : "silver";
|
|
829
|
+
|
|
830
|
+
middleware(
|
|
831
|
+
async (ctx, next) => {
|
|
832
|
+
ctx.set(Tier, tierOf(ctx)); // middleware runs outside the boundary
|
|
833
|
+
return next();
|
|
834
|
+
},
|
|
835
|
+
() => [
|
|
836
|
+
cache({ ttl: 300, key: (ctx) => `tier:${tierOf(ctx)}` }, () => [
|
|
837
|
+
path("/pricing", (ctx) => <Pricing tier={ctx.get(Tier)} />),
|
|
838
|
+
]),
|
|
839
|
+
],
|
|
840
|
+
);
|
|
841
|
+
```
|
|
842
|
+
|
|
801
843
|
A loader bound with its **own** `cache()` (`loader(Def, () => [cache({...})])`)
|
|
802
844
|
stores its value, and its key does not inherit the route's. A miss whose body
|
|
803
|
-
read `cookies()`, `headers()` or a non-cacheable
|
|
845
|
+
read `cookies()`, `headers()`, `ctx.request.headers` or a non-cacheable
|
|
846
|
+
`ctx.get()` fails unless the
|
|
804
847
|
binding has a `key()` or its store a `keyGenerator`, which must then include
|
|
805
848
|
the value (`/loader` → "Cache Key").
|
|
806
849
|
|
|
@@ -826,9 +869,12 @@ cached handler.
|
|
|
826
869
|
This is the **consumption-lane rule**, and it holds for every shared
|
|
827
870
|
artifact — `cache()`, `"use cache"`, and the PPR shell (`/ppr`): handler
|
|
828
871
|
consumption = baked copy; client-side `useLoader` = live. The tiers differ on
|
|
829
|
-
identity reads inside a handler-consumed loader: `cache()`
|
|
830
|
-
|
|
831
|
-
|
|
872
|
+
identity reads inside a handler-consumed loader: `cache()` permits them (the
|
|
873
|
+
leak above); `"use cache"` throws when the cached function is the first to
|
|
874
|
+
read the loader in the request, because the loader body then runs inside it
|
|
875
|
+
and its value is part of the result (a loader already started elsewhere
|
|
876
|
+
returns its value unchecked, and that value is stored); a PPR shell capture
|
|
877
|
+
refuses them and the route stays uncached. It is stated once in `/rango` → Invariants.
|
|
832
878
|
|
|
833
879
|
```typescript
|
|
834
880
|
// WRONG — throws: cookies() read directly in a cached handler
|
|
@@ -874,9 +920,11 @@ See `/cache-guide` for the full decision guide and the `cache()` vs `"use cache"
|
|
|
874
920
|
|
|
875
921
|
## Nested Cache Boundaries
|
|
876
922
|
|
|
877
|
-
An inner boundary overrides
|
|
878
|
-
|
|
879
|
-
|
|
923
|
+
An inner boundary overrides `ttl`, `swr` and `store` for its subtree;
|
|
924
|
+
`cache(false)` turns caching off for a subtree. The options that decide
|
|
925
|
+
whether and where a record may be shared build up instead: `key` composes
|
|
926
|
+
(see below), every enclosing `condition()` must allow the inner boundary, and
|
|
927
|
+
the inner records carry the enclosing `tags` too:
|
|
880
928
|
|
|
881
929
|
```typescript
|
|
882
930
|
cache({ ttl: 300 }, () => [
|
|
@@ -898,6 +946,39 @@ cache miss, with no shell served and none captured. A `ppr` route under a
|
|
|
898
946
|
layout's `cache()` therefore cannot opt out of that `cache()` and still get a
|
|
899
947
|
shell (`/ppr`).
|
|
900
948
|
|
|
949
|
+
### Conditions and tags inherit
|
|
950
|
+
|
|
951
|
+
```typescript
|
|
952
|
+
cache(
|
|
953
|
+
{
|
|
954
|
+
condition: (ctx) => !ctx.request.headers.has("x-preview"),
|
|
955
|
+
tags: ["catalog"],
|
|
956
|
+
},
|
|
957
|
+
() => [
|
|
958
|
+
cache({ ttl: 60, tags: ["prices"] }, () => [
|
|
959
|
+
path("/prices", PricesPage, { name: "prices" }),
|
|
960
|
+
]),
|
|
961
|
+
],
|
|
962
|
+
);
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
- A preview request renders the prices page live: the outer `condition()` bypasses
|
|
966
|
+
the inner record's read and write, and a `ppr` route's shell. Conditions
|
|
967
|
+
combine with AND, through a `cache(false)` with a `cache()` re-enabled
|
|
968
|
+
below it too.
|
|
969
|
+
- The prices record carries `catalog` and `prices` (static and function
|
|
970
|
+
`tags` alike), so `updateTag("catalog")` evicts it, and the `ppr` shells
|
|
971
|
+
and documents built from it.
|
|
972
|
+
- `cache(false)` is unchanged: it caches nothing below it until a `cache()`
|
|
973
|
+
re-enables caching.
|
|
974
|
+
- A loader's own `cache()` and `"use cache"` are separate layers: they take
|
|
975
|
+
neither the route's `condition()` nor its `tags`, nor its `key()`
|
|
976
|
+
partition.
|
|
977
|
+
|
|
978
|
+
Before issue #974, only the innermost boundary's `condition` and `tags`
|
|
979
|
+
applied: a request an outer `condition()` refused still read and wrote the
|
|
980
|
+
inner record, and `updateTag()` of an outer tag left it in place.
|
|
981
|
+
|
|
901
982
|
### Keys nest: a partition covers the whole subtree
|
|
902
983
|
|
|
903
984
|
A `key()` partitions every record under its `cache()`. A nested `cache()`
|
|
@@ -911,14 +992,14 @@ const tier = (ctx) =>
|
|
|
911
992
|
cache({ ttl: 300, key: (ctx) => `tier:${tier(ctx)}` }, () => [
|
|
912
993
|
layout(TierLayout, () => [
|
|
913
994
|
// No key() of its own: the outer key() result, then this boundary's own
|
|
914
|
-
// default key ("tier%3Agold|doc%3Aexample.com%2Fpricing"), so
|
|
915
|
-
// and /faq keep their own records
|
|
995
|
+
// default key ("key:tier%3Agold|doc%3Aexample.com%2Fpricing"), so
|
|
996
|
+
// /pricing and /faq keep their own records
|
|
916
997
|
cache({ ttl: 60 }, () => [
|
|
917
998
|
path("/pricing", PricingPage, { name: "pricing" }),
|
|
918
999
|
path("/faq", FaqPage, { name: "faq" }),
|
|
919
1000
|
]),
|
|
920
1001
|
|
|
921
|
-
// Its own key() composes with the outer one ("tier%3Agold|v%3Ab")
|
|
1002
|
+
// Its own key() composes with the outer one ("key:tier%3Agold|key:v%3Ab")
|
|
922
1003
|
cache(
|
|
923
1004
|
{ ttl: 60, key: (ctx) => `v:${ctx.searchParams.get("v") ?? "a"}` },
|
|
924
1005
|
() => [path("/plans", PlansPage, { name: "plans" })],
|
|
@@ -927,26 +1008,29 @@ cache({ ttl: 300, key: (ctx) => `tier:${tier(ctx)}` }, () => [
|
|
|
927
1008
|
]);
|
|
928
1009
|
```
|
|
929
1010
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
|
935
|
-
|
|
|
936
|
-
|
|
|
937
|
-
| `cache(
|
|
938
|
-
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
`
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1011
|
+
Every `key()` result is stored as `key:` plus its URI encoding; a default
|
|
1012
|
+
key in a composed key is URI-encoded. The parts are joined by `|`, `key()`
|
|
1013
|
+
results first, outermost first:
|
|
1014
|
+
|
|
1015
|
+
| Boundary the route sits in | Record key |
|
|
1016
|
+
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1017
|
+
| a single keyed `cache()` | Its `key()` result, namespaced (`tier:gold` is stored as `key:tier%3Agold`) |
|
|
1018
|
+
| `cache({ ttl })`, no `key`, under a keyed one | The chain's `key()` results, then this boundary's default key (its store's `keyGenerator` result, else the default key), encoded |
|
|
1019
|
+
| `cache({ key })` under a keyed one | The chain's `key()` results |
|
|
1020
|
+
| deeper nesting | Composes the same way; only the innermost boundary's missing `key` adds its default key |
|
|
1021
|
+
| under a `cache({ store })` on another store | Also that store's `keyGenerator` result, one part per store: empty for a store that returns the default key while another's differs, none when all return it |
|
|
1022
|
+
| `cache(false)` | No caching, as before; a `cache()` re-enabled below it stays in the partition |
|
|
1023
|
+
| no `key` anywhere | The default key, or the store's `keyGenerator` result, as before |
|
|
1024
|
+
|
|
1025
|
+
- No `key()` result, whatever it returns, equals a default key or a
|
|
1026
|
+
composed key, and no two composed keys are equal: a namespaced result
|
|
1027
|
+
starts with `key:` and holds no `|`, a default key never starts with
|
|
1028
|
+
`key:`, and each part of a composed key is encoded, so the parts and their
|
|
1029
|
+
kinds are recoverable. Returning request input from `key()` cannot name
|
|
1030
|
+
another route's record (before issue #975 a header value
|
|
1031
|
+
`doc:example.com/pricing` returned raw named the pricing page's default-keyed
|
|
1032
|
+
record). A prefix (`tier:${value}`) is still worth it for readable keys,
|
|
1033
|
+
and normalizing to the values you serve keeps the partition count bounded.
|
|
950
1034
|
- An inner boundary without `key` keeps everything its default key tells
|
|
951
1035
|
apart (path, params, search, document vs navigation), so its routes never
|
|
952
1036
|
share a record, even when the outer `key()` names no route.
|
|
@@ -954,17 +1038,31 @@ The parts are joined by `|`, outermost first, each URI-encoded:
|
|
|
954
1038
|
single `key()` does.
|
|
955
1039
|
- Each `key()` runs once per request, however many boundaries and lookups use
|
|
956
1040
|
it.
|
|
1041
|
+
- A store's `keyGenerator` partitions the records of a `cache()` on that
|
|
1042
|
+
store without a `key()` of its own. A `cache({ store })` on another store
|
|
1043
|
+
nested under it keys its records by that `keyGenerator` result too, so a
|
|
1044
|
+
locale-partitioned outer boundary never shares an inner record across
|
|
1045
|
+
locales. With several such stores each keeps its position: one that
|
|
1046
|
+
returns the default key for a request adds an empty part while another's
|
|
1047
|
+
differs, and when all return it the key is unchanged. A `keyGenerator` must
|
|
1048
|
+
return the default key, not `""`, to leave a request unpartitioned: an
|
|
1049
|
+
empty result there fails key resolution, so the request renders uncached,
|
|
1050
|
+
and the router warns once naming the store. On the same store the inner
|
|
1051
|
+
boundary's own default key already carries it (or its own `key()`
|
|
1052
|
+
overrides it).
|
|
957
1053
|
- A response route's entry (`path.json()` and the other response routes) is
|
|
958
|
-
keyed the same way. A `ppr` route's shell is
|
|
959
|
-
`key()` results
|
|
960
|
-
|
|
1054
|
+
keyed the same way, behind a `response:` prefix. A `ppr` route's shell is
|
|
1055
|
+
partitioned by the chain's `key()` results, namespaced the same way, since
|
|
1056
|
+
the shell key already carries the URL (plus the `keyGenerator` results
|
|
1057
|
+
above, and the store's for an inner boundary without `key`).
|
|
961
1058
|
- To share an inner cache across partitions, move it outside the keyed
|
|
962
1059
|
`cache()`.
|
|
963
1060
|
|
|
964
1061
|
Before issue #970, the innermost boundary alone decided the key: an inner
|
|
965
1062
|
`cache()` without `key` wrote under the default key and an inner `key()`
|
|
966
1063
|
dropped the outer partition, so a silver visitor could HIT what a gold
|
|
967
|
-
visitor's request had cached.
|
|
1064
|
+
visitor's request had cached. Before issue #974 an outer `cache({ store })`'s
|
|
1065
|
+
`keyGenerator` did not reach an inner boundary on another store.
|
|
968
1066
|
|
|
969
1067
|
## Custom Cache Store
|
|
970
1068
|
|
|
@@ -134,7 +134,7 @@ flow into the generated route map, so `href()`, `ctx.reverse()` and
|
|
|
134
134
|
| `loader()` | `loader(Def, use?)` or `loader(Def, { ssr: false }, use?)` — see below |
|
|
135
135
|
| `loading()` | Route-level boundary around the optimistic render; inline `<Suspense>` at read sites keeps the destination's chrome visible while only the reads wait |
|
|
136
136
|
| `revalidate()` | Valid **inside a loader() use callback only**; runs in the browser |
|
|
137
|
-
| `transition()` | Inside a `path()` use callback only (at most one).
|
|
137
|
+
| `transition()` | Inside a `path()` use callback only (at most one). ViewTransition config plus a browser-run `when` predicate (see `/view-transitions`); same-route navs in a group already hold without it |
|
|
138
138
|
| `intercept()` | Dot-local named target in the SAME definition; use may contain `loader()`/`loading()` |
|
|
139
139
|
|
|
140
140
|
At the top level of the builder only `path()`, `layout()` and `intercept()`
|