@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.
- package/README.md +3 -2
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +18 -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 +48 -2
- package/dist/types/cache/cache-policy.d.ts +18 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +192 -26
- package/dist/types/cache/cache-tag.d.ts +116 -11
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +41 -8
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +141 -105
- package/dist/types/cache/tag-invalidation.d.ts +54 -3
- package/dist/types/cache/types.d.ts +267 -96
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
- 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/prerender/build-shell-capture.d.ts +11 -4
- 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/instrument.d.ts +8 -0
- 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/middleware-types.d.ts +6 -1
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
- package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
- 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 +21 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
- package/dist/types/rsc/shell-capture.d.ts +202 -74
- package/dist/types/rsc/shell-serve.d.ts +144 -50
- package/dist/types/rsc/types.d.ts +9 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +155 -33
- package/dist/types/server/cookie-store.d.ts +33 -0
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +210 -63
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- 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-loader.d.ts +18 -3
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +133 -0
- package/dist/types/testing/shell-status.d.ts +44 -19
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +14 -1
- 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 +21 -18
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1005 -105
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +175 -76
- package/skills/caching/SKILL.md +361 -33
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +12 -7
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +52 -37
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +32 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +101 -7
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +197 -44
- package/skills/middleware/SKILL.md +34 -9
- package/skills/migrate-nextjs/SKILL.md +68 -37
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +65 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +715 -371
- package/skills/prerender/SKILL.md +30 -17
- package/skills/rango/SKILL.md +72 -34
- package/skills/route/SKILL.md +14 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +49 -43
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +19 -1
- 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 +202 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +56 -4
- package/skills/typesafety/env-and-bindings.md +18 -2
- package/skills/use-cache/SKILL.md +91 -23
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +75 -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 +9 -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 +62 -2
- package/src/cache/cache-policy.ts +31 -4
- package/src/cache/cache-runtime.ts +209 -129
- package/src/cache/cache-scope.ts +573 -69
- package/src/cache/cache-tag.ts +475 -19
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +1094 -376
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +53 -9
- package/src/cache/index.ts +2 -0
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +249 -277
- package/src/cache/tag-invalidation.ts +109 -18
- package/src/cache/types.ts +274 -102
- package/src/cache/vercel/vercel-cache-store.ts +561 -74
- 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 +173 -110
- package/src/route-definition/dsl-helpers.ts +6 -3
- 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 +42 -5
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +30 -8
- package/src/router/loader-resolution.ts +89 -33
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +89 -42
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +14 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +52 -19
- package/src/router/match-middleware/cache-store.ts +36 -10
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +20 -4
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +10 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +18 -13
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +366 -70
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +215 -126
- package/src/router/segment-resolution/mask-nested.ts +161 -42
- package/src/router/segment-resolution/revalidation.ts +27 -13
- 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 +47 -3
- package/src/rsc/handler.ts +24 -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 +10 -32
- package/src/rsc/response-cache-serve.ts +69 -78
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +712 -338
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +49 -29
- package/src/rsc/shell-capture-constants.ts +182 -12
- package/src/rsc/shell-capture.ts +1126 -612
- package/src/rsc/shell-serve.ts +331 -85
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +9 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +328 -72
- package/src/server/cookie-store.ts +184 -133
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +364 -100
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +14 -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 +67 -5
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +381 -0
- package/src/testing/shell-status.ts +73 -47
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/handler-context.ts +14 -1
- 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 +21 -18
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +9 -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 +89 -9
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
package/package.json
CHANGED
|
@@ -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".
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
`
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
22
|
-
| -------------------- | ---------------------------------------------- |
|
|
23
|
-
| Function / component | `"use cache"` | one function's return value
|
|
24
|
-
| Loader data | `loader(L, () => [cache({...})])` | one loader's result
|
|
25
|
-
| Segments, runtime | `cache({...}, () => [...])` | rendered Flight segments of a subtree
|
|
26
|
-
| Segments, build time | `Prerender()` / `Static()` | Flight segments rendered at build
|
|
27
|
-
| HTML shell | `ppr` path option | HTML prelude + React postponed state
|
|
28
|
-
| Whole response (app) | `createDocumentCacheMiddleware()` + `s-maxage` | final response in the app store
|
|
29
|
-
| Whole response (CDN) | `Cache-Control: s-maxage` read by the platform | final response outside the app
|
|
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 **
|
|
57
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
`cacheTag(...tags)` — inside `"use cache"` it tags that entry; called during
|
|
64
|
-
request render outside `"use cache"` it tags the
|
|
65
|
-
|
|
66
|
-
`
|
|
67
|
-
|
|
68
|
-
|
|
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)
|
|
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.
|
|
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
|
-
(
|
|
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()`,
|
|
117
|
-
`ctx.headers.set()` and other response
|
|
118
|
-
`ctx`/`env`/`req` args are excluded from the
|
|
119
|
-
|
|
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** |
|
|
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.
|
|
220
|
-
|
|
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()
|
|
271
|
-
`"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
|
|
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**
|
|
368
|
-
|
|
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`
|
|
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
|
|
417
|
-
|
|
|
418
|
-
| `cookies()` / `headers()` (read or write)
|
|
419
|
-
| `ctx.
|
|
420
|
-
| `ctx.
|
|
421
|
-
| `ctx.
|
|
422
|
-
| `ctx.
|
|
423
|
-
|
|
|
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
|
|
430
|
-
|
|
431
|
-
cache"`
|
|
432
|
-
(`await ctx.use(Loader)`)
|
|
433
|
-
|
|
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
|
|
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
|
|
493
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
`
|
|
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
|
-
|
|
525
|
-
|
|
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
|
|
570
|
-
`cache()`
|
|
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
|
|