@rangojs/router 0.25.0 → 0.26.1
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/bin/rango.js +1 -1
- package/dist/types/cache/cf/cf-cache-store.d.ts +0 -2
- package/dist/types/cache/cf/cf-cache-types.d.ts +0 -16
- package/dist/types/cache/handle-snapshot.d.ts +2 -2
- package/dist/types/cache/shell-snapshot.d.ts +6 -4
- package/dist/types/cache/types.d.ts +8 -6
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +1 -15
- package/dist/types/handles/deferred-resolution.d.ts +3 -2
- package/dist/types/handles/meta.d.ts +5 -0
- package/dist/types/prerender/build-shell-capture.d.ts +1 -2
- package/dist/types/prerender/writable-store.d.ts +2 -3
- package/dist/types/router/dev-reload-handoff.d.ts +26 -0
- package/dist/types/router/router-interfaces.d.ts +0 -5
- package/dist/types/router/router-options.d.ts +0 -20
- package/dist/types/router/segment-resolution/mask-nested.d.ts +5 -3
- package/dist/types/router-versions.d.ts +8 -6
- package/dist/types/rsc/shell-capture-constants.d.ts +10 -6
- package/dist/types/rsc/shell-capture.d.ts +25 -8
- package/dist/types/rsc/types.d.ts +0 -8
- package/dist/types/server/build-version-table.d.ts +10 -13
- package/dist/types/server/context.d.ts +26 -2
- package/dist/types/server/handle-store.d.ts +17 -6
- package/dist/types/server/request-context.d.ts +4 -3
- package/dist/types/testing/build-versions.d.ts +0 -3
- package/dist/types/testing/internal/seed-vars.d.ts +4 -4
- package/dist/types/testing/serve-shell-request.d.ts +1 -1
- package/dist/types/types/loader-types.d.ts +6 -1
- package/dist/types/urls/pattern-types.d.ts +7 -6
- package/dist/vite/index.js +3 -8
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +4 -8
- package/skills/caching/SKILL.md +3 -5
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/hooks/handle-and-actions.md +6 -6
- package/skills/host-router/SKILL.md +1 -1
- package/skills/loader/SKILL.md +10 -9
- package/skills/ppr/SKILL.md +83 -60
- package/skills/prerender/SKILL.md +3 -3
- package/skills/router-setup/SKILL.md +0 -2
- package/skills/testing/render-handler.md +17 -17
- package/skills/testing/serve-shell-request.md +1 -1
- package/skills/testing/server-actions.md +16 -16
- package/skills/vercel/SKILL.md +1 -1
- package/src/cache/cf/cf-cache-store.ts +4 -7
- package/src/cache/cf/cf-cache-types.ts +0 -17
- package/src/cache/handle-snapshot.ts +2 -2
- package/src/cache/shell-snapshot.ts +7 -3
- package/src/cache/types.ts +8 -6
- package/src/cache/vercel/vercel-cache-store.ts +2 -18
- package/src/handles/deferred-resolution.ts +3 -2
- package/src/handles/meta.ts +5 -0
- package/src/prerender/build-shell-capture.ts +1 -2
- package/src/prerender/writable-store.ts +2 -3
- package/src/route-definition/dsl-helpers.ts +14 -14
- package/src/router/dev-reload-handoff.ts +73 -0
- package/src/router/manifest.ts +9 -2
- package/src/router/match-middleware/intercept-resolution.ts +2 -2
- package/src/router/router-interfaces.ts +0 -6
- package/src/router/router-options.ts +0 -21
- package/src/router/segment-resolution/loader-cache.ts +4 -3
- package/src/router/segment-resolution/mask-nested.ts +5 -3
- package/src/router-versions.ts +7 -7
- package/src/router.ts +19 -8
- package/src/rsc/handler.ts +3 -7
- package/src/rsc/rsc-rendering.ts +44 -12
- package/src/rsc/shell-capture-constants.ts +10 -6
- package/src/rsc/shell-capture.ts +163 -88
- package/src/rsc/types.ts +0 -9
- package/src/server/build-version-table.ts +12 -24
- package/src/server/context.ts +64 -2
- package/src/server/handle-store.ts +77 -10
- package/src/server/request-context.ts +4 -3
- package/src/testing/build-versions.ts +0 -3
- package/src/testing/dispatch.ts +1 -3
- package/src/testing/internal/seed-vars.ts +4 -4
- package/src/testing/serve-shell-request.ts +2 -5
- package/src/types/loader-types.ts +6 -1
- package/src/urls/pattern-types.ts +7 -6
- package/src/vite/discovery/shell-prerender-phase.ts +1 -5
|
@@ -60,12 +60,13 @@ export interface PartialPrerenderProps {
|
|
|
60
60
|
/**
|
|
61
61
|
* Upper bound (serialized UTF-8 bytes) on the loader pins in the capture
|
|
62
62
|
* data snapshot: the bake-lane loader records. The doc record (the handler
|
|
63
|
-
* layer every HIT replays) is exempt.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* Defaults to 8 MiB. The whole
|
|
68
|
-
*
|
|
63
|
+
* layer every HIT replays) is exempt. A document HIT is served only from an
|
|
64
|
+
* entry that holds a pin for every `ssr: false` loader its HTML baked, so
|
|
65
|
+
* over the cap no document shell is stored: documents render in full
|
|
66
|
+
* (MISS), and client navigations still replay the handler layer and run
|
|
67
|
+
* those loaders fresh. Reported once per key. Defaults to 8 MiB. The whole
|
|
68
|
+
* entry is bounded separately by the store's value limit (Cloudflare KV:
|
|
69
|
+
* 25 MiB); an entry over it is refused.
|
|
69
70
|
*/
|
|
70
71
|
maxSnapshotBytes?: number;
|
|
71
72
|
/**
|
package/dist/vite/index.js
CHANGED
|
@@ -7068,8 +7068,7 @@ var DEFAULT_ROUTER_VERSIONS_KEY = "*";
|
|
|
7068
7068
|
function uniformVersions(version) {
|
|
7069
7069
|
return { data: version, document: version };
|
|
7070
7070
|
}
|
|
7071
|
-
function resolveVersionsFrom(table, routerId2
|
|
7072
|
-
if (override !== void 0) return uniformVersions(override);
|
|
7071
|
+
function resolveVersionsFrom(table, routerId2) {
|
|
7073
7072
|
if (!table) return void 0;
|
|
7074
7073
|
const pair = (routerId2 !== void 0 && Object.hasOwn(table, routerId2) ? table[routerId2] : void 0) ?? table[DEFAULT_ROUTER_VERSIONS_KEY];
|
|
7075
7074
|
return pair ? { data: pair[0], document: pair[1] } : void 0;
|
|
@@ -7078,7 +7077,7 @@ function resolveVersionsFrom(table, routerId2, override) {
|
|
|
7078
7077
|
// package.json
|
|
7079
7078
|
var package_default = {
|
|
7080
7079
|
name: "@rangojs/router",
|
|
7081
|
-
version: "0.
|
|
7080
|
+
version: "0.26.1",
|
|
7082
7081
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
7083
7082
|
keywords: [
|
|
7084
7083
|
"react",
|
|
@@ -12257,11 +12256,7 @@ async function runShellPrerenderPhase(s, builder, versionsTable) {
|
|
|
12257
12256
|
const mismatches = [];
|
|
12258
12257
|
for (const [routerId2, routerInstance] of registry) {
|
|
12259
12258
|
if (typeof routerInstance.match !== "function") continue;
|
|
12260
|
-
const versions = resolveVersionsFrom(
|
|
12261
|
-
versionsTable,
|
|
12262
|
-
routerId2,
|
|
12263
|
-
routerInstance.version
|
|
12264
|
-
);
|
|
12259
|
+
const versions = resolveVersionsFrom(versionsTable, routerId2);
|
|
12265
12260
|
try {
|
|
12266
12261
|
const res = await captureMod.captureShellForBuild({
|
|
12267
12262
|
router: routerInstance,
|
package/package.json
CHANGED
|
@@ -211,17 +211,13 @@ Tag invalidation markers are stored without a version, so `updateTag()` and
|
|
|
211
211
|
`revalidateTag()` apply to entries of every version, including a version that
|
|
212
212
|
comes back in a rollback.
|
|
213
213
|
|
|
214
|
-
- **Force a clear:** change
|
|
215
|
-
`
|
|
216
|
-
|
|
217
|
-
versions), or invalidate tags.
|
|
218
|
-
- **Keep the old "every deploy clears the cache" behavior:** set `version` to a
|
|
219
|
-
per-deploy value (a build id), or on Vercel keep a deployment-scoped
|
|
214
|
+
- **Force a clear:** invalidate tags, or change the router's server code. There
|
|
215
|
+
is no `version` setting; the build versions are the only ones.
|
|
216
|
+
- **Clear on every deploy:** on Vercel, keep a deployment-scoped
|
|
220
217
|
`getCache({ namespace: process.env.VERCEL_DEPLOYMENT_ID })`.
|
|
221
218
|
- **Separate environments:** two deployments built from the same code have the
|
|
222
219
|
same versions. If they share a store (the same KV namespace), they share
|
|
223
|
-
entries. Give each environment its own KV namespace or cache namespace
|
|
224
|
-
own `version`.
|
|
220
|
+
entries. Give each environment its own KV namespace or cache namespace.
|
|
225
221
|
|
|
226
222
|
Two things give a router a new version on every build, and with it a cold
|
|
227
223
|
cache on every deploy:
|
package/skills/caching/SKILL.md
CHANGED
|
@@ -616,8 +616,7 @@ from that router's built code: segment entries and `fn:` items with the data
|
|
|
616
616
|
version, `doc:` responses and `shell2:` shells with the document version. A
|
|
617
617
|
deploy that changes a router's code reads under a new version; a deploy that does
|
|
618
618
|
not keeps the entries. Tag invalidation markers carry no version, so `updateTag()`
|
|
619
|
-
reaches entries of every version. `
|
|
620
|
-
value. See `/cache-guide` → "Cross-deploy safety" and `/cloudflare` for bindings.
|
|
619
|
+
reaches entries of every version. See `/cache-guide` → "Cross-deploy safety" and `/cloudflare` for bindings.
|
|
621
620
|
|
|
622
621
|
### With KV L2 Persistence
|
|
623
622
|
|
|
@@ -859,12 +858,11 @@ global, so they read fresh in any region.
|
|
|
859
858
|
Writes over 2 MB (the platform limit, `maxItemBytes`) are skipped, and tags
|
|
860
859
|
beyond the per-item cap are dropped with a warning. The official client swallows
|
|
861
860
|
`expireTag` failures, so treat `updateTag()` on Vercel as best-effort. See
|
|
862
|
-
`/vercel` for the preset and the remaining options (`
|
|
861
|
+
`/vercel` for the preset and the remaining options (`name`, `debug`).
|
|
863
862
|
|
|
864
863
|
`VercelCacheStore` versions its keys by default: families `s` and `i` take the
|
|
865
864
|
router's data version, `r` and `h` its document version, and tag markers
|
|
866
|
-
(`rg:tm:{tag}`) take none.
|
|
867
|
-
deploy, set `version` to a per-deploy value or keep a deployment-scoped
|
|
865
|
+
(`rg:tm:{tag}`) take none. To clear the cache on every deploy, keep a deployment-scoped
|
|
868
866
|
`getCache({ namespace: process.env.VERCEL_DEPLOYMENT_ID })`.
|
|
869
867
|
|
|
870
868
|
## Cache purity & tainted objects
|
|
@@ -356,7 +356,7 @@ code, and stored HTML (PPR shells, document-cache responses) by the document
|
|
|
356
356
|
version. A build whose router code changed cannot replay Flight containing an old
|
|
357
357
|
component shape or dead client-chunk reference, and a rebuild of unchanged code
|
|
358
358
|
keeps its cache. Tag invalidation markers carry no version, so `updateTag()`
|
|
359
|
-
reaches entries of every version.
|
|
359
|
+
reaches entries of every version.
|
|
360
360
|
The browser's Rango state also includes the document version, rotating HTTP and
|
|
361
361
|
in-memory prefetch cache identity on boot.
|
|
362
362
|
|
|
@@ -46,12 +46,12 @@ belongs to the segment that pushed it and reaches `useHandle` when that push
|
|
|
46
46
|
settles. The page never waits for a handle, and a handle never shows the next
|
|
47
47
|
page's values over the page being left:
|
|
48
48
|
|
|
49
|
-
| Case | What `useHandle` returns
|
|
50
|
-
| ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
51
|
-
| Document load | The values the HTML was rendered from. `push(promise)` and `.defer()` are awaited during SSR, so the document ships the resolved value. |
|
|
52
|
-
| Navigation to a page that commits at once (a cold click that mounts new segments) | The destination's values, in the page's commit; the page's effects read them
|
|
53
|
-
| Navigation to a page React holds in a transition (`transition()`, same-route search change, fully prefetched click, a handler that awaits) | The page being left keeps its values; the destination's land in the commit right after the page, in the same task, before paint
|
|
54
|
-
| `push(promise)` or `.defer()` on a client navigation | The previous value, then the new one when the promise resolves, possibly well after the page
|
|
49
|
+
| Case | What `useHandle` returns |
|
|
50
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| Document load | The values the HTML was rendered from. `push(promise)` and `.defer()` are awaited during SSR, so the document ships the resolved value. On a `ppr` shell HIT that holds for the handlers' and the `ssr: false` loaders' pushes, which the shell baked; a live-lane loader's push arrives right after hydration (see `/ppr`). |
|
|
52
|
+
| Navigation to a page that commits at once (a cold click that mounts new segments) | The destination's values, in the page's commit; the page's effects read them |
|
|
53
|
+
| Navigation to a page React holds in a transition (`transition()`, same-route search change, fully prefetched click, a handler that awaits) | The page being left keeps its values; the destination's land in the commit right after the page, in the same task, before paint |
|
|
54
|
+
| `push(promise)` or `.defer()` on a client navigation | The previous value, then the new one when the promise resolves, possibly well after the page |
|
|
55
55
|
|
|
56
56
|
You never see a pending value, a blank, or the destination's title over the old
|
|
57
57
|
page. A crumb whose `content` is a promise read with `use()` in `<Suspense>`
|
|
@@ -382,7 +382,7 @@ Each `createRouter()` app has its own cache version, computed by `vite build` fr
|
|
|
382
382
|
| Client code of any app changes | kept for every app | cleared for every app | every app's tabs reload |
|
|
383
383
|
| Server code shared by A and B changes | cleared for A and B | cleared for A and B | A's and B's tabs reload |
|
|
384
384
|
|
|
385
|
-
The host entry (the file that creates the host router and its middleware) is not part of a lazily mounted sub-app's version. If host middleware changes what an app renders from inside a cache boundary,
|
|
385
|
+
The host entry (the file that creates the host router and its middleware) is not part of a lazily mounted sub-app's version. If host middleware changes what an app renders from inside a cache boundary, invalidate its tags. See `/cache-guide`.
|
|
386
386
|
|
|
387
387
|
## Shared cache store
|
|
388
388
|
|
package/skills/loader/SKILL.md
CHANGED
|
@@ -668,12 +668,13 @@ rejects) skips the whole entry, as above. Entries written
|
|
|
668
668
|
before handles were recorded replay none until they are rewritten (expiry or
|
|
669
669
|
SWR revalidation).
|
|
670
670
|
|
|
671
|
-
On a `ppr` route the shell keeps a copy of an `ssr: false` loader's
|
|
672
|
-
|
|
673
|
-
serves the loader's data from the shell, the
|
|
674
|
-
and the entry only adds
|
|
675
|
-
|
|
676
|
-
|
|
671
|
+
On a `ppr` route the shell keeps a copy of an `ssr: false` loader's pushes
|
|
672
|
+
too, a pushed promise resolved (#1182). The two never disagree on the page:
|
|
673
|
+
where a replay of the shell serves the loader's data from the shell, the
|
|
674
|
+
shell's copy is the one shown and the entry only adds a push whose value
|
|
675
|
+
holds a promise, which the shell could not keep; where it does not (the
|
|
676
|
+
loader runs or reads its entry), the entry's pushes take the shell copy's
|
|
677
|
+
place, none if the entry recorded none. See `/ppr` → "Handles on a shell
|
|
677
678
|
HIT".
|
|
678
679
|
|
|
679
680
|
### Cache Key
|
|
@@ -1040,9 +1041,9 @@ handler reserved with `.defer()` instead (`/breadcrumbs`, "Deferred content").
|
|
|
1040
1041
|
|
|
1041
1042
|
Under `cache()`, loader pushes are not stored with the cached segments: the
|
|
1042
1043
|
loader re-runs on every hit and its live push is the only copy. (`ppr` keeps
|
|
1043
|
-
the
|
|
1044
|
-
|
|
1045
|
-
see `/ppr`.)
|
|
1044
|
+
the pushes of an `ssr: false` loader, and of the loaders it awaits, in the
|
|
1045
|
+
shell, a pushed promise resolved, and a promise-free `ssr: false` loader does
|
|
1046
|
+
not run on a shell HIT — see `/ppr`.)
|
|
1046
1047
|
|
|
1047
1048
|
Reads are the other direction and gated: `ctx.get(handle)` throws unless the
|
|
1048
1049
|
loader first does `await ctx.rendered()` (DSL-registered loaders only —
|
package/skills/ppr/SKILL.md
CHANGED
|
@@ -372,7 +372,8 @@ replay its eligible snapshot. In both cases `matchPartial()`:
|
|
|
372
372
|
- runs live DSL loaders fresh with their normal `loading()` streaming
|
|
373
373
|
behavior, and serves bake-lane loaders from the pins of a shell captured by a
|
|
374
374
|
document request; a snapshot captured by a navigation alone carries no pins,
|
|
375
|
-
|
|
375
|
+
nor does the navigation-only entry a capture over `ppr.maxSnapshotBytes`
|
|
376
|
+
stores, and their replay runs them fresh (see below);
|
|
376
377
|
- keeps the existing prefetch key, source scope, and in-flight lock unchanged.
|
|
377
378
|
|
|
378
379
|
This is deliberately invisible to the browser: the response is the same
|
|
@@ -382,8 +383,9 @@ snapshot is a shell a document request captured, as on a document HIT: a
|
|
|
382
383
|
promise-free `ssr: false` loader is served from its pin without running
|
|
383
384
|
(unless the capture marked it to run: see "On a shell HIT"), and one whose
|
|
384
385
|
return holds promises runs with its baked parts overlaid. A snapshot captured
|
|
385
|
-
by a navigation alone carries no pins,
|
|
386
|
-
|
|
386
|
+
by a navigation alone carries no pins, nor does an entry over
|
|
387
|
+
`ppr.maxSnapshotBytes`, and its replay runs them fresh. Loaders without
|
|
388
|
+
`ssr: false` stay live.
|
|
387
389
|
|
|
388
390
|
A route's own `cache()` scope — including one inherited from an ancestor, the
|
|
389
391
|
common app-wide storefront shape — COMPOSES with replay instead of disabling
|
|
@@ -836,14 +838,17 @@ that settled inside the capture window pinned its capture-time value into the
|
|
|
836
838
|
shared shell; found live as a storefront basket — with the capturing session's
|
|
837
839
|
identifiers — served to anonymous visitors.)
|
|
838
840
|
|
|
839
|
-
### Handles: handler pushes bake; loader pushes keep "nesting = liveness"
|
|
841
|
+
### Handles: handler and bake-lane pushes bake; loader pushes keep "nesting = liveness"
|
|
840
842
|
|
|
841
843
|
- `ctx.use(H)(promise)` from a handler — a TOP-LEVEL pushed promise is awaited
|
|
842
844
|
at capture and BAKED into the shell (bounded by `ppr.captureTimeout`, 15s by
|
|
843
845
|
default).
|
|
844
|
-
-
|
|
845
|
-
|
|
846
|
-
|
|
846
|
+
- `ctx.use(H)(promise)` (or `.defer()`) from an `ssr: false` LOADER, or a
|
|
847
|
+
loader it awaits — awaited at capture and BAKED the same way, judged by the
|
|
848
|
+
value it settles to, as `push(await promise)` would be (#1182). One that
|
|
849
|
+
rejects, or does not settle within `ppr.captureTimeout`, refuses the
|
|
850
|
+
capture, as a handler's does. A loader without `ssr: false` never runs at
|
|
851
|
+
capture, so its pushes are never in the shell.
|
|
847
852
|
- `ctx.use(H)({ x: promise })` from a HANDLER — also baked. The capture waits
|
|
848
853
|
for every promise nested in a handler's push, at any depth of plain objects,
|
|
849
854
|
arrays and JSX props, before it records the handles every HIT replays. Keep
|
|
@@ -921,7 +926,9 @@ by a navigation alone carries no pins, and its replay runs these loaders fresh.
|
|
|
921
926
|
fill the holes — and the recorded baked paths are overlaid onto the fresh
|
|
922
927
|
result, so the payload matches the prelude while the nested promises stream
|
|
923
928
|
fresh. A fresh rejection skips the overlay and goes to the loader's error
|
|
924
|
-
boundary
|
|
929
|
+
boundary: on a document HIT the payload then carries the error where the
|
|
930
|
+
HTML has the baked value, and React renders that part again on the client
|
|
931
|
+
(one hydration mismatch, the one known residual of a HIT). Give it its own cache,
|
|
925
932
|
`loader(Def, { ssr: false }, () => [cache({ ttl })])`, so that run reads
|
|
926
933
|
through the loader cache.
|
|
927
934
|
|
|
@@ -982,9 +989,12 @@ loader running on the replay awaits it. On a document HIT the page hydrates
|
|
|
982
989
|
with the shell's copy and the hole's value takes its place after hydration
|
|
983
990
|
("Handles on a shell HIT" below). An
|
|
984
991
|
`ssr: false` loader is served from the shell only while the entry holds its
|
|
985
|
-
pin: a snapshot captured by a navigation alone carries none, and a
|
|
986
|
-
`ppr.maxSnapshotBytes`
|
|
987
|
-
pushes are that run's, like
|
|
992
|
+
pin: a snapshot captured by a navigation alone carries none, and a capture
|
|
993
|
+
over `ppr.maxSnapshotBytes` stores the navigation half only, without them.
|
|
994
|
+
There a navigation runs the loader fresh, and its pushes are that run's, like
|
|
995
|
+
its data. A document HIT is served only from an entry that holds a pin for
|
|
996
|
+
every `ssr: false` loader its HTML baked, so a document never hydrates one
|
|
997
|
+
loader's run over HTML rendered from the capture's value.
|
|
988
998
|
|
|
989
999
|
A `Prerender` + `ppr` page follows the same table. Its handler layer comes
|
|
990
1000
|
from the prerendered payload, which never holds a loader push (loaders do not
|
|
@@ -994,63 +1004,76 @@ on its own, and a HIT restores them as above (#1057).
|
|
|
994
1004
|
A dependency the route does not register, which an `ssr: false` loader
|
|
995
1005
|
awaited at capture outside any live loader, is on neither lane: its settled
|
|
996
1006
|
pushes stay as the prelude rendered them, even where a live loader also
|
|
997
|
-
awaits it, while its data is fresh. (They follow the run instead
|
|
998
|
-
entry has
|
|
1007
|
+
awaits it, while its data is fresh. (They follow the run instead when the
|
|
1008
|
+
replayed entry has no pin for the loader that awaits it, because that loader
|
|
1009
|
+
then runs.) To keep
|
|
999
1010
|
its pushes live, declare it as its own `loader()` on the route. A push the
|
|
1000
|
-
capture cannot record (
|
|
1001
|
-
loader record of the page to run: those bodies then still run on each
|
|
1002
|
-
in the background, and that push reaches the page (from the loader's
|
|
1003
|
-
`cache()` entry when it hits) with the run's value, on a document HIT and
|
|
1004
|
-
a client navigation alike. On a document HIT it arrives after hydration.
|
|
1005
|
-
|
|
1006
|
-
**Handles on a shell HIT.**
|
|
1007
|
-
|
|
1008
|
-
(
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1011
|
+
capture cannot record (one whose value, once settled, holds a promise) marks
|
|
1012
|
+
every loader record of the page to run: those bodies then still run on each
|
|
1013
|
+
replay, in the background, and that push reaches the page (from the loader's
|
|
1014
|
+
own `cache()` entry when it hits) with the run's value, on a document HIT and
|
|
1015
|
+
on a client navigation alike. On a document HIT it arrives after hydration.
|
|
1016
|
+
|
|
1017
|
+
**Handles on a shell HIT.** The shell holds what its capture's document
|
|
1018
|
+
rendered: the handlers' pushes and the `ssr: false` loaders' pushes, a
|
|
1019
|
+
promise (`push(promise)`, `.defer()`) resolved. A pushed promise is judged by
|
|
1020
|
+
the value it settles to, as `push(await promise)` would be (#1182). A
|
|
1021
|
+
live-lane loader's push is never in a shell.
|
|
1022
|
+
|
|
1023
|
+
Four things follow, in this order of scope:
|
|
1024
|
+
|
|
1025
|
+
1. **What is in a shell's HTML.** The capture awaits a promise an
|
|
1026
|
+
`ssr: false` loader (or a loader it awaits) pushes, under
|
|
1027
|
+
`ppr.captureTimeout`, and bakes the value. One that settles to a value
|
|
1028
|
+
holding a promise is live: the shell renders no element for it. A loader
|
|
1029
|
+
without `ssr: false` never runs at capture, so nothing of it is ever in
|
|
1030
|
+
the shell.
|
|
1031
|
+
2. **A pushed promise that fails refuses the capture.** One that rejects, or
|
|
1032
|
+
does not settle within `ppr.captureTimeout`, keeps the route on MISS, as
|
|
1033
|
+
a handler's does, and the warning names the loader and the handle. Catch
|
|
1034
|
+
it and push a fallback, or push it from a live-lane loader.
|
|
1035
|
+
3. **On a shell HIT every loader push made during the request arrives after
|
|
1018
1036
|
hydration**, a live-lane loader's included. The document hydrates with
|
|
1019
1037
|
exactly the handle data the shell's HTML was rendered from (the handlers'
|
|
1020
|
-
pushes and the
|
|
1021
|
-
|
|
1022
|
-
|
|
1038
|
+
pushes and the pushes of the loaders that ran at capture), and the
|
|
1039
|
+
request's own pushes follow as a normal `useHandle` update.
|
|
1040
|
+
4. **`useHandle` on every document, `ppr` or not:** a reader that React is
|
|
1023
1041
|
hydrating reads the handle data its HTML was rendered from, wherever its
|
|
1024
1042
|
boundary hydrates, and takes the newer data right after. So a reader
|
|
1025
1043
|
inside the `loading()` or `<Suspense>` of a loader that pushes after an
|
|
1026
1044
|
`await` hydrates clean.
|
|
1027
1045
|
|
|
1028
|
-
| On the HIT's request
|
|
1029
|
-
|
|
|
1030
|
-
| a handler's push, a pinned `ssr: false` loader's
|
|
1031
|
-
| a
|
|
1032
|
-
| a live-lane loader's push
|
|
1033
|
-
| the run of an `ssr: false` loader the shell has no pin for | the capture's push | hydrates with the capture's, then shows the run's |
|
|
1046
|
+
| On the HIT's request | In the HIT's HTML | The client |
|
|
1047
|
+
| ------------------------------------------------------------------------------------------ | ---------------------- | ----------------------------- |
|
|
1048
|
+
| a handler's push, a pinned `ssr: false` loader's push (`push(promise)` and `.defer()` too) | yes (the shell has it) | hydrates with it |
|
|
1049
|
+
| a push by an `ssr: false` loader whose value holds a promise | no | gets it right after hydration |
|
|
1050
|
+
| a live-lane loader's push | no | gets it right after hydration |
|
|
1034
1051
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1052
|
+
So a title an `ssr: false` loader pushes to `Meta`, promise or not, is in
|
|
1053
|
+
every HIT's `<head>`: a crawler and a link preview see it. It bakes with the
|
|
1054
|
+
shell and is invalidated with it (its TTL, its tags, `updateTag()`). A value
|
|
1055
|
+
that must stay per request comes from a loader without `ssr: false`, which
|
|
1056
|
+
nothing has to read:
|
|
1039
1057
|
|
|
1040
1058
|
```ts
|
|
1041
|
-
|
|
1042
|
-
ctx.use(Notes)(
|
|
1059
|
+
const StockNote = createLoader(async (ctx) => {
|
|
1060
|
+
ctx.use(Notes)(db.stockNote(ctx.params.id)); // live: after hydration on a HIT
|
|
1061
|
+
return null;
|
|
1062
|
+
});
|
|
1063
|
+
path("/product/:id", ProductPage, { name: "product", ppr: true }, () => [
|
|
1064
|
+
loader(Product, { ssr: false }), // bakes: its data and its pushes
|
|
1065
|
+
loader(StockNote), // live lane: runs on every request
|
|
1066
|
+
]);
|
|
1043
1067
|
```
|
|
1044
1068
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
says "per request", the same as a promise in the loader's data.
|
|
1069
|
+
Or nest the value in a settled push (`{ label, count: promise }`): the whole
|
|
1070
|
+
push then stays live, and every pinned body of the page runs on each HIT.
|
|
1048
1071
|
|
|
1049
1072
|
A pinned loader's pushes are exactly the ones its capture made. If its body
|
|
1050
|
-
pushes a
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
delivered on every replay.
|
|
1073
|
+
pushes a value only on some runs (behind a condition), a replay that runs it
|
|
1074
|
+
shows the capture's answer, a push or none, never a later run's next to the
|
|
1075
|
+
pinned data. A push whose value holds a promise is the exception by design:
|
|
1076
|
+
it is delivered on every replay.
|
|
1054
1077
|
|
|
1055
1078
|
One arrangement mixes sources by design. A loader that reads a
|
|
1056
1079
|
`cache()`-bound loader with `ctx.use()` before that binding starts (a loader
|
|
@@ -1392,13 +1415,13 @@ path(
|
|
|
1392
1415
|
);
|
|
1393
1416
|
```
|
|
1394
1417
|
|
|
1395
|
-
| Field | Default | Notes
|
|
1396
|
-
| ------------------ | ------- |
|
|
1397
|
-
| `ttl` | `300` | shell freshness window in seconds (`ppr: true` uses the default)
|
|
1398
|
-
| `swr` | — | stale window: serve the stale shell + background recapture
|
|
1399
|
-
| `tags` | — | operational tags UNIONED with the tags the capture collects — see "Invalidation" below
|
|
1400
|
-
| `maxSnapshotBytes` | 8 MiB | cap on the loader pins in the entry's snapshot (the bake-lane loader records). The recorded handler layer is exempt.
|
|
1401
|
-
| `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
|
|
1418
|
+
| Field | Default | Notes |
|
|
1419
|
+
| ------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1420
|
+
| `ttl` | `300` | shell freshness window in seconds (`ppr: true` uses the default) |
|
|
1421
|
+
| `swr` | — | stale window: serve the stale shell + background recapture |
|
|
1422
|
+
| `tags` | — | operational tags UNIONED with the tags the capture collects — see "Invalidation" below |
|
|
1423
|
+
| `maxSnapshotBytes` | 8 MiB | cap on the loader pins in the entry's snapshot (the bake-lane loader records). The recorded handler layer is exempt. A document HIT is served only from an entry that holds a pin for every `ssr: false` loader its HTML baked, so over the cap no document shell is stored: the entry keeps the handler layer only, documents are a MISS (full SSR, no hydration error, no remount), client navigations still replay the handler layer and run the bake-lane loaders fresh, and the URL backs off like a refused capture. Warned once per key: "the document shell was not stored; raise maxSnapshotBytes or shrink the ssr: false data" |
|
|
1424
|
+
| `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 |
|
|
1402
1425
|
|
|
1403
1426
|
A shell never outlives the route `cache()` entry it was captured from: under
|
|
1404
1427
|
a route `cache()`, the shell is fresh no longer than that entry is (at most
|
|
@@ -1329,9 +1329,9 @@ write that started before an invalidation of one of its tags is refused.
|
|
|
1329
1329
|
### Limits worth knowing
|
|
1330
1330
|
|
|
1331
1331
|
- **Only the router's own handler.** A warm goes through `router.fetch`, the
|
|
1332
|
-
handler `createRouter`'s `cache
|
|
1333
|
-
entry that builds its own `createRSCHandler({ cache
|
|
1334
|
-
|
|
1332
|
+
handler `createRouter`'s `cache` and `nonce` configure. An app
|
|
1333
|
+
entry that builds its own `createRSCHandler({ cache })` is not what a warm
|
|
1334
|
+
runs: put the option on `createRouter`.
|
|
1335
1335
|
- **Document records only.** Navigation (`partial:`) and intercept records
|
|
1336
1336
|
fill on the first navigation.
|
|
1337
1337
|
- **One location at a time.** A warm does not reach a copy another location
|
|
@@ -266,8 +266,6 @@ Notes:
|
|
|
266
266
|
a cleared cache) on every build; the build prints one note when that applies.
|
|
267
267
|
A router that renders no such action keeps its cache without one (`/use-cache`,
|
|
268
268
|
`/cache-guide`).
|
|
269
|
-
- `createRouter({ version })` takes effect on every preset, including `node`
|
|
270
|
-
and `vercel`. It replaces both computed cache versions with that exact value.
|
|
271
269
|
- `@rangojs/router/vite` also exports `poke()`, a dev-server plugin: type `e`
|
|
272
270
|
then Enter (or Ctrl+R where the terminal passes it through) to full-reload
|
|
273
271
|
the browser.
|
|
@@ -8,23 +8,23 @@ A Rango route handler is a pure function `(ctx) => rsc` — the function you pas
|
|
|
8
8
|
|
|
9
9
|
### Options — `RenderHandlerOptions`
|
|
10
10
|
|
|
11
|
-
| Field | Type | Meaning
|
|
12
|
-
| ---------------------- | ------------------------------------------------------ |
|
|
13
|
-
| `params` | `Record<string, string>` | Route params surfaced as `ctx.params`.
|
|
14
|
-
| `env` | `TEnv` | Environment bindings surfaced as `ctx.env`.
|
|
15
|
-
| `request` | `Request \| string` | Backing Request (string or `Request`); defaults to a localhost GET.
|
|
16
|
-
| `headers` | `HeadersInit` | Request headers (e.g. `Cookie`) the handler reads via `cookies()`. Used only when `request` is a string or omitted; a passed `Request` keeps its own headers.
|
|
17
|
-
| `build` | `boolean` | Seed `ctx.build` (default `false`) to test a handler that branches on the build-time pass, including calling `ctx.dynamic()` on a miss. Assert the opt-out via `result.dynamic`.
|
|
18
|
-
| `theme` | `ThemeConfig \| true` | Theme config (`createRouter({ theme })` shape). Without it `ctx.theme`/`ctx.setTheme` are inert; pass one to test a handler that reads the theme or writes the theme cookie.
|
|
19
|
-
| `vars` | `VarsInit` (object or `[[Var, value]]` tuples) | Variables a prior middleware set, read via `ctx.get(...)`.
|
|
20
|
-
| `routeName` | `string` | Matched route name (drives `ctx.routeName` and scoped reverse).
|
|
21
|
-
| `routeMap` | `Record<string, string>` | Route name -> pattern map enabling `ctx.reverse()`.
|
|
22
|
-
| `loaders` | `ReadonlyArray<readonly [LoaderDefinition, unknown]>` | Seed the data `ctx.use(SomeLoader)` returns. Matched by loader reference; NO real loader runs.
|
|
23
|
-
| `clientComponents` | `Record<string, unknown>` | `"use client"` components in the handler's RSC, so they serialize as real boundaries when `rangoUseClientTransform()` is not wired. Keyed by name.
|
|
24
|
-
| `stateCookie` | `StateCookieSeed` (`{ prefix?, routerId?, version? }`) | Customize the rango state cookie a handler calling `invalidateClientCache()` rotates. The name is ALWAYS seeded (default `rango-state_router_0`) so the rotation `Set-Cookie` fires like production rather than no-opping; override `prefix`/`routerId` to match your `createRouter({ stateCookiePrefix, id })`, or `version` (
|
|
25
|
-
| `cacheStore` | `SegmentCacheStore` | Segment cache store backing a `"use cache"` function the handler invokes (e.g. `new MemorySegmentCacheStore()`). WITHOUT it, `registerCachedFunction` takes the uncached bypass and the cached path is NOT exercised (the runtime emits a one-time warning under the test runner). Pair with `cacheProfiles`.
|
|
26
|
-
| `cacheProfiles` | `Record<string, CacheProfile>` | Cache profiles in the `createRouter({ cacheProfiles })` shape, required for `"use cache: profileName"` resolution once a `cacheStore` is wired.
|
|
27
|
-
| `inActionRevalidation` | `boolean` | Render as if inside a server action's revalidation render (production sets this in `revalidateAfterAction`). A stale `"use cache"` entry whose profile sets `foregroundOnAction: true` then re-executes in the FOREGROUND (fresh result in this render) instead of SWR. Pair with `cacheStore` + `cacheProfiles` to exercise the opt-in.
|
|
11
|
+
| Field | Type | Meaning |
|
|
12
|
+
| ---------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `params` | `Record<string, string>` | Route params surfaced as `ctx.params`. |
|
|
14
|
+
| `env` | `TEnv` | Environment bindings surfaced as `ctx.env`. |
|
|
15
|
+
| `request` | `Request \| string` | Backing Request (string or `Request`); defaults to a localhost GET. |
|
|
16
|
+
| `headers` | `HeadersInit` | Request headers (e.g. `Cookie`) the handler reads via `cookies()`. Used only when `request` is a string or omitted; a passed `Request` keeps its own headers. |
|
|
17
|
+
| `build` | `boolean` | Seed `ctx.build` (default `false`) to test a handler that branches on the build-time pass, including calling `ctx.dynamic()` on a miss. Assert the opt-out via `result.dynamic`. |
|
|
18
|
+
| `theme` | `ThemeConfig \| true` | Theme config (`createRouter({ theme })` shape). Without it `ctx.theme`/`ctx.setTheme` are inert; pass one to test a handler that reads the theme or writes the theme cookie. |
|
|
19
|
+
| `vars` | `VarsInit` (object or `[[Var, value]]` tuples) | Variables a prior middleware set, read via `ctx.get(...)`. |
|
|
20
|
+
| `routeName` | `string` | Matched route name (drives `ctx.routeName` and scoped reverse). |
|
|
21
|
+
| `routeMap` | `Record<string, string>` | Route name -> pattern map enabling `ctx.reverse()`. |
|
|
22
|
+
| `loaders` | `ReadonlyArray<readonly [LoaderDefinition, unknown]>` | Seed the data `ctx.use(SomeLoader)` returns. Matched by loader reference; NO real loader runs. |
|
|
23
|
+
| `clientComponents` | `Record<string, unknown>` | `"use client"` components in the handler's RSC, so they serialize as real boundaries when `rangoUseClientTransform()` is not wired. Keyed by name. |
|
|
24
|
+
| `stateCookie` | `StateCookieSeed` (`{ prefix?, routerId?, version? }`) | Customize the rango state cookie a handler calling `invalidateClientCache()` rotates. The name is ALWAYS seeded (default `rango-state_router_0`) so the rotation `Set-Cookie` fires like production rather than no-opping; override `prefix`/`routerId` to match your `createRouter({ stateCookiePrefix, id })`, or `version` (a seeded app version: the value is `{version}:{timestamp}`, default `"0"`). |
|
|
25
|
+
| `cacheStore` | `SegmentCacheStore` | Segment cache store backing a `"use cache"` function the handler invokes (e.g. `new MemorySegmentCacheStore()`). WITHOUT it, `registerCachedFunction` takes the uncached bypass and the cached path is NOT exercised (the runtime emits a one-time warning under the test runner). Pair with `cacheProfiles`. |
|
|
26
|
+
| `cacheProfiles` | `Record<string, CacheProfile>` | Cache profiles in the `createRouter({ cacheProfiles })` shape, required for `"use cache: profileName"` resolution once a `cacheStore` is wired. |
|
|
27
|
+
| `inActionRevalidation` | `boolean` | Render as if inside a server action's revalidation render (production sets this in `revalidateAfterAction`). A stale `"use cache"` entry whose profile sets `foregroundOnAction: true` then re-executes in the FOREGROUND (fresh result in this render) instead of SWR. Pair with `cacheStore` + `cacheProfiles` to exercise the opt-in. |
|
|
28
28
|
|
|
29
29
|
### Context — `HandlerContext` (what your handler receives)
|
|
30
30
|
|
|
@@ -249,7 +249,7 @@ Vitest runs no Vite id transform, so give each router an explicit `id` when a te
|
|
|
249
249
|
| `hydration` | What the document hydrates with (`metadata.handles`, read to its end before hydration starts). For a `partial` request, the last state it streamed. |
|
|
250
250
|
| `late` | The states that arrived on the late channel (`metadata.handlesLate`), in order. Each replaces the client's handle data after hydration. Often `[]`. |
|
|
251
251
|
|
|
252
|
-
On a HIT the client hydrates with exactly what the shell was rendered from, so `hydration` is also what the shell's HTML holds, and whatever this request's loaders push, replace or drop is in `late` (see `/ppr`, "Handles on a shell HIT"):
|
|
252
|
+
On a HIT the client hydrates with exactly what the shell was rendered from, so `hydration` is also what the shell's HTML holds (the handlers' pushes and the `ssr: false` loaders', a pushed promise resolved), and whatever this request's loaders push, replace or drop is in `late` (see `/ppr`, "Handles on a shell HIT"):
|
|
253
253
|
|
|
254
254
|
```ts
|
|
255
255
|
// The handler pushed ctx.use(Notes)("from-handler"); a live loader pushes
|