@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
|
@@ -214,24 +214,41 @@ intercept(
|
|
|
214
214
|
"product",
|
|
215
215
|
<ProductModal />,
|
|
216
216
|
// Only intercept when coming from a different section
|
|
217
|
-
{ when: ({ from }) => !from.pathname.startsWith("/shop/product/") },
|
|
217
|
+
{ when: ({ from }) => !from.url.pathname.startsWith("/shop/product/") },
|
|
218
218
|
() => [
|
|
219
219
|
loader(ProductLoader),
|
|
220
220
|
]
|
|
221
221
|
)
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
-
`when` is a synchronous match-time selector
|
|
225
|
-
`
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
224
|
+
`when` is a synchronous match-time selector that runs on the server. It
|
|
225
|
+
receives `from` and `to` (the destination), each `{ url, params, routeName }`
|
|
226
|
+
— the shape `transition({ when })` sees, without `state`, because history
|
|
227
|
+
state never reaches the server — plus `segments` (the client's current
|
|
228
|
+
segment path and ids), `request`, and `env`. `from` is the page the
|
|
229
|
+
navigation leaves, except while an intercept is open: then it is the
|
|
230
|
+
intercept's source page, the page under the modal (`match-api.ts`,
|
|
231
|
+
`navigation-snapshot.ts` `effectiveFromUrl`). Opening `/item/1` as a modal
|
|
232
|
+
over `/list/open` and then navigating to `/item/2` from inside the modal
|
|
233
|
+
gives `from.url.pathname === "/list/open"`, not `/item/1`. That differs from
|
|
234
|
+
`transition({ when })`'s `from`, which is always the committed location on
|
|
235
|
+
screen. `routeName` is set for named routes only. Pass an array of predicates
|
|
236
|
+
for AND logic (all must return true). Omit `when` entirely and the intercept
|
|
237
|
+
always activates. `when` is not re-evaluated during action revalidation, so
|
|
238
|
+
an open modal stays open after an action. A selector that throws does not
|
|
239
|
+
intercept: the full page renders and the error is logged with the route name.
|
|
240
|
+
|
|
241
|
+
A prefetch of a route an intercept targets is stored per source page, whether
|
|
242
|
+
or not the intercept applies from that page (`rsc-rendering.ts`
|
|
243
|
+
`x-rsc-prefetch-scope: source`, set from `MatchResult.interceptTargeted`). A
|
|
244
|
+
full page prefetched from a page where `when` is false never serves a click
|
|
245
|
+
from a page where it is true. Routes no intercept targets keep one shared
|
|
246
|
+
prefetch entry.
|
|
230
247
|
|
|
231
248
|
```typescript
|
|
232
249
|
// Intercept only when opened from the shop index
|
|
233
250
|
intercept("@modal", "product", <ProductModal />, {
|
|
234
|
-
when: ({
|
|
251
|
+
when: ({ from }) => from.routeName === "index",
|
|
235
252
|
})
|
|
236
253
|
```
|
|
237
254
|
|
|
@@ -242,8 +259,8 @@ intercept(
|
|
|
242
259
|
<ProductModal />,
|
|
243
260
|
{
|
|
244
261
|
when: [
|
|
245
|
-
({ from }) => from.pathname.startsWith("/shop"),
|
|
246
|
-
({
|
|
262
|
+
({ from }) => from.url.pathname.startsWith("/shop"),
|
|
263
|
+
({ to }) => to.params.slug !== "featured",
|
|
247
264
|
],
|
|
248
265
|
},
|
|
249
266
|
() => [
|
|
@@ -401,7 +418,7 @@ layout(ShopLayout, () => [
|
|
|
401
418
|
"@modal",
|
|
402
419
|
".detail",
|
|
403
420
|
<ProductModal />,
|
|
404
|
-
{ when: ({ from }) => from.pathname.startsWith("/shop") },
|
|
421
|
+
{ when: ({ from }) => from.url.pathname.startsWith("/shop") },
|
|
405
422
|
() => [loader(ProductLoader)],
|
|
406
423
|
),
|
|
407
424
|
])
|
|
@@ -482,7 +499,7 @@ export const shopPatterns = urls(({
|
|
|
482
499
|
"@modal",
|
|
483
500
|
".product", // dot-local: resolves to "shop.product" in this include
|
|
484
501
|
<ProductModalContent />,
|
|
485
|
-
{ when: ({ from }) => !from.pathname.startsWith("/shop/product/") },
|
|
502
|
+
{ when: ({ from }) => !from.url.pathname.startsWith("/shop/product/") },
|
|
486
503
|
() => [
|
|
487
504
|
layout(<ModalWrapper />),
|
|
488
505
|
loading(<ProductModalSkeleton />),
|
package/skills/loader/SKILL.md
CHANGED
|
@@ -7,7 +7,8 @@ argument-hint: "[loader]"
|
|
|
7
7
|
# Data Loaders with loader()
|
|
8
8
|
|
|
9
9
|
A loader is a server function created with `createLoader()` and registered on a
|
|
10
|
-
route segment with `loader()`. It runs fresh on every request
|
|
10
|
+
route segment with `loader()`. It runs fresh on every request (on a `ppr` route,
|
|
11
|
+
one registered with `ssr: false` is baked into the shell instead), streams its
|
|
11
12
|
result to the client, and client components read it with `useLoader()`. This
|
|
12
13
|
skill covers defining, registering, consuming, revalidating, caching, and
|
|
13
14
|
client-fetching loaders.
|
|
@@ -121,25 +122,39 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
|
121
122
|
> (plain GET only). See the hooks skill ("Scoping refetch with a `key`" and
|
|
122
123
|
> "Refreshing multiple loaders together").
|
|
123
124
|
> - `cache({ key })` — a **server** cache identity (storage hit/miss/ttl/swr).
|
|
125
|
+
> On a route `cache()` over a `ppr` route, each key value also gets its own
|
|
126
|
+
> shell; keep the value set small.
|
|
124
127
|
> - `revalidate()` — which **server** segments/loaders recompute during
|
|
125
128
|
> navigation and action refreshes.
|
|
126
129
|
|
|
127
130
|
DSL loaders are the **live data layer**: they resolve fresh on every request,
|
|
128
131
|
even when the route is inside a `cache()` boundary, so `cache()` gives you
|
|
129
132
|
cached UI + fresh data by default. See "Loaders: The Live Data Layer" below.
|
|
133
|
+
The exception is a `ppr` route's `ssr: false` loader: it bakes into the shell,
|
|
134
|
+
and a shell HIT runs it only when its return carries promises.
|
|
130
135
|
|
|
131
136
|
### Cache safety
|
|
132
137
|
|
|
133
|
-
DSL
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
A
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
A DSL loader without `ssr: false` can read `createVar({ cache: false })`
|
|
139
|
+
variables: it runs on every request, `cache()` and PPR shell HITs included, so
|
|
140
|
+
the read guard is bypassed for it. On a `ppr` route the capture runs an
|
|
141
|
+
`ssr: false` loader, and a loader a handler awaits, and bakes their values into
|
|
142
|
+
the shell: a `cookies()`, `headers()`, `ctx.request.headers` or
|
|
143
|
+
`{ cache: false }` read there refuses the capture, so the route keeps
|
|
144
|
+
rendering without a shell (warned once per key).
|
|
145
|
+
A loader bound with its own `cache()` is the other exception: its value is
|
|
146
|
+
stored and shared, so a miss whose body read `cookies()`, `headers()`,
|
|
147
|
+
`ctx.request.headers`, a non-cacheable variable or the theme fails unless the
|
|
148
|
+
binding has a `key()` or a
|
|
149
|
+
store `keyGenerator`, which must then include them (see "Cache Key"). A
|
|
150
|
+
`"use cache"` function the loader calls is not a loader body: a non-cacheable
|
|
151
|
+
read inside it throws, also through the loader's `ctx` passed in, so read the
|
|
152
|
+
var in the loader and pass the value in as an argument. Passing the loader's
|
|
153
|
+
`ctx` itself is fine: the call keys by route, params and search, like a handler
|
|
154
|
+
`ctx` (`/use-cache`). A fetchable loader called with a request body
|
|
155
|
+
(`load({ method: "POST", body })` or form data) is the exception: the body is
|
|
156
|
+
not in the key, so the call runs uncached. Pass the body fields the function
|
|
157
|
+
needs as arguments instead.
|
|
143
158
|
|
|
144
159
|
### ctx.use(Loader) — escape hatch
|
|
145
160
|
|
|
@@ -157,7 +172,9 @@ path("/product/:slug", async (ctx) => {
|
|
|
157
172
|
])
|
|
158
173
|
```
|
|
159
174
|
|
|
160
|
-
`ctx.use(Loader)` is a
|
|
175
|
+
`ctx.use(Loader)` is a per-request read on any render that runs the handler,
|
|
176
|
+
memoized for the request (a route `cache()` HIT or a `ppr` shell HIT runs no
|
|
177
|
+
handler, so the handler's output from the read is replayed): however many
|
|
161
178
|
times it is called, and whether or not the loader is also registered with
|
|
162
179
|
`loader()`, it runs once per request. It never reads or writes the loader
|
|
163
180
|
store cache on its own: a loader's `cache()` belongs to its DSL binding
|
|
@@ -171,11 +188,16 @@ the tree).
|
|
|
171
188
|
- The handler output depends on the loader data. If the route is inside
|
|
172
189
|
`cache()`, the handler is cached with the loader result baked in —
|
|
173
190
|
defeating the live data guarantee.
|
|
174
|
-
- The same holds under a PPR shell capture (`/ppr`): handler consumption is
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
191
|
+
- The same holds under a PPR shell capture (`/ppr`): handler consumption is the
|
|
192
|
+
BAKED lane — the loader executes at capture and the rendered value is a
|
|
193
|
+
capture-time copy served on every HIT (a HIT never runs the handler);
|
|
194
|
+
`useLoader` of a loader without `ssr: false` is the live lane (an `ssr: false`
|
|
195
|
+
loader is the bake lane however it is read). Unlike `cache()`, an identity
|
|
196
|
+
read inside that loader (`cookies()`, `headers()`, `ctx.request.headers`, a
|
|
197
|
+
`{ cache: false }` variable) refuses the capture, so the route stays
|
|
198
|
+
uncached. One rule across
|
|
199
|
+
`cache()`, `"use cache"`, and PPR: the consumption-lane rule (`/rango` →
|
|
200
|
+
Invariants).
|
|
179
201
|
- Non-cacheable variable reads (`createVar({ cache: false })`) inside the
|
|
180
202
|
handler still throw, even if the data came from a loader.
|
|
181
203
|
- Prefer DSL `loader()` + client `useLoader()` for data that depends on
|
|
@@ -237,7 +259,8 @@ export const ProductLoader = createLoader(async (ctx) => {
|
|
|
237
259
|
// Platform bindings (DB, KV, etc.) — plain bindings from createRouter<TEnv>()
|
|
238
260
|
const db = ctx.env.DB;
|
|
239
261
|
|
|
240
|
-
// Request headers
|
|
262
|
+
// Request headers (guarded like headers(): an unkeyed loader cache() fill
|
|
263
|
+
// or a ppr capture of this loader refuses the read)
|
|
241
264
|
const auth = ctx.request.headers.get("Authorization");
|
|
242
265
|
|
|
243
266
|
// Variables set by middleware (from Rango.Vars augmentation)
|
|
@@ -491,11 +514,16 @@ Loaders resolve fresh on every request, even when the route's UI segments are
|
|
|
491
514
|
served from cache. Route-level `cache()` caches rendered segments but never
|
|
492
515
|
loader data: loaders are excluded when the segments are stored and re-resolved
|
|
493
516
|
when they are served. Caching a loader's own data is a separate opt-in (see
|
|
494
|
-
"Opting a Loader into Caching").
|
|
517
|
+
"Opting a Loader into Caching"). A `ppr` shell does store some loader data: an
|
|
518
|
+
`ssr: false` loader's settled data bakes into the shell, and a HIT runs that
|
|
519
|
+
loader only when its return carries promises (see "`ssr: false`" below and
|
|
520
|
+
`/ppr`).
|
|
495
521
|
|
|
496
|
-
Pre-rendering follows the same rule:
|
|
497
|
-
(there is no real request context), and at runtime the worker resolves
|
|
498
|
-
fresh against the live database.
|
|
522
|
+
Pre-rendering follows the same rule: the build-time Flight payload skips
|
|
523
|
+
loaders (there is no real request context), and at runtime the worker resolves
|
|
524
|
+
them fresh against the live database. The exception is a `Prerender` + `ppr`
|
|
525
|
+
route: its build-time shell capture runs `ssr: false` loaders and bakes their
|
|
526
|
+
settled data into the shell, like a runtime capture.
|
|
499
527
|
|
|
500
528
|
### Parallel and streaming — latency overlaps first paint
|
|
501
529
|
|
|
@@ -516,8 +544,8 @@ If you come from a framework where the loader is a blocking step that runs
|
|
|
516
544
|
before the response is built, this is the shift to internalize: here the
|
|
517
545
|
response starts streaming first and loader data fills in. (The one deliberate
|
|
518
546
|
exception is per-loader: `loader(Def, { ssr: false })` awaits that
|
|
519
|
-
loader before first flush on document renders
|
|
520
|
-
below.)
|
|
547
|
+
loader before first flush on document renders other than a `ppr` shell HIT,
|
|
548
|
+
where the shell flushes first — see "`ssr: false`" below.)
|
|
521
549
|
|
|
522
550
|
### See it: `debugPerformance`
|
|
523
551
|
|
|
@@ -579,7 +607,11 @@ loader(ProductLoader, () => [cache({ ttl: 300 })]),
|
|
|
579
607
|
|
|
580
608
|
This works wherever the DSL binding is declared, an intercept's `use()`
|
|
581
609
|
included. A handler's `ctx.use(Loader)` read never caches by itself (see
|
|
582
|
-
"ctx.use(Loader) — escape hatch").
|
|
610
|
+
"ctx.use(Loader) — escape hatch"). Once the binding started, every
|
|
611
|
+
`ctx.use(Loader)` in the request, from the handler or from another loader's
|
|
612
|
+
body, gets the binding's value: one value per loader per request, a HIT
|
|
613
|
+
included. A reader that runs before the binding starts (a layout's loader
|
|
614
|
+
reading a route's cached loader) gets the value of its own run, as before.
|
|
583
615
|
|
|
584
616
|
The loader's data is cached independently from the route's segment cache,
|
|
585
617
|
using the same `SegmentCacheStore` (app-level or per-loader override).
|
|
@@ -626,6 +658,7 @@ at two levels:
|
|
|
626
658
|
import { cookies } from "@rangojs/router";
|
|
627
659
|
|
|
628
660
|
// Full override — key function replaces the default entirely
|
|
661
|
+
// Stored as "loader:{loaderId}:key:product%3Ashoe%3Aen"
|
|
629
662
|
loader(ProductLoader, () => [
|
|
630
663
|
cache({
|
|
631
664
|
ttl: 300,
|
|
@@ -637,17 +670,92 @@ loader(ProductLoader, () => [
|
|
|
637
670
|
// Set in the store configuration, applies to all entries in that store
|
|
638
671
|
```
|
|
639
672
|
|
|
640
|
-
Resolution priority (same as route-level `cache()`):
|
|
673
|
+
Resolution priority (same order as route-level `cache()`):
|
|
641
674
|
|
|
642
|
-
1. `key(ctx)` from cache options — full override
|
|
643
|
-
|
|
675
|
+
1. `key(ctx)` from cache options — full override, stored namespaced as
|
|
676
|
+
`loader:{loaderId}:key:` plus the result's URI encoding
|
|
677
|
+
2. `store.keyGenerator(ctx, defaultKey)` — store-level modification, stored
|
|
678
|
+
as returned
|
|
644
679
|
3. Default key — `loader:{loaderId}:{host}{pathname}:{sortedParams}`
|
|
645
680
|
|
|
681
|
+
Because the `key()` result is namespaced by the loader id, no value it
|
|
682
|
+
returns, request input included, can name another loader's entry or a
|
|
683
|
+
default key: two loaders whose `key()` returns the same value keep separate
|
|
684
|
+
entries. A readable prefix is still good practice. A store `keyGenerator`
|
|
685
|
+
result is stored as returned, so build it on the `defaultKey` it receives
|
|
686
|
+
(which carries the loader id): a generator that returns raw request input
|
|
687
|
+
can still name another entry.
|
|
688
|
+
|
|
646
689
|
A `key` function (or store `keyGenerator`) that throws is **not** caught: the
|
|
647
690
|
loader fails as if its body threw. There is no silent fallback to the default
|
|
648
691
|
key, because a personalised key collapsing onto the broad default would share
|
|
649
692
|
one user's data with everyone.
|
|
650
693
|
|
|
694
|
+
**Request-scoped reads in the body must be part of the key.** The default key
|
|
695
|
+
names no user, so one entry serves everyone who requests the same loader, host,
|
|
696
|
+
path and params. A route `cache()` around the route does not partition it
|
|
697
|
+
either: a loader's own `cache()` is an independent layer, keyed only by what it
|
|
698
|
+
declares. On a miss, a body that reads `cookies()`, `headers()`,
|
|
699
|
+
`ctx.request.headers` or a non-cacheable variable (`createVar({ cache: false })`,
|
|
700
|
+
or a value written with `ctx.set(..., { cache: false })`) therefore **fails**,
|
|
701
|
+
and nothing is stored,
|
|
702
|
+
unless the binding declares identity with a `key()` or a store `keyGenerator`.
|
|
703
|
+
The check tests only that one of them is there, not what it contains: any
|
|
704
|
+
`key()` or store `keyGenerator` switches it off, so it must itself include the
|
|
705
|
+
value. That includes a store-wide `keyGenerator` that only adds a region
|
|
706
|
+
prefix, like the one described above.
|
|
707
|
+
|
|
708
|
+
```typescript
|
|
709
|
+
export const SessionLoader = createLoader(async () =>
|
|
710
|
+
getAccount(cookies().get("session")?.value),
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
// The miss fails: cookies() cannot be called inside loader "…SessionLoader",
|
|
714
|
+
// whose own cache() has no key().
|
|
715
|
+
loader(SessionLoader, () => [cache({ ttl: 60 })]),
|
|
716
|
+
|
|
717
|
+
// One entry per session.
|
|
718
|
+
loader(SessionLoader, () => [
|
|
719
|
+
cache({
|
|
720
|
+
ttl: 60,
|
|
721
|
+
key: (ctx) => `account:${cookies().get("session")?.value ?? "anon"}`,
|
|
722
|
+
}),
|
|
723
|
+
]),
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
The same holds in a stale entry's background refresh (the refresh fails and the
|
|
727
|
+
stale entry keeps serving) and for the loaders the body reads with `ctx.use()`,
|
|
728
|
+
because their values land in the entry. That includes a keyed cached loader
|
|
729
|
+
served from its own cache: its entry keeps the read its miss made. It also holds
|
|
730
|
+
when something else ran the loader first, such as a parent layout's handler
|
|
731
|
+
calling `ctx.use(SessionLoader)`: the miss reuses that run and fails the same
|
|
732
|
+
way. A read that settles after the value, in a promise inside it or in a
|
|
733
|
+
handle push that settles within the 5 s handle-encode timeout, can't fail a
|
|
734
|
+
value that was already served: that user gets their own value, nothing is
|
|
735
|
+
stored, and `onError` gets the same error. A push still pending after the
|
|
736
|
+
timeout drops the entry's handles, and the value is stored without them. A loader with no `cache()` of its own
|
|
737
|
+
reads request data freely.
|
|
738
|
+
|
|
739
|
+
The raw reads count the same: `ctx.request.headers` (the loader's
|
|
740
|
+
`ctx.request` is the request's own `Request`) and `getRequestContext().cookie()`
|
|
741
|
+
/ `.cookies()` fail an unkeyed miss exactly like `cookies()`. A `key()` or a
|
|
742
|
+
store `keyGenerator` reads them freely, since that is how the entry is
|
|
743
|
+
partitioned:
|
|
744
|
+
|
|
745
|
+
```typescript
|
|
746
|
+
loader(LocaleLoader, () => [
|
|
747
|
+
cache({
|
|
748
|
+
ttl: 60,
|
|
749
|
+
key: (ctx) => `lang:${ctx.request.headers.get("accept-language") ?? "en"}`,
|
|
750
|
+
}),
|
|
751
|
+
]),
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
One kind of read is not seen, the same as in `"use cache"`: a value computed
|
|
755
|
+
outside the loader and handed in, such as a per-request memo a handler filled
|
|
756
|
+
from `cookies()` before the loader awaited it. Key what it carries too. If the
|
|
757
|
+
value must stay per request, drop the loader's `cache()` instead.
|
|
758
|
+
|
|
651
759
|
### Tags for Invalidation
|
|
652
760
|
|
|
653
761
|
```typescript
|
|
@@ -665,6 +773,34 @@ loader(ProductLoader, () => [
|
|
|
665
773
|
]),
|
|
666
774
|
```
|
|
667
775
|
|
|
776
|
+
The tags the loader body records tag the entry too: `cacheTag()` calls, the
|
|
777
|
+
tags of `"use cache"` reads, render-time `cacheTag()` in server components of
|
|
778
|
+
the value or a handle push, and the tags the loaders it reads with `ctx.use`
|
|
779
|
+
record, including one the route, a layout or the handler started first. A
|
|
780
|
+
loader it reads that has its own `cache()` also brings its `cache({ tags })`
|
|
781
|
+
and, on a HIT, its entry's tags. `updateTag()` of any of them drops the entry,
|
|
782
|
+
and a HIT records them again, so a route `cache()`, ppr shell or document built
|
|
783
|
+
over the HIT stays evictable. A stale hit's background refresh stores the
|
|
784
|
+
refreshed body's tags; it runs the loaders it reads again rather than reusing
|
|
785
|
+
the page's (possibly stale) values.
|
|
786
|
+
|
|
787
|
+
One case keeps a `cache({ tags })` off: a cached loader that reads another
|
|
788
|
+
loader's `cache()` binding before that binding starts (for example a cached
|
|
789
|
+
layout loader reading a route's cached loader), and writes its entry before
|
|
790
|
+
it. That reader's entry has the tags the loader's body records, but not the
|
|
791
|
+
tags only its `cache({ tags })` lists. If the reader must drop with them, record
|
|
792
|
+
them in the loader body with `cacheTag()` too.
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
796
|
+
cacheTag(`product:${ctx.params.slug}`);
|
|
797
|
+
return db.product(ctx.params.slug);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
loader(ProductLoader, () => [cache({ ttl: 600 })]),
|
|
801
|
+
// updateTag(`product:${slug}`) drops this entry.
|
|
802
|
+
```
|
|
803
|
+
|
|
668
804
|
### Stale-While-Revalidate
|
|
669
805
|
|
|
670
806
|
```typescript
|
|
@@ -691,7 +827,8 @@ loader(ProductLoader, () => [
|
|
|
691
827
|
```
|
|
692
828
|
|
|
693
829
|
When `condition` returns false, the loader runs fresh and the cache is bypassed
|
|
694
|
-
entirely (no read, no write).
|
|
830
|
+
entirely (no read, no write). Like `key()`, a `condition()` reads request
|
|
831
|
+
identity freely: it decides whether the entry is used, it is not stored in it.
|
|
695
832
|
|
|
696
833
|
### Per-Loader Store Override
|
|
697
834
|
|
|
@@ -818,7 +955,10 @@ flushes, so both signals are resolved server-side while the tree is built:
|
|
|
818
955
|
`redirect()` replaces the whole page with the redirect carrier (still a 200
|
|
819
956
|
document, the client replaces to the target on hydration) and `notFound()`
|
|
820
957
|
renders the not-found UI at the owning segment with a real 404. No read site
|
|
821
|
-
runs, so a `useLoader` in a layout above every Suspense boundary is safe.
|
|
958
|
+
runs, so a `useLoader` in a layout above every Suspense boundary is safe. On a
|
|
959
|
+
`ppr` route, a flagged loader that settles with `notFound()` or `redirect()`
|
|
960
|
+
during the shell capture refuses the capture (warned once per key): that URL
|
|
961
|
+
gets no shell, and its renders deliver the signal as above.
|
|
822
962
|
|
|
823
963
|
Session/auth gates belong in middleware (they are request-shaped, not
|
|
824
964
|
data-shaped, and middleware CAN emit a real pre-stream 302). Data-dependent
|
|
@@ -857,7 +997,9 @@ they are replayed from the entry (see "Opting a Loader into Caching").
|
|
|
857
997
|
|
|
858
998
|
Under `cache()`, loader pushes are not stored with the cached segments: the
|
|
859
999
|
loader re-runs on every hit and its live push is the only copy. (`ppr` keeps
|
|
860
|
-
settled `ssr: false`
|
|
1000
|
+
the settled pushes of an `ssr: false` loader, and of the loaders it awaits, in
|
|
1001
|
+
the shell, and a promise-free `ssr: false` loader does not run on a shell HIT —
|
|
1002
|
+
see `/ppr`.)
|
|
861
1003
|
|
|
862
1004
|
Reads are the other direction and gated: `ctx.get(handle)` throws unless the
|
|
863
1005
|
loader first does `await ctx.rendered()` (DSL-registered loaders only —
|
|
@@ -883,14 +1025,17 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
|
883
1025
|
The options object is typed as `LoaderOptions`
|
|
884
1026
|
(`import type { LoaderOptions } from "@rangojs/router"`).
|
|
885
1027
|
|
|
886
|
-
The knob mirrors `loading(fallback, { ssr: false })` — SSR delivery is off
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1028
|
+
The knob mirrors `loading(fallback, { ssr: false })` — SSR delivery is off for
|
|
1029
|
+
this loader, so nothing of it is left to stream in the document: document
|
|
1030
|
+
renders await this loader before first flush — data is settled (`useLoader`
|
|
1031
|
+
reads it synchronously, no fallback paints), handle pushes beat the barrier
|
|
1032
|
+
snapshot, and a thrown `notFound()` deterministically precedes Response
|
|
1033
|
+
construction (real 404, no warm-up race). Client navigations stream exactly as
|
|
1034
|
+
before (on a `ppr` route, a navigation that replays a shell captured by a
|
|
1035
|
+
document request serves a promise-free flagged loader from its pin, as a
|
|
1036
|
+
document HIT does; a snapshot captured by a navigation alone carries no pins,
|
|
1037
|
+
and its replay runs the loader fresh). Scoped per LOADER: the flagged loader
|
|
1038
|
+
awaits only itself; siblings keep streaming.
|
|
894
1039
|
|
|
895
1040
|
Delivery is in-place, not merely in-document. React's Fizz outlines any
|
|
896
1041
|
COMPLETED Suspense boundary over ~500 bytes to an end-of-stream
|
|
@@ -906,17 +1051,25 @@ still outline; their reveal must wait for the CSS.
|
|
|
906
1051
|
|
|
907
1052
|
The costs and constraints:
|
|
908
1053
|
|
|
909
|
-
- Every document
|
|
910
|
-
That is the point — but keep flagged
|
|
911
|
-
routes.
|
|
1054
|
+
- Every document render that is not a `ppr` shell HIT pays the flagged
|
|
1055
|
+
loader's latency before first byte. That is the point — but keep flagged
|
|
1056
|
+
loaders fast, and flag loaders, not routes. On a HIT the shell flushes
|
|
1057
|
+
first: a flagged loader whose return has no promises does not run (unless
|
|
1058
|
+
the capture marked it to run: see `/ppr` → On a shell HIT), and one whose
|
|
1059
|
+
return carries promises runs to fill its holes.
|
|
912
1060
|
- A flagged loader must not `await ctx.rendered()` / `ctx.get(handle)` — the
|
|
913
1061
|
document render awaits the loader before the render barrier resolves, so
|
|
914
1062
|
that wait is a cycle by construction; it throws a deadlock error naming the
|
|
915
1063
|
fix.
|
|
916
1064
|
- Under PPR the flag is also the bake lane: the settled non-promise data
|
|
917
1065
|
(handle pushes included) freezes into the stored shell — see `/ppr` → The
|
|
918
|
-
loader lane rule.
|
|
919
|
-
|
|
1066
|
+
loader lane rule. On a HIT, a flagged loader whose return has no promises is
|
|
1067
|
+
served from the shell and does not run (unless the capture marked it to run:
|
|
1068
|
+
see `/ppr` → On a shell HIT); one whose return carries promises
|
|
1069
|
+
runs on every HIT, with its baked parts overlaid. A `cookies()`, `headers()`,
|
|
1070
|
+
`ctx.request.headers` or `{ cache: false }` read in it refuses the capture. The
|
|
1071
|
+
`progressiveChunkSize` auto-raise is live-document only; captured shells
|
|
1072
|
+
outline per the explicit option or React's default.
|
|
920
1073
|
|
|
921
1074
|
Also available in `clientUrls()` route groups (`/client-urls`), where the
|
|
922
1075
|
loader-heavy shape makes it most useful.
|
|
@@ -75,7 +75,10 @@ render pass — normal renders, post-action revalidation, PE re-renders — its
|
|
|
75
75
|
variables are never stale: middleware is the safest `ctx.set` rung on the
|
|
76
76
|
data-passing ladder (`/rango` → "Passing data down the tree"). But it does
|
|
77
77
|
not change partial revalidation boundaries between handler/layout/parallel
|
|
78
|
-
segments.
|
|
78
|
+
segments. On a PPR shell HIT no handler or layout runs; on a route `cache()`
|
|
79
|
+
HIT the segments inside the `cache()` boundary don't run, and those above it
|
|
80
|
+
do. Output that didn't run shows the values read when the shell was captured
|
|
81
|
+
or the entry written.
|
|
79
82
|
|
|
80
83
|
For shared segment data, use named revalidation contracts on both the producer
|
|
81
84
|
and consumer segments, even when middleware is present in the chain.
|
|
@@ -271,6 +274,24 @@ export const myMiddleware: Middleware = async (ctx, next) => {
|
|
|
271
274
|
Cookies and request headers are not `ctx` members; use the free functions
|
|
272
275
|
`cookies()` and `headers()` from `@rangojs/router`.
|
|
273
276
|
|
|
277
|
+
Middleware reads request identity freely: `cookies()`, `headers()` and
|
|
278
|
+
`ctx.request.headers` alike. The same `Request` reaches handlers and loaders
|
|
279
|
+
as `ctx.request`, and there `ctx.request.headers` answers like `headers()`: it
|
|
280
|
+
throws inside a `cache()` boundary or a `"use cache"` function and refuses a
|
|
281
|
+
`ppr` shell capture (#976). To render a header on a cached or `ppr` route,
|
|
282
|
+
copy it here with `ctx.set()` (a cacheable variable) and include it in the
|
|
283
|
+
route's `cache()` `key()`, so each value gets its own record and shell:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
const Tier = createVar<string>();
|
|
287
|
+
|
|
288
|
+
export const tierMiddleware: Middleware = async (ctx, next) => {
|
|
289
|
+
ctx.set(Tier, ctx.request.headers.get("x-tier") ?? "free");
|
|
290
|
+
return next();
|
|
291
|
+
};
|
|
292
|
+
// cache({ key: (ctx) => `tier:${ctx.request.headers.get("x-tier") ?? "free"}` }, ...)
|
|
293
|
+
```
|
|
294
|
+
|
|
274
295
|
### Changing the response after `next()`
|
|
275
296
|
|
|
276
297
|
After `await next()`, `ctx.header()` and `ctx.headers` write to the real
|
|
@@ -334,19 +355,23 @@ the token (`createVar<T>({ cache: false })`) or per write
|
|
|
334
355
|
(`ctx.set("user", user, { cache: false })`). Reading it with `ctx.get()` inside
|
|
335
356
|
a `cache()` boundary or a `"use cache"` function then throws instead of baking
|
|
336
357
|
one user's data into a shared entry (inside `"use cache"`, also through a
|
|
337
|
-
middleware `ctx` passed in); loaders can still read it because
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
358
|
+
middleware `ctx` passed in); under `cache()` loaders can still read it because a
|
|
359
|
+
DSL loader re-runs on every hit (a `"use cache"` function a loader calls cannot,
|
|
360
|
+
and a loader bound with its own `cache()` only with a `key()`, which switches
|
|
361
|
+
the check off, so the key must include the value). On a `ppr` route only a
|
|
362
|
+
loader without `ssr: false` that no handler awaits may read it: a read the shell
|
|
363
|
+
capture waits for refuses the capture. A middleware `ctx` passed to a
|
|
364
|
+
`"use cache"` function keys the call by host, pathname, params and search, plus
|
|
365
|
+
the route name once the route is matched (a global middleware before `next()`
|
|
366
|
+
has none yet), and its `set()`, `header()` and `headers` writes throw inside the
|
|
367
|
+
function (`/use-cache`).
|
|
343
368
|
|
|
344
369
|
## Build-Time PPR Middleware
|
|
345
370
|
|
|
346
371
|
Normal `Prerender` Flight payload collection does not run middleware: there is
|
|
347
372
|
no request to wrap. The exception is `Prerender` + `ppr` build-shell capture.
|
|
348
|
-
After the Flight payload exists, the
|
|
349
|
-
|
|
373
|
+
After the Flight payload exists, the build replays global and route middleware
|
|
374
|
+
for each generated URL before capturing the shell.
|
|
350
375
|
|
|
351
376
|
In that build-shell pass:
|
|
352
377
|
|