@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/skills/ppr/SKILL.md
CHANGED
|
@@ -51,7 +51,9 @@ only prerequisite is an app-level `createRouter({ cache })` store that
|
|
|
51
51
|
implements the shell family (`getShell`/`putShell`): `MemorySegmentCacheStore`
|
|
52
52
|
(dev/tests), `CFCacheStore` (Cache API L1 + optional KV L2), or
|
|
53
53
|
`VercelCacheStore` (Vercel Runtime Cache). A ppr route on a store without the
|
|
54
|
-
family renders normally (no shell) with a once-per-key warning.
|
|
54
|
+
family renders normally (no shell) with a once-per-key warning. A custom store
|
|
55
|
+
returns each `ShellCacheEntry` field as `putShell` received it, including
|
|
56
|
+
`buildVersion` and `snapshot` (both required; the snapshot can be empty).
|
|
55
57
|
|
|
56
58
|
```typescript
|
|
57
59
|
import { createRouter, urls } from "@rangojs/router";
|
|
@@ -138,7 +140,8 @@ lifetime. A HIT never runs a handler. To keep a value fresh per request, load
|
|
|
138
140
|
it in a loader without `ssr: false` and read it with `useLoader` under
|
|
139
141
|
`loading()` or an inline `<Suspense>`; inside an `ssr: false` loader, return
|
|
140
142
|
that part as a nested promise. Request-scoped reads the capture now waits for
|
|
141
|
-
refuse it: `cookies()`, `headers()`,
|
|
143
|
+
refuse it: `cookies()`, `headers()`, `ctx.request.headers`, a
|
|
144
|
+
`{ cache: false }` variable, and
|
|
142
145
|
`ctx.dynamic()` inside a handler promise, an async component, a handle push, or
|
|
143
146
|
a loader a handler awaits. A normal `ctx.get()` value is not guarded: shell
|
|
144
147
|
material is shared per host+URL and request partition. `cache(false)`, or a
|
|
@@ -163,12 +166,17 @@ cache(
|
|
|
163
166
|
|
|
164
167
|
The partition follows nested scopes (issue #970): a `ppr` route under a
|
|
165
168
|
`cache()` nested in a keyed one is partitioned by the outer `key()` when the
|
|
166
|
-
inner boundary has none, and by both composed (outermost first,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
inner boundary has none, and by both composed (outermost first, joined by
|
|
170
|
+
`|`) when it has its own. Each `key()` result is namespaced as `key:` plus
|
|
171
|
+
its URI encoding (issue #975), exactly as in the record key, so the shell of
|
|
172
|
+
`key: () => "tier:gold"` is `<host><path>:shell|key%3Atier%253Agold`. The
|
|
173
|
+
route's record adds the inner boundary's own default key when it has no
|
|
174
|
+
`key`; the shell partition does not, since the shell key already carries the
|
|
175
|
+
URL (a store `keyGenerator`'s result is kept when it adds anything, an
|
|
176
|
+
enclosing `cache({ store })`'s on another store included, issue #974). An
|
|
177
|
+
enclosing `condition()` that refuses the request means no shell, as the
|
|
178
|
+
route's own does. See `/caching`, "Keys nest" and "Conditions and tags
|
|
179
|
+
inherit".
|
|
172
180
|
|
|
173
181
|
```tsx
|
|
174
182
|
cache({ ttl: 300, key: (ctx) => `tier:${tierOf(ctx)}` }, () => [
|
|
@@ -180,8 +188,11 @@ cache({ ttl: 300, key: (ctx) => `tier:${tierOf(ctx)}` }, () => [
|
|
|
180
188
|
```
|
|
181
189
|
|
|
182
190
|
This is the sanctioned way to vary shell material by request: the capture
|
|
183
|
-
guards still refuse `cookies()`/`headers()` in handler
|
|
184
|
-
function
|
|
191
|
+
guards still refuse `cookies()`/`headers()`/`ctx.request.headers` in handler
|
|
192
|
+
work, while the key function, a store `keyGenerator`, a `condition()` and a `tags()` function read
|
|
193
|
+
what they need (`cookies()` and `ctx.request.headers` included, at capture
|
|
194
|
+
too). To render the partition value, copy it in middleware with `ctx.set()`
|
|
195
|
+
and read `ctx.get()` in the handler (see `/middleware`). A `key()` runs once per
|
|
185
196
|
request: the shell read, the record lookup and the capture share its result
|
|
186
197
|
(each `key()` of a nested chain once).
|
|
187
198
|
One that throws serves no shell (like the record path renders uncached); a
|
|
@@ -209,9 +220,11 @@ value of it.
|
|
|
209
220
|
|
|
210
221
|
In tests, `serveShellRequest` reports the key the serve path resolved
|
|
211
222
|
(`result.key`, partition included); `shellCacheKey(url, searchParams,
|
|
212
|
-
partition)` builds the same key from the
|
|
213
|
-
scopes, pass the `key()` results as an
|
|
214
|
-
(`shellCacheKey(url, undefined, ["tier:gold",
|
|
223
|
+
partition)` builds the same key from the `key()` result, namespacing it as
|
|
224
|
+
production does. Under nested keyed scopes, pass the `key()` results as an
|
|
225
|
+
array, outermost first (`shellCacheKey(url, undefined, ["tier:gold",
|
|
226
|
+
"v:a"])`); for a store `keyGenerator` partition pass
|
|
227
|
+
`{ keys, generated }` (`/testing`, `cache-prerender.md`).
|
|
215
228
|
|
|
216
229
|
## Where PPR sits: the cache onion
|
|
217
230
|
|
|
@@ -321,8 +334,8 @@ replay its eligible snapshot. In both cases `matchPartial()`:
|
|
|
321
334
|
- keeps the existing prefetch key, source scope, and in-flight lock unchanged.
|
|
322
335
|
|
|
323
336
|
This is deliberately invisible to the browser: the response is the same
|
|
324
|
-
`RscPayload` shape as any other partial navigation.
|
|
325
|
-
|
|
337
|
+
`RscPayload` shape as any other partial navigation. A shell snapshot holds no
|
|
338
|
+
`"use cache"` or loader `cache()` values. Bake-lane loader pins are, when the replayed
|
|
326
339
|
snapshot is a shell a document request captured, as on a document HIT: a
|
|
327
340
|
promise-free `ssr: false` loader is served from its pin without running
|
|
328
341
|
(unless the capture marked it to run: see "On a shell HIT"), and one whose
|
|
@@ -352,13 +365,20 @@ backend failure (`CACHE_READ_ERROR`) — also never falls back: it renders
|
|
|
352
365
|
uncached, exactly as without ppr. (Document requests treat the same two
|
|
353
366
|
opt-outs as a cache miss before any shell read: see the serve pipeline above.)
|
|
354
367
|
|
|
355
|
-
`transition({ when })` is
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
368
|
+
`transition({ when })` is a browser predicate: the server never evaluates
|
|
369
|
+
it, so a shell, a replay and a cache entry never hold a decision. The route
|
|
370
|
+
carries it as a client reference (attached right before Flight on every
|
|
371
|
+
match, never stored), and the browser decides each navigation from
|
|
372
|
+
`{ kind, from, to }`. Location state middleware sets reaches `to.state` on a
|
|
373
|
+
replay `HIT` (middleware runs on every HIT); a handler's does not (a HIT runs
|
|
374
|
+
no handler). The decision gates only how the browser commits; it never makes
|
|
375
|
+
a replay send a segment. A replay `HIT`
|
|
376
|
+
makes the same segment decision as the live partial path: a segment the client
|
|
377
|
+
holds that the navigation does not re-send (`revalidate()` false, or a layout
|
|
378
|
+
the default keeps) is omitted from the response, so the client keeps its tree
|
|
379
|
+
(a list the client built up, "Load more" style, stays as it is). Intercepts, an active nonce, and an
|
|
359
380
|
absent/corrupt segment snapshot fall open to the ordinary partial path when
|
|
360
|
-
encountered by the shell capture.
|
|
361
|
-
explicit cache tier remains frozen by that tier's normal semantics.
|
|
381
|
+
encountered by the shell capture.
|
|
362
382
|
|
|
363
383
|
Two more decisions are made before any shell-store read, so probes and
|
|
364
384
|
prerendered routes never spend passive `getShell` I/O:
|
|
@@ -413,17 +433,17 @@ consumer-invisible.
|
|
|
413
433
|
|
|
414
434
|
The bounded bypass tokens, grouped by when they are decided:
|
|
415
435
|
|
|
416
|
-
| Token
|
|
417
|
-
|
|
|
418
|
-
| `method`, `dynamic`, `nonce`, `store-unavailable`, `passive-read-unsupported`
|
|
419
|
-
| `no-navigation-context`
|
|
420
|
-
| `prerender-store`
|
|
421
|
-
| `intercept`
|
|
422
|
-
| `cache-disabled`
|
|
423
|
-
| `read-error`, `no-entry`, `invalid-version`, `corrupt-entry
|
|
424
|
-
| `no-segment-snapshot`
|
|
425
|
-
| `explicit-cache-hit`
|
|
426
|
-
| `snapshot-miss`
|
|
436
|
+
| Token | Decided | Meaning |
|
|
437
|
+
| ----------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
438
|
+
| `method`, `dynamic`, `nonce`, `store-unavailable`, `passive-read-unsupported` | pre-read | request/route/store ineligible for replay |
|
|
439
|
+
| `no-navigation-context` | pre-read | no `X-RSC-Router-Client-Path`/`Referer`; a partial match is impossible |
|
|
440
|
+
| `prerender-store` | pre-read or match | `Prerender()` route served by its baked artifact (pre-read probe of the normal variant; reclassified post-match when the store actually served, either variant) |
|
|
441
|
+
| `intercept` | match | the navigation resolved to an intercept — replay is never armed for intercepts (they keep their normal cache path); no heal capture |
|
|
442
|
+
| `cache-disabled` | pre-read or match | `cache(false)` (pre-read, static) or `condition()` false (decided at the lookup); consumer opt-out is absolute |
|
|
443
|
+
| `read-error`, `no-entry`, `invalid-version`, `corrupt-entry` | shell read | no usable shell entry (`no-entry`/`invalid-version`/`corrupt-entry` schedule the navigation-only heal capture) |
|
|
444
|
+
| `no-segment-snapshot` | eligibility | entry exists but its snapshot has no recorded segments for the page; on a route with an enabled `cache()` scope, it heals when the lookup did not refuse (a `condition()` false-at-capture entry becomes replayable) |
|
|
445
|
+
| `explicit-cache-hit` | match | the route's own `cache()` tier supplied the match |
|
|
446
|
+
| `snapshot-miss` | match | an eligible snapshot was seeded but matching did not consume it |
|
|
427
447
|
|
|
428
448
|
### Capture-generation invalidation
|
|
429
449
|
|
|
@@ -522,10 +542,9 @@ curl -s -D - -o /dev/null https://app.example.com/products/1 | grep -i x-rango-s
|
|
|
522
542
|
capture diagnoses itself. A stored attempt reports `recordSettleMs`
|
|
523
543
|
(`record=` in the log line): how long the capture waited for the handler
|
|
524
544
|
layer to settle (promises it passes or pushes, async server components,
|
|
525
|
-
loaders it awaits
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
(`entry=`): the stored entry's size. In dev, past 2s, the slow source is also
|
|
545
|
+
loaders it awaits, top-level pushed handle promises and bake-lane loader
|
|
546
|
+
containers) and its segment record to encode; and `entryBytes` (`entry=`):
|
|
547
|
+
the stored entry's size. In dev, past 2s, the settle is also
|
|
529
548
|
named once per key in a console warning with the remedies (`cache()` the
|
|
530
549
|
work / move it into a live loader / return it as a nested promise or drop
|
|
531
550
|
`ssr: false` in a bake-lane loader). In dev, with
|
|
@@ -776,7 +795,9 @@ identifiers — served to anonymous visitors.)
|
|
|
776
795
|
consumer's `<Suspense>` on every HIT (the same mask as bake-lane loader data,
|
|
777
796
|
at any depth of plain objects, arrays and JSX props; a promise inside a
|
|
778
797
|
`Map`, `Set`, or class instance is not masked). A live-lane
|
|
779
|
-
loader never runs at capture
|
|
798
|
+
loader never runs at capture unless a handler or an `ssr: false` loader
|
|
799
|
+
awaits it; either
|
|
800
|
+
way its pushes, and those of the loaders it awaits, are live on every HIT.
|
|
780
801
|
|
|
781
802
|
### Want a hole for already-resolved data?
|
|
782
803
|
|
|
@@ -865,18 +886,41 @@ loader(ProductLoader, { ssr: false }), // promise-free: baked, not run on a HIT
|
|
|
865
886
|
loader(ReviewsLoader), // live: the hole, under loading()
|
|
866
887
|
```
|
|
867
888
|
|
|
889
|
+
**A hole never reads the shell's copy of a value.** The shell stores its
|
|
890
|
+
recorded segments and the `ssr: false` loaders' baked containers, and no
|
|
891
|
+
`"use cache"` or loader `cache()` value. Every cache read on a HIT goes to
|
|
892
|
+
the store: a live loader's, a nested promise's, and the body of an
|
|
893
|
+
`ssr: false` loader that runs on the HIT. So a live loader that reads the
|
|
894
|
+
same `"use cache"` key as an `ssr: false` loader shows the store's current
|
|
895
|
+
value once it refreshes, while the baked container keeps the capture's. If a
|
|
896
|
+
hole needs a stable value, give it its own cache (`"use cache"` with a
|
|
897
|
+
profile, or a loader `cache()`); the shell never provides one.
|
|
898
|
+
|
|
868
899
|
**Handle pushes from a bake-lane loader appear once.** The capture records
|
|
869
900
|
the settled, thenable-free handle pushes of every loader body it runs (the
|
|
870
901
|
prelude rendered them): the `ssr: false` loader's own, those of the loaders
|
|
871
|
-
it awaits with `ctx.use()`, and those its own `cache()` entry replays. Every
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
902
|
+
it awaits with `ctx.use()`, and those its own `cache()` entry replays. Every
|
|
903
|
+
HIT restores them with the handler layer, and they stand: a bake-lane loader
|
|
904
|
+
that does run on the HIT (a hole-carrying one) reads the store, so its settled
|
|
905
|
+
pushes, and those a `"use cache"` hit inside its body replays, are dropped
|
|
906
|
+
instead of replacing the restored ones. Any handle, deduping or not, shows
|
|
907
|
+
each value once, and it is the value the prelude rendered. A loader the route
|
|
908
|
+
also registers on the live lane is a hole: its live run replaces its restored
|
|
909
|
+
pushes, and those of the loaders it awaits, so they show its live value, even
|
|
910
|
+
when a bake-lane loader running on the HIT awaits it. A run that throws or
|
|
911
|
+
makes no push shows none of the capture's once its run ends; a hole slower
|
|
912
|
+
than the handler barrier shows the shell's copy in the first snapshot until
|
|
913
|
+
then. A hole with its own loader `cache()` that hits shows the pushes that
|
|
914
|
+
entry recorded, none if it recorded none, matching its data. A dependency the route does not
|
|
915
|
+
register, which an `ssr: false` loader awaited at capture outside any live
|
|
916
|
+
loader, is on neither lane: its settled pushes stay as the prelude rendered
|
|
917
|
+
them, even where a live loader also awaits it, while its data is fresh. To
|
|
918
|
+
keep its pushes live, declare it as its own `loader()` on the route. A push the capture cannot record (a
|
|
919
|
+
deferred push, or one holding a promise) marks every loader record of the
|
|
920
|
+
page to run: those bodies then still run on each HIT, in the background, and
|
|
921
|
+
that push reaches the page (from the loader's own `cache()` entry when it
|
|
922
|
+
hits) with the run's value; a push that lands after the document's handle snapshot
|
|
923
|
+
reaches the client after hydration.
|
|
880
924
|
|
|
881
925
|
Four hard edges (each e2e/unit-pinned):
|
|
882
926
|
|
|
@@ -893,7 +937,7 @@ Four hard edges (each e2e/unit-pinned):
|
|
|
893
937
|
response. The one exception: a handler that calls `ctx.dynamic()` FIRST
|
|
894
938
|
re-permits its own header/cookie write (#735) — a dynamic() render never
|
|
895
939
|
HITs, so the write is deterministic (see "Opting out per request").
|
|
896
|
-
- **Identity refuses.** `cookies()`/`headers()` inside a bake-lane loader
|
|
940
|
+
- **Identity refuses.** `cookies()`/`headers()`/`ctx.request.headers` inside a bake-lane loader
|
|
897
941
|
throws during capture and the capture REFUSES (deterministic, once-per-key
|
|
898
942
|
warned) — identity can never bake into the shared shell. Drop the loader's
|
|
899
943
|
`ssr: false` flag (the live lane is exempt; give it a boundary) or move the
|
|
@@ -921,7 +965,7 @@ Four hard edges (each e2e/unit-pinned):
|
|
|
921
965
|
- **Baked containers show CAPTURE-time data** for the shell's lifetime on
|
|
922
966
|
document GETs, and on soft navigations that replay a shell captured by a
|
|
923
967
|
document request (`x-rango-ppr-replay: HIT`). Live loaders and their
|
|
924
|
-
|
|
968
|
+
`"use cache"` reads remain fresh. That IS the bake lane's meaning; if a value
|
|
925
969
|
must be fresh on every serve, it belongs on the live lane (no `ssr: false`) or
|
|
926
970
|
in a nested promise.
|
|
927
971
|
|
|
@@ -939,7 +983,8 @@ their data should bake vs stay live. Your levers, in order of preference:
|
|
|
939
983
|
shared/config data returns as plain values (bakes, pinned per shell);
|
|
940
984
|
per-request data returns as NESTED promises consumed under the widget's own
|
|
941
985
|
`<Suspense>` (live holes). No `loading()`, no restructuring. One wall: a
|
|
942
|
-
bake-lane loader that reads `cookies()`/`headers()`
|
|
986
|
+
bake-lane loader that reads `cookies()`/`headers()`/`ctx.request.headers`
|
|
987
|
+
refuses the capture —
|
|
943
988
|
identity belongs on the live lane (a separate unflagged loader); a nested
|
|
944
989
|
promise inside the flagged loader still runs during capture and trips the
|
|
945
990
|
guard.
|
|
@@ -997,8 +1042,9 @@ The identity rule, stated once: per-user data on a PPR page lives in a
|
|
|
997
1042
|
live-lane loader (no `ssr: false`) consumed CLIENT-side with `useLoader` under
|
|
998
1043
|
`loading()` or an inline `<Suspense>`, or in a NESTED promise inside a
|
|
999
1044
|
bake-lane loader (a hole, fresh per request). A nested promise's body still
|
|
1000
|
-
runs at capture, so it must not call `cookies()`/`headers()`
|
|
1001
|
-
reads belong in the live-lane loader.
|
|
1045
|
+
runs at capture, so it must not call `cookies()`/`headers()` or read
|
|
1046
|
+
`ctx.request.headers` itself — identity reads belong in the live-lane loader.
|
|
1047
|
+
Reading `cookies()`, `headers()`, `ctx.request.headers`, or a
|
|
1002
1048
|
`{ cache: false }` variable anywhere the capture waits for refuses the capture
|
|
1003
1049
|
by construction: handler/render code, a promise the handler passes or pushes,
|
|
1004
1050
|
an async server component, a bake-lane loader, and a loader a handler awaits
|
|
@@ -1037,8 +1083,8 @@ Three levers, in preference order:
|
|
|
1037
1083
|
|
|
1038
1084
|
Head material (Meta) pushed by HANDLERS cannot be a hole — the head is shell —
|
|
1039
1085
|
so those promises bake by design; make them cheap with lever 2.
|
|
1040
|
-
`recordSettleMs`
|
|
1041
|
-
|
|
1086
|
+
`recordSettleMs` on the capture debug event tells you what each capture
|
|
1087
|
+
actually paid. A
|
|
1042
1088
|
Meta pushed by a LIVE-lane loader is different: those loaders are masked at
|
|
1043
1089
|
capture, so the push happens at request time and applies client-side
|
|
1044
1090
|
(`metadata.handlesLate`) — it is never in the cached shell's head, by
|
|
@@ -1078,11 +1124,14 @@ serve-time: the commit point runs the full chain on EVERY serve.
|
|
|
1078
1124
|
|
|
1079
1125
|
Because handlers on uncached segments EXECUTE during capture — along with
|
|
1080
1126
|
everything they produce and every BAKE-lane loader — the capture guard is
|
|
1081
|
-
load-bearing: `cookies()`, `headers()`,
|
|
1082
|
-
|
|
1127
|
+
load-bearing: `cookies()`, `headers()`, `ctx.request.headers`,
|
|
1128
|
+
`getRequestContext().cookie()` / `.cookies()`, a theme read, and a
|
|
1129
|
+
`{ cache: false }` variable read THROW during a capture render, and the capture
|
|
1083
1130
|
is refused even when the code catches that throw, so identity can never leak
|
|
1084
1131
|
into a shared shell through them. Live-lane loaders (every loader without
|
|
1085
|
-
`ssr: false`) are exempt
|
|
1132
|
+
`ssr: false`) are exempt when only a client component reads them: masked at
|
|
1133
|
+
capture, they never run there. One a handler or an `ssr: false` loader awaits
|
|
1134
|
+
runs at capture, and its identity reads refuse it.
|
|
1086
1135
|
|
|
1087
1136
|
## allReady: the SEO/bot story
|
|
1088
1137
|
|
|
@@ -1102,7 +1151,8 @@ middleware on every serve. A 401/redirect short-circuit returns before any
|
|
|
1102
1151
|
shell byte.
|
|
1103
1152
|
|
|
1104
1153
|
**(b) Request-scoped reads refuse the capture.** During the background capture
|
|
1105
|
-
render, `cookies()`, `headers()`, the
|
|
1154
|
+
render, `cookies()`, `headers()`, the raw reads `ctx.request.headers` and
|
|
1155
|
+
`getRequestContext().cookie()` / `.cookies()`, the visitor's theme (`ctx.theme`,
|
|
1106
1156
|
`getRequestContext().theme`), and `ctx.get()` of a `{ cache: false }` variable
|
|
1107
1157
|
(`createVar({ cache: false })`, or a value written with
|
|
1108
1158
|
`ctx.set(..., { cache: false })`) THROW, wherever the capture waits for them:
|
|
@@ -1110,14 +1160,15 @@ a handler, a promise it passes or pushes, an async server component, a
|
|
|
1110
1160
|
bake-lane loader, and a loader a handler awaits (`await ctx.use(Loader)`). The
|
|
1111
1161
|
capture is refused even if your code catches the throw, and the route keeps
|
|
1112
1162
|
serving MISSes with a once-per-key warning. `ctx.dynamic()` called there
|
|
1113
|
-
refuses it too. The live lane (no `ssr: false`) stays exempt
|
|
1114
|
-
runs
|
|
1163
|
+
refuses it too. The live lane (no `ssr: false`) stays exempt when only a client
|
|
1164
|
+
component reads it; one a handler or an `ssr: false` loader awaits runs at
|
|
1165
|
+
capture, and its identity reads refuse it.
|
|
1115
1166
|
|
|
1116
1167
|
**(c) Residual hazard — middleware-derived per-user state.** A NORMAL `ctx`
|
|
1117
1168
|
variable (no `cache: false`) set by an upstream auth middleware and rendered by
|
|
1118
1169
|
shell material is photographed into the SHARED shell (the capture inherits
|
|
1119
|
-
post-middleware state)
|
|
1120
|
-
|
|
1170
|
+
post-middleware state). The guard cannot see it. That is scope fidelity
|
|
1171
|
+
working as designed — for shared
|
|
1121
1172
|
values. It reaches handlers and BAKE-LANE LOADERS alike: a loader reading a
|
|
1122
1173
|
middleware-provided session object (`ctx.get("session")`) never calls
|
|
1123
1174
|
`cookies()` itself, so whatever it returns as settled container data is
|
|
@@ -1183,13 +1234,13 @@ path(
|
|
|
1183
1234
|
);
|
|
1184
1235
|
```
|
|
1185
1236
|
|
|
1186
|
-
| Field | Default | Notes
|
|
1187
|
-
| ------------------ | ------- |
|
|
1188
|
-
| `ttl` | `300` | shell freshness window in seconds (`ppr: true` uses the default)
|
|
1189
|
-
| `swr` | — | stale window: serve the stale shell + background recapture
|
|
1190
|
-
| `tags` | — | operational tags UNIONED with the tags the capture collects — see "Invalidation" below
|
|
1191
|
-
| `maxSnapshotBytes` | 8 MiB | cap on the loader pins in the entry's snapshot (bake-lane loader records
|
|
1192
|
-
| `captureTimeout` | 15000ms | ONE capture deadline: the handler layer settling (promises it passes or pushes, async server components, loaders it awaits), bake-lane loaders, and the prerender; a capture that misses it stores nothing rather than a partial shell
|
|
1237
|
+
| Field | Default | Notes |
|
|
1238
|
+
| ------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1239
|
+
| `ttl` | `300` | shell freshness window in seconds (`ppr: true` uses the default) |
|
|
1240
|
+
| `swr` | — | stale window: serve the stale shell + background recapture |
|
|
1241
|
+
| `tags` | — | operational tags UNIONED with the tags the capture collects — see "Invalidation" below |
|
|
1242
|
+
| `maxSnapshotBytes` | 8 MiB | cap on the loader pins in the entry's snapshot (the bake-lane loader records). The recorded handler layer is exempt. Over the cap the pins are dropped and the shell is stored with its handler layer (warned once per key); bake-lane loaders then read the live store, and drift is repaired client-side |
|
|
1243
|
+
| `captureTimeout` | 15000ms | ONE capture deadline: the handler layer settling (promises it passes or pushes, async server components, loaders it awaits), bake-lane loaders, and the prerender; a capture that misses it stores nothing rather than a partial shell |
|
|
1193
1244
|
|
|
1194
1245
|
A shell never outlives the route `cache()` entry it was captured from: under
|
|
1195
1246
|
a route `cache()`, the shell is fresh no longer than that entry is (at most
|
|
@@ -1238,8 +1289,8 @@ render-callable `cacheTag()` in a plain server component (no
|
|
|
1238
1289
|
`"use cache"`/`cache()` in its tree) — plus the tags of loaders on the BAKE
|
|
1239
1290
|
lane (they execute during capture and their data is in the shell). A tag
|
|
1240
1291
|
recorded outside what the shell renders is not on it. A live-lane loader's tags (every loader without `ssr: false`)
|
|
1241
|
-
attach only when a handler consumes its value with
|
|
1242
|
-
it; one read only by the client stays off, since it is masked at capture and
|
|
1292
|
+
attach only when a handler or an `ssr: false` loader consumes its value with
|
|
1293
|
+
`ctx.use()`, which bakes it; one read only by the client stays off, since it is masked at capture and
|
|
1243
1294
|
its hole is already live. `ppr.tags` adds
|
|
1244
1295
|
operational tags the render cannot know (a tenant id, a deploy marker).
|
|
1245
1296
|
|
|
@@ -1267,7 +1318,7 @@ evicted by tag at all — move always-fresh data into a live-lane loader (no
|
|
|
1267
1318
|
|
|
1268
1319
|
## Pitfalls
|
|
1269
1320
|
|
|
1270
|
-
- **A bake-lane loader that reads `cookies()`/`headers()`**: the capture is
|
|
1321
|
+
- **A bake-lane loader that reads `cookies()`/`headers()`/`ctx.request.headers`**: the capture is
|
|
1271
1322
|
REFUSED (deterministic, once-per-key warned) — the route keeps serving
|
|
1272
1323
|
MISSes. Move identity onto the live lane (drop `ssr: false`, or split the read into
|
|
1273
1324
|
a separate unflagged loader; give the reader a boundary). A nested promise
|
|
@@ -1299,8 +1350,9 @@ evicted by tag at all — move always-fresh data into a live-lane loader (no
|
|
|
1299
1350
|
bake-lane loader data — plain BAKE-lane loader data bakes just like handler
|
|
1300
1351
|
material.
|
|
1301
1352
|
- **The session-object bake trap (the guard cannot save you here)**: the
|
|
1302
|
-
capture guard sees `cookies()`, `headers()`,
|
|
1303
|
-
reads ONLY. A handler or bake-lane loader
|
|
1353
|
+
capture guard sees `cookies()`, `headers()`, `ctx.request.headers`, the theme
|
|
1354
|
+
and `{ cache: false }` variable reads ONLY. A handler or bake-lane loader
|
|
1355
|
+
reading a NORMAL middleware-provided
|
|
1304
1356
|
session object (`ctx.get("session")`) refuses nothing. Inside a bake-lane
|
|
1305
1357
|
loader, per-user data survives ONLY behind a nested promise — the shape is
|
|
1306
1358
|
the declaration, and it holds for BOTH branches regardless of settle timing
|
|
@@ -1352,10 +1404,10 @@ evicted by tag at all — move always-fresh data into a live-lane loader (no
|
|
|
1352
1404
|
same `"use cache"` entry reads it from the store on every HIT. It is still
|
|
1353
1405
|
cached under its own profile; once the entry expires or is invalidated and
|
|
1354
1406
|
refreshes, the hole shows the refreshed value next to the shell's old one. The
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1407
|
+
shell stores no `"use cache"` value, only its recorded handler output and the
|
|
1408
|
+
`ssr: false` loaders' baked containers. A hole reads the store even when a
|
|
1409
|
+
bake-lane loader read the same entry at capture: the baked container keeps
|
|
1410
|
+
the capture's value, and the hole shows the current entry.
|
|
1359
1411
|
- **Uncached nondeterminism in server output is frozen, not drifting**: a raw
|
|
1360
1412
|
`Date.now()` / `Math.random()` / uncached `fetch` in a handler or server
|
|
1361
1413
|
component renders once per capture, and the prelude and every HIT show that
|
|
@@ -795,8 +795,8 @@ At runtime, the cache-lookup middleware checks `ctx.isIntercept`:
|
|
|
795
795
|
intercept-resolution middleware runs live. This handles `when` config conditions
|
|
796
796
|
that prevented pre-rendering.
|
|
797
797
|
|
|
798
|
-
The `when` config selector receives an `InterceptSelectorContext`
|
|
799
|
-
|
|
798
|
+
The `when` config selector receives an `InterceptSelectorContext` whose `from`
|
|
799
|
+
location (the page being left) is unknown at build time. All intercepts are pre-rendered unconditionally;
|
|
800
800
|
`when` is evaluated at runtime by the intercept-resolution middleware.
|
|
801
801
|
|
|
802
802
|
### Example: Pre-rendered route with intercept
|
|
@@ -820,7 +820,7 @@ layout(ShopLayout, () => [
|
|
|
820
820
|
"@modal",
|
|
821
821
|
".detail",
|
|
822
822
|
<ProductModal />,
|
|
823
|
-
{ when: ({ from }) => from.pathname === "/shop" },
|
|
823
|
+
{ when: ({ from }) => from.url.pathname === "/shop" },
|
|
824
824
|
() => [loader(ProductLoader)],
|
|
825
825
|
),
|
|
826
826
|
])
|
package/skills/rango/SKILL.md
CHANGED
|
@@ -131,11 +131,13 @@ stated, greppable contract.
|
|
|
131
131
|
- **The consumption-lane rule.** For every shared artifact (`cache()`,
|
|
132
132
|
`"use cache"`, the PPR shell): server-side handler consumption
|
|
133
133
|
(`await ctx.use(loader)`) yields a BAKED copy — the capture-time value
|
|
134
|
-
freezes into the shared artifact. Identity reads (`cookies()`/`headers()
|
|
135
|
-
a `{ cache: false }` variable) inside that loader
|
|
136
|
-
`cache()
|
|
137
|
-
purity & tainted objects")
|
|
138
|
-
|
|
134
|
+
freezes into the shared artifact. Identity reads (`cookies()`/`headers()`/
|
|
135
|
+
`ctx.request.headers`, a `{ cache: false }` variable) inside that loader
|
|
136
|
+
are permitted under a route `cache()` (a documented footgun; see
|
|
137
|
+
`/caching` → "Cache purity & tainted objects"). They throw inside
|
|
138
|
+
`"use cache"` (when the cached function is the first to read the loader in
|
|
139
|
+
the request; an already-started loader's value is stored unchecked) and REFUSE a PPR capture: the route serves uncached until
|
|
140
|
+
the read moves to a live loader. Client-side consumption
|
|
139
141
|
(`useLoader` in a `"use client"` component) is the LIVE lane. DSL
|
|
140
142
|
`loader()` segments follow their PPR lane (only `ssr: false` bakes; see
|
|
141
143
|
`/ppr` → The loader lane rule).
|
|
@@ -146,7 +148,7 @@ stated, greppable contract.
|
|
|
146
148
|
only when its return carries promises). Live data belongs in a loader without
|
|
147
149
|
`ssr: false`, read with `useLoader` under `loading()` or an inline
|
|
148
150
|
`<Suspense>`.
|
|
149
|
-
- Inside `"use cache"`: `cookies()`/`headers()` and `ctx` side-effects
|
|
151
|
+
- Inside `"use cache"`: `cookies()`/`headers()`/`ctx.request.headers` and `ctx` side-effects
|
|
150
152
|
(`set`/`header`/`setTheme`/`onResponse`/`setLocationState`) throw; `ctx.use(Handle)`
|
|
151
153
|
is captured on miss and replayed on hit. A non-cacheable variable read
|
|
152
154
|
(`createVar({ cache: false })`) throws too — see the correctness bullet below.
|
|
@@ -163,6 +165,23 @@ stated, greppable contract.
|
|
|
163
165
|
- Nested caches: the outer cache window bounds the inner — an inner shorter TTL
|
|
164
166
|
only applies when the enclosing cache recomputes; put a value in a loader if it
|
|
165
167
|
must be fresher. See `/cache-guide` → "Combining Both".
|
|
168
|
+
- **A predicate runs where its effect lands.** A server-tree `revalidate()`
|
|
169
|
+
decides what the server re-sends, so it runs on the server; an `intercept({
|
|
170
|
+
when })` selector decides what the server renders, so it runs on the server;
|
|
171
|
+
a `clientUrls()` `revalidate()` and `transition({ when })` decide a browser
|
|
172
|
+
commit, so they run in the browser. A browser predicate sees URLs, params,
|
|
173
|
+
route names, history state and action data, never `get()`, `env` or handler
|
|
174
|
+
state; in a server `urls()` it is written inline (the build hoists it into a
|
|
175
|
+
client module) or imported from a `"use client"` module (`/view-transitions`).
|
|
176
|
+
- **Vocabulary.** `revalidate()` keeps React Router's `shouldRevalidate`
|
|
177
|
+
dialect (`currentUrl`/`nextUrl`, `currentParams`/`nextParams`). Navigation
|
|
178
|
+
selectors (`intercept({ when })`, `transition({ when })`) use `from`/`to`
|
|
179
|
+
locations (`{ url, params, routeName }`, plus `state` in the browser).
|
|
180
|
+
- **A throwing predicate yields the conservative default.** For
|
|
181
|
+
`transition({ when })` that is no hold (an urgent commit) plus
|
|
182
|
+
`console.error`; for a `revalidate()` it is the default decision it would
|
|
183
|
+
have deferred to, logged; for an `intercept({ when })` selector it is no
|
|
184
|
+
intercept (the full page renders), logged with the route name.
|
|
166
185
|
|
|
167
186
|
## Don't confuse
|
|
168
187
|
|
package/skills/route/SKILL.md
CHANGED
|
@@ -608,6 +608,8 @@ whether the routes mount.
|
|
|
608
608
|
|
|
609
609
|
A route can configure its own `transition()` — the wrap goes around the route's component itself (routes are leaves; they have no separate default outlet channel). If the route component renders a `<ParallelOutlet />` directly, that slot remains inside the route's VT subtree, so prefer mounting parallel slots in a layout when combining intercept modals with route-level transitions. See [skills/view-transitions](../view-transitions/SKILL.md) for examples and the wrap-location rules across layouts, routes, and slots.
|
|
610
610
|
|
|
611
|
+
`transition({ when })` and `revalidate()` decide different things: `when` (a browser predicate) gates the hold, `revalidate()` decides whether the route segment is re-sent. A `revalidate()` returning false keeps the client's segment on a navigation whatever `when` returns, including a `ppr` navigation replayed from the shell.
|
|
612
|
+
|
|
611
613
|
## Handler-attached `.use`
|
|
612
614
|
|
|
613
615
|
Page handlers can carry their own loader, middleware, error boundaries, parallels, and other defaults via a `.use` callback — so the page is self-contained and reusable across mount sites without re-wiring the same items.
|
package/skills/testing/SKILL.md
CHANGED
|
@@ -89,31 +89,32 @@ The single rule that drives everything:
|
|
|
89
89
|
|
|
90
90
|
Each primitive links to its sub-file (API + recipe + caveats).
|
|
91
91
|
|
|
92
|
-
| The behavior is… | Layer | Primitive
|
|
93
|
-
| ---------------------------------------------------------------------------------------------------------------------------------- | ------------ |
|
|
94
|
-
| a `reverse` / `href` / params / env TYPE contract (a wrong name or missing param must not compile) | types (tsc) | [`@ts-expect-error` / `expectTypeOf`](./reverse-and-types.md)
|
|
95
|
-
| one loader's data logic | unit (node) | [`runLoader`](./loader.md)
|
|
96
|
-
| a loader's cookie / header / thrown `redirect()`/`notFound()` / handle-push output | unit (node) | [`runLoaderResult`](./loader.md)
|
|
97
|
-
| one middleware's ordering / short-circuit / cookie+header merge | unit (node) | [`runMiddleware`](./middleware.md)
|
|
98
|
-
| a `"use server"` action's cookie / header / flash output (even on `throw redirect()`) | unit (node) | [`runInRequestContext`](./server-actions.md)
|
|
99
|
-
| a `transition({ when })`
|
|
100
|
-
| a `clientUrls()` `revalidate()` / `isAction(ref)` predicate | unit (node) | [`runClientRevalidate`](./predicates.md) (production chain evaluator + locked defaults)
|
|
101
|
-
| a handle's `collect`/accumulator, a seeded handle read, or a loader handle write | unit | [`collectHandle` / seeded `handles` / `handlePushes`](./handles.md)
|
|
102
|
-
| a CLIENT component reading router context (`useParams`/`useReverse`/`Outlet`/`useNavigation`/`useLoader`) | unit (DOM) | [`renderRoute`](./client-components.md)
|
|
103
|
-
| a stale indicator from `useLoader().isLoading` while a `transition()` navigation re-runs the loader | unit (DOM) | [`renderRoute` + `navigate(url, { loaders })`](./client-components.md#held-navigation)
|
|
104
|
-
| a
|
|
105
|
-
| a
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
| a `"use cache"` hit in a
|
|
111
|
-
| a
|
|
112
|
-
| cache
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
92
|
+
| The behavior is… | Layer | Primitive | Import root |
|
|
93
|
+
| ---------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
94
|
+
| a `reverse` / `href` / params / env TYPE contract (a wrong name or missing param must not compile) | types (tsc) | [`@ts-expect-error` / `expectTypeOf`](./reverse-and-types.md) | `@rangojs/router/client` + `tsc` |
|
|
95
|
+
| one loader's data logic | unit (node) | [`runLoader`](./loader.md) | `@rangojs/router/testing` |
|
|
96
|
+
| a loader's cookie / header / thrown `redirect()`/`notFound()` / handle-push output | unit (node) | [`runLoaderResult`](./loader.md) | `@rangojs/router/testing` |
|
|
97
|
+
| one middleware's ordering / short-circuit / cookie+header merge | unit (node) | [`runMiddleware`](./middleware.md) | `@rangojs/router/testing` |
|
|
98
|
+
| a `"use server"` action's cookie / header / flash output (even on `throw redirect()`) | unit (node) | [`runInRequestContext`](./server-actions.md) | `@rangojs/router/testing` |
|
|
99
|
+
| a `transition({ when })` browser predicate against `{ kind, from, to, action }` | unit (node) | [`runTransitionWhen`](./predicates.md) (`{ applied, gatedOff, context }`) | `@rangojs/router/testing` |
|
|
100
|
+
| a `clientUrls()` `revalidate()` / `isAction(ref)` predicate | unit (node) | [`runClientRevalidate`](./predicates.md) (production chain evaluator + locked defaults) | `@rangojs/router/testing` |
|
|
101
|
+
| a handle's `collect`/accumulator, a seeded handle read, or a loader handle write | unit | [`collectHandle` / seeded `handles` / `handlePushes`](./handles.md) | `@rangojs/router/testing` |
|
|
102
|
+
| a CLIENT component reading router context (`useParams`/`useReverse`/`Outlet`/`useNavigation`/`useLoader`) | unit (DOM) | [`renderRoute`](./client-components.md) | `@rangojs/router/testing/dom` |
|
|
103
|
+
| a stale indicator from `useLoader().isLoading` while a `transition()` navigation re-runs the loader | unit (DOM) | [`renderRoute` + `navigate(url, { loaders })`](./client-components.md#held-navigation) | `@rangojs/router/testing/dom` |
|
|
104
|
+
| a component reading `useLocationState(Def)` after `router.push(url, { state: [Def(value)] })` | unit (DOM) | [`renderRoute` + `withLocationStateKey`](./client-components.md#location-state) | `@rangojs/router/testing[/dom]` |
|
|
105
|
+
| a redirect / status / headers / cookies / **response route** (json/text/html/xml/md), no Flight | integration | [`dispatch`](./response-routes.md) | `@rangojs/router/testing` |
|
|
106
|
+
| a real async **Server Component** (assert what it rendered: typed boundary props, server-rendered host content, inlined-vs-island) | RSC unit | [`renderServerTree` + `findClientBoundaries`/`findElements`](./server-tree.md) | `@rangojs/router/testing/flight` |
|
|
107
|
+
| the exact Flight **wire payload** shape (a drift snapshot) | RSC unit | [`renderToFlightString` + `toMatchFlightSnapshot`](./flight.md) | `@rangojs/router/testing/flight` |
|
|
108
|
+
| a real route **handler** `(ctx) => rsc` (params/loaders/vars -> rendered RSC + effects) | RSC unit | [`renderHandler`](./render-handler.md) | `@rangojs/router/testing/flight` |
|
|
109
|
+
| navigation, hydration, PE parity, view transitions, real SSR | e2e | [`createRangoE2E` -> `parityDescribe`/`expectParity`](./e2e-parity.md) | `@rangojs/router/testing/e2e` |
|
|
110
|
+
| a `"use cache"` hit in a handler (the body runs once across renders; the value round-trips) | RSC unit | [`renderHandler` + seeded `cacheStore`](./render-handler.md#a-use-cache-hit) | `@rangojs/router/testing/flight` |
|
|
111
|
+
| a `"use cache"` hit in a loader (the same, in the react-server project) | RSC unit | [`runLoader` + seeded `cacheStore`](./loader.md) | `@rangojs/router/testing` |
|
|
112
|
+
| a loader's own `cache()` (HIT, `updateTag()` of a tag its body recorded) | RSC unit | [`runLoader` + `cache` + seeded `cacheStore`](./loader.md) | `@rangojs/router/testing` |
|
|
113
|
+
| cache hit/miss/stale, prerender (= a cache hit by design) | e2e + signal | [`assertCacheStatus` (header) / `assertCacheDecision` (telemetry sink)](./cache-prerender.md) | `@rangojs/router/testing[/e2e]` |
|
|
114
|
+
| a `ppr` route's shell: MISS -> capture -> HIT, what the shell froze vs what a HIT renders live, tag eviction, navigation replay | RSC unit | [`serveShellRequest`](./serve-shell-request.md) | `@rangojs/router/testing/flight` |
|
|
115
|
+
| PPR shell HIT/MISS in real HTML (`ppr` path option), shell store key / getShell after flush | e2e + signal | [`assertShellStatus` / `shellCacheKey` + store](./cache-prerender.md#ppr-shell-and-navigation-replay) | `@rangojs/router/testing[/e2e]` |
|
|
116
|
+
| generated route map drift vs runtime | unit (node) | [`assertGeneratedRoutesMatch`](./reverse-and-types.md) | `@rangojs/router/testing` |
|
|
117
|
+
| a platform binding (`env.DB` / Durable Object / `env.R2`) | unit/integr. | [your own double via `env`](./bindings.md) | (any primitive's `env` option) |
|
|
117
118
|
|
|
118
119
|
Cross-references to the DSL skills: `/loader`, `/middleware`, `/server-actions`,
|
|
119
120
|
`/handler-use`, `/hooks`, `/response-routes`, `/route`, `/caching`, `/prerender`,
|
|
@@ -144,6 +145,7 @@ beside their function):
|
|
|
144
145
|
| | `runInRequestContext`, `createTestRequestContext`, `runWithRequestContext`, `toRequest`, `seedVariables` | [`server-actions.md`](./server-actions.md) |
|
|
145
146
|
| | `runTransitionWhen`, `runClientRevalidate` | [`predicates.md`](./predicates.md) |
|
|
146
147
|
| | `collectHandle` | [`handles.md`](./handles.md) |
|
|
148
|
+
| | `withLocationStateKey` | [`client-components.md`](./client-components.md) |
|
|
147
149
|
| | `dispatch` | [`response-routes.md`](./response-routes.md) |
|
|
148
150
|
| | `assertCacheStatus`, `assertCacheDecision`, `parseCacheHeader`, `createCacheSink`, `filterCacheDecisions` | [`cache-prerender.md`](./cache-prerender.md) |
|
|
149
151
|
| | `assertShellStatus`, `parseShellStatus`, `shellCacheKey`, `SHELL_STATUS_HEADER`, `assertPprReplayStatus`, `parsePprReplayStatus`, `PPR_REPLAY_STATUS_HEADER` | [`cache-prerender.md`](./cache-prerender.md) |
|