@rangojs/router 0.34.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/{chunk-4HHFQSDC.js → chunk-3CQXDX3A.js} +5 -5
- package/dist/bin/{client-urls-projection-4Q5EQDAL.js → client-urls-projection-2GBDM5YK.js} +1 -1
- package/dist/bin/rango.js +1 -1
- package/dist/bin/{runtime-discovery-2ZTYRZ3V.js → runtime-discovery-PA3V3IFQ.js} +2 -2
- package/dist/bin/{temp-rsc-server-EUNKRFJD.js → temp-rsc-server-WDXEHDY7.js} +2 -2
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/types.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +1 -1
- package/dist/types/client-urls/types.d.ts +6 -6
- package/dist/types/decode-loader-results.d.ts +1 -1
- package/dist/types/errors.d.ts +3 -3
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/loader-store.d.ts +20 -8
- package/dist/types/outlet-context.d.ts +1 -1
- package/dist/types/route-definition/helpers-types.d.ts +5 -5
- package/dist/types/router/loader-resolution.d.ts +1 -1
- package/dist/types/router/match-context.d.ts +12 -0
- package/dist/types/router/navigation-snapshot.d.ts +17 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +7 -7
- package/dist/types/router/types.d.ts +1 -1
- package/dist/types/rsc/shell-capture-constants.d.ts +2 -2
- package/dist/types/server/context.d.ts +3 -3
- package/dist/types/server/request-context.d.ts +1 -1
- package/dist/types/ssr/index.d.ts +1 -1
- package/dist/types/ssr/ssr-root.d.ts +1 -1
- package/dist/types/ssr-suspension-warning.d.ts +1 -1
- package/dist/types/types/loader-types.d.ts +19 -19
- package/dist/types/types/segments.d.ts +2 -2
- package/dist/types/urls/path-helper-types.d.ts +4 -4
- package/dist/types/urls/pattern-types.d.ts +1 -1
- package/dist/types/vite/plugin-types.d.ts +1 -1
- package/dist/vite/index.js +7 -7
- package/package.json +1 -1
- package/skills/app-structure/e-commerce.md +1 -1
- package/skills/breadcrumbs/SKILL.md +5 -5
- package/skills/cache-guide/SKILL.md +4 -4
- package/skills/caching/SKILL.md +1 -1
- package/skills/catalog.json +2 -2
- package/skills/client-urls/SKILL.md +20 -20
- package/skills/comparison/references/framework-comparison.md +6 -6
- package/skills/defer-hydration/SKILL.md +2 -2
- package/skills/handler-use/SKILL.md +5 -5
- package/skills/hooks/data.md +5 -2
- package/skills/hooks/handle-and-actions.md +9 -9
- package/skills/loader/SKILL.md +52 -47
- package/skills/middleware/SKILL.md +2 -2
- package/skills/migrate-nextjs/SKILL.md +10 -10
- package/skills/migrate-react-router/SKILL.md +1 -1
- package/skills/migrate-react-router/component-migration.md +2 -2
- package/skills/migrate-react-router/data-and-actions.md +2 -2
- package/skills/migrate-react-router/route-mapping.md +2 -2
- package/skills/parallel/SKILL.md +2 -2
- package/skills/ppr/SKILL.md +91 -92
- package/skills/prerender/SKILL.md +5 -5
- package/skills/rango/SKILL.md +7 -7
- package/skills/route/SKILL.md +1 -1
- package/skills/router-setup/SKILL.md +2 -2
- package/skills/scripts/SKILL.md +3 -3
- package/skills/shell-manifest/SKILL.md +3 -3
- package/skills/testing/serve-shell-request.md +1 -1
- package/skills/theme/SKILL.md +2 -2
- package/skills/typesafety/env-and-bindings.md +2 -2
- package/skills/use-cache/SKILL.md +1 -1
- package/skills/ux-architecture/SKILL.md +7 -7
- package/src/cache/shell-snapshot.ts +1 -1
- package/src/cache/types.ts +1 -1
- package/src/client-urls/client-urls.ts +16 -9
- package/src/client-urls/server-projection.ts +4 -4
- package/src/client-urls/types.ts +6 -6
- package/src/decode-loader-results.ts +1 -1
- package/src/errors.ts +9 -9
- package/src/handles/meta.ts +2 -2
- package/src/loader-store.ts +29 -13
- package/src/outlet-context.ts +1 -1
- package/src/route-definition/dsl-helpers.ts +4 -8
- package/src/route-definition/helpers-types.ts +5 -5
- package/src/router/loader-resolution.ts +4 -4
- package/src/router/match-api.ts +2 -0
- package/src/router/match-context.ts +22 -1
- package/src/router/match-middleware/cache-lookup.ts +17 -8
- package/src/router/match-middleware/intercept-resolution.ts +3 -3
- package/src/router/navigation-snapshot.ts +39 -8
- package/src/router/segment-resolution/loader-cache.ts +8 -8
- package/src/router/segment-resolution/prefetch-deferral.ts +1 -1
- package/src/router/segment-resolution/revalidation.ts +3 -3
- package/src/router/types.ts +1 -1
- package/src/rsc/shell-capture-constants.ts +3 -3
- package/src/rsc/shell-capture.ts +12 -12
- package/src/segment-system.tsx +3 -3
- package/src/server/context.ts +5 -5
- package/src/server/cookie-store.ts +1 -1
- package/src/server/request-context.ts +1 -1
- package/src/ssr/index.tsx +6 -6
- package/src/ssr/ssr-root.tsx +1 -1
- package/src/ssr-suspension-warning.ts +4 -4
- package/src/types/loader-types.ts +19 -19
- package/src/types/segments.ts +2 -2
- package/src/urls/path-helper-types.ts +4 -4
- package/src/urls/pattern-types.ts +1 -1
- package/src/use-loader.tsx +5 -3
- package/src/vite/discovery/shell-prerender-phase.ts +1 -1
- package/src/vite/plugin-types.ts +1 -1
package/skills/ppr/SKILL.md
CHANGED
|
@@ -10,7 +10,7 @@ Caches the rendered HTML **shell** of a page route (React `prerender` prelude
|
|
|
10
10
|
bytes plus `postponed` state) and, on a later request, flushes those bytes after
|
|
11
11
|
route classification and the complete middleware chain, but before downstream
|
|
12
12
|
tail rendering. It then resumes React's HTML renderer for just the live holes.
|
|
13
|
-
The browser sees one ordinary streamed document; live loaders (no `ssr:
|
|
13
|
+
The browser sees one ordinary streamed document; live loaders (no `ssr: "await"`)
|
|
14
14
|
stay fresh on every request. A normal render (no shell) is untouched, and every
|
|
15
15
|
request the shell cannot serve falls open to it.
|
|
16
16
|
|
|
@@ -38,7 +38,7 @@ PPR with HTTP shared-cache headers.
|
|
|
38
38
|
- You want build-time Flight segment payloads from `Static()`/`Prerender()` —
|
|
39
39
|
see `/prerender`. A `Prerender` page may also declare `ppr`; then the
|
|
40
40
|
build-time shell capture bakes the HTML shell while loaders without
|
|
41
|
-
`ssr:
|
|
41
|
+
`ssr: "await"` stay live.
|
|
42
42
|
- You want cached segments with live loaders but no cached HTML — that is the
|
|
43
43
|
`cache()` DSL: see `/caching`.
|
|
44
44
|
- You are unsure which cache layer you need — start at `/cache-guide`.
|
|
@@ -119,9 +119,9 @@ settled by then stores no shell (the once-per-key warning names the cause: "did
|
|
|
119
119
|
not settle within ppr.captureTimeout"). A shell HIT never runs a handler: it
|
|
120
120
|
replays the handler layer the capture recorded.
|
|
121
121
|
|
|
122
|
-
To keep a region fresh per request, load it in a loader WITHOUT `ssr:
|
|
122
|
+
To keep a region fresh per request, load it in a loader WITHOUT `ssr: "await"`
|
|
123
123
|
and read it with `useLoader` under `loading()` or an inline `<Suspense>` (a live
|
|
124
|
-
hole, see "The hole doctrine"); inside a `loader(Def, { ssr:
|
|
124
|
+
hole, see "The hole doctrine"); inside a `loader(Def, { ssr: "await" })`, return
|
|
125
125
|
the live part as a nested promise.
|
|
126
126
|
|
|
127
127
|
A route WITHOUT the `ppr` option renders normally (no shell): no store read, no
|
|
@@ -138,8 +138,8 @@ promise it passes to a component under `<Suspense>`, an async server component,
|
|
|
138
138
|
a nested promise in a handle it pushes, and a loader it awaits are all awaited
|
|
139
139
|
at capture (bounded by `ppr.captureTimeout`) and served frozen for the shell's
|
|
140
140
|
lifetime. A HIT never runs a handler. To keep a value fresh per request, load
|
|
141
|
-
it in a loader without `ssr:
|
|
142
|
-
`loading()` or an inline `<Suspense>`; inside an `ssr:
|
|
141
|
+
it in a loader without `ssr: "await"` and read it with `useLoader` under
|
|
142
|
+
`loading()` or an inline `<Suspense>`; inside an `ssr: "await"` loader, return
|
|
143
143
|
that part as a nested promise. Request-scoped reads the capture now waits for
|
|
144
144
|
refuse it: `cookies()`, `headers()`, `ctx.request.headers`, a
|
|
145
145
|
`{ cache: false }` variable, and
|
|
@@ -356,7 +356,7 @@ slow leaves the entry alone.
|
|
|
356
356
|
### Soft navigation caches and reuses the handler layer
|
|
357
357
|
|
|
358
358
|
In short: client-side (soft) navigations to a `ppr` route reuse the handler
|
|
359
|
-
segments captured with the shell, while loaders without `ssr:
|
|
359
|
+
segments captured with the shell, while loaders without `ssr: "await"` stay live.
|
|
360
360
|
Nothing needs configuring; the rest of this section explains the
|
|
361
361
|
`x-rango-ppr-replay` header for when you are debugging replay behavior.
|
|
362
362
|
|
|
@@ -380,12 +380,12 @@ This is deliberately invisible to the browser: the response is the same
|
|
|
380
380
|
`RscPayload` shape as any other partial navigation. A shell snapshot holds no
|
|
381
381
|
`"use cache"` or loader `cache()` values. Bake-lane loader pins are, when the replayed
|
|
382
382
|
snapshot is a shell a document request captured, as on a document HIT: a
|
|
383
|
-
promise-free `ssr:
|
|
383
|
+
promise-free `ssr: "await"` loader is served from its pin without running
|
|
384
384
|
(unless the capture marked it to run: see "On a shell HIT"), and one whose
|
|
385
385
|
return holds promises runs with its baked parts overlaid. A snapshot captured
|
|
386
386
|
by a navigation alone carries no pins, nor does an entry over
|
|
387
387
|
`ppr.maxSnapshotBytes`, and its replay runs them fresh. Loaders without
|
|
388
|
-
`ssr:
|
|
388
|
+
`ssr: "await"` stay live.
|
|
389
389
|
|
|
390
390
|
A route's own `cache()` scope — including one inherited from an ancestor, the
|
|
391
391
|
common app-wide storefront shape — COMPOSES with replay instead of disabling
|
|
@@ -615,7 +615,7 @@ curl -s -D - -o /dev/null https://app.example.com/products/1 | grep -i x-rango-s
|
|
|
615
615
|
the stored entry's size. In dev, past 2s, the settle is also
|
|
616
616
|
named once per key in a console warning with the remedies (`cache()` the
|
|
617
617
|
work / move it into a live loader / return it as a nested promise or drop
|
|
618
|
-
`ssr:
|
|
618
|
+
`ssr: "await"` in a bake-lane loader). In dev, with
|
|
619
619
|
`debugPerformance` on, the last capture outcome for a key also rides the
|
|
620
620
|
next document GET's `Server-Timing` as `ppr-capture;desc="…"`.
|
|
621
621
|
- For deployed Cloudflare tier diagnostics, build with
|
|
@@ -682,10 +682,10 @@ skill → `serve-shell-request.md` and `cache-prerender.md` (PPR shell section).
|
|
|
682
682
|
Holes are **render-defined**, decided by the shape of the tree. There are two
|
|
683
683
|
classes, and the handler layer is always on the SHELL side:
|
|
684
684
|
|
|
685
|
-
| Class | What decides it
|
|
686
|
-
| -------------- |
|
|
687
|
-
| **STRUCTURAL** | a LIVE-lane loader (no `ssr:
|
|
688
|
-
| **SHELL** | everything the handler layer produces — awaited data, a promise it passes as a prop (even under the consumer's `<Suspense>`), an async server component, handle pushes at any depth, a loader it awaits (`await ctx.use(Loader)`) — plus replayed `cache()` segments and the settled non-promise data of BAKE-lane (`ssr:
|
|
685
|
+
| Class | What decides it | At capture | At serve (HIT) |
|
|
686
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
|
687
|
+
| **STRUCTURAL** | a LIVE-lane loader (no `ssr: "await"`) read under a boundary: `loading()` (the whole segment subtree under it) or an inline `<Suspense>` above the reader | live loaders masked; the boundary postpones; its fallback bakes in as route structure | loaders run fresh; resume fills the hole |
|
|
688
|
+
| **SHELL** | everything the handler layer produces — awaited data, a promise it passes as a prop (even under the consumer's `<Suspense>`), an async server component, handle pushes at any depth, a loader it awaits (`await ctx.use(Loader)`) — plus replayed `cache()` segments and the settled non-promise data of BAKE-lane (`ssr: "await"`) loaders | awaited (one deadline, `ppr.captureTimeout`) and baked into the prelude and the shell's recorded segments | served from the frozen prelude; no handler runs |
|
|
689
689
|
|
|
690
690
|
One hole lives INSIDE loader data, decided by shape: a promise nested in a
|
|
691
691
|
bake-lane loader's return value, or in a handle container a loader pushes, is
|
|
@@ -696,13 +696,12 @@ HIT").
|
|
|
696
696
|
|
|
697
697
|
The promise rule, by producer: **a promise a handler produces bakes, at any
|
|
698
698
|
depth; a promise nested in loader data stays live.** A
|
|
699
|
-
`loader(Def, { ssr:
|
|
699
|
+
`loader(Def, { ssr: "await" })` is the BAKE lane (see the lane rule below): its
|
|
700
700
|
settled non-promise data is shell material, exactly like handler output, while
|
|
701
701
|
its nested promises are holes. A live-lane loader is a hole as a whole.
|
|
702
|
-
`ssr:
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
the shell. Delivery details: `/loader` → "`ssr: false` — Guarantee a Loader in
|
|
702
|
+
`ssr: "await"` on a loader makes the render await it: the document does not
|
|
703
|
+
stream a fallback for it but resolves the loader before the first flush, so
|
|
704
|
+
under `ppr` its value lands in the prelude and bakes into the shell. Delivery details: `/loader` → "`ssr: "await"` — Guarantee a Loader in
|
|
706
705
|
the Document".
|
|
707
706
|
|
|
708
707
|
An async server component that never settles inside `ppr.captureTimeout`
|
|
@@ -712,7 +711,7 @@ its data into a live loader read under a boundary.
|
|
|
712
711
|
|
|
713
712
|
### The loader lane rule
|
|
714
713
|
|
|
715
|
-
**Only `loader(Def, { ssr:
|
|
714
|
+
**Only `loader(Def, { ssr: "await" })` bakes into the shell, and only the parts
|
|
716
715
|
of its returned data that are NOT promises.** The loader runs at capture; its
|
|
717
716
|
settled non-promise values are frozen into the shell (snapshot-pinned per
|
|
718
717
|
shell). Any promise inside the returned data is masked at capture and stays a
|
|
@@ -749,7 +748,7 @@ registered read stays a live hole.)
|
|
|
749
748
|
`ctx.use(Handle)({ ..., x: promise })` inside a loader keeps `x` live. The
|
|
750
749
|
same push from a HANDLER bakes `x` (see "Handles").
|
|
751
750
|
|
|
752
|
-
**Every loader without `ssr:
|
|
751
|
+
**Every loader without `ssr: "await"` is entirely live**, whether or not it has
|
|
753
752
|
`loading()`: masked at capture with a never-resolving promise, postponed at its
|
|
754
753
|
boundary, and streamed fresh on every request. It needs a boundary —
|
|
755
754
|
`loading()` or an inline `<Suspense>` above the reader — or the shell is
|
|
@@ -758,7 +757,7 @@ sanity gate refuses the capture, and the route stays an eternal MISS (warned
|
|
|
758
757
|
once per key). When that read also throws on the MISS render, the document is
|
|
759
758
|
the 500 recovery document the `/loader` skill describes ("A read outside any
|
|
760
759
|
`<Suspense>`"), and nothing is stored. A route whose loaders are ALL
|
|
761
|
-
`ssr:
|
|
760
|
+
`ssr: "await"` needs no `loading()`: nothing masks, and the shell captures
|
|
762
761
|
complete.
|
|
763
762
|
|
|
764
763
|
```typescript
|
|
@@ -772,7 +771,7 @@ export const ProductLoader = createLoader(async (ctx) => {
|
|
|
772
771
|
});
|
|
773
772
|
|
|
774
773
|
path("/products/:id", ProductPage, { name: "product", ppr: true }, () => [
|
|
775
|
-
loader(ProductLoader, { ssr:
|
|
774
|
+
loader(ProductLoader, { ssr: "await" }), // BAKE lane
|
|
776
775
|
loader(StockLoader), // LIVE lane: ProductPage reads it under an inline <Suspense>
|
|
777
776
|
]);
|
|
778
777
|
```
|
|
@@ -834,7 +833,7 @@ async function Handler(ctx: HandlerContext) {
|
|
|
834
833
|
<Suspense>: awaited at capture → BAKED */}
|
|
835
834
|
<LiveStats /> {/* "use client": useLoader(StatsLoader)
|
|
836
835
|
under <Suspense>, StatsLoader registered
|
|
837
|
-
without ssr:
|
|
836
|
+
without ssr: "await" → HOLE */}
|
|
838
837
|
</section>
|
|
839
838
|
);
|
|
840
839
|
}
|
|
@@ -845,10 +844,10 @@ async function Handler(ctx: HandlerContext) {
|
|
|
845
844
|
| Your live region is… | Use | Why |
|
|
846
845
|
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
847
846
|
| data that must be fresh EVERY serve | `loader()` (the live lane) + `useLoader` under `loading()` or an inline `<Suspense>` | the guaranteed hole — masked at capture, fresh every serve, immune to fast resolution |
|
|
848
|
-
| loader data, shell container + live parts | `loader(Def, { ssr:
|
|
847
|
+
| loader data, shell container + live parts | `loader(Def, { ssr: "await" })` (the bake lane): `{ static, dynamic: promise }` | plain values bake (snapshot-pinned per shell); nested promises hole at the consumer's `<Suspense>` |
|
|
849
848
|
| handler-fetched data (db, fetch) | nothing to choose: it bakes, whether the handler awaits it or passes the promise down | handler output is shell material; move the fetch into a live-lane loader to make it live |
|
|
850
849
|
| handle metadata that must be live | push it from a loader: a live-lane loader pushes per request; a bake-lane loader's nested promise stays a hole | handler pushes bake at any depth; loader pushes keep "nesting = liveness" |
|
|
851
|
-
| already-resolved / instant / synchronous values | `loader(() => Promise.resolve(x))` (no `ssr:
|
|
850
|
+
| already-resolved / instant / synchronous values | `loader(() => Promise.resolve(x))` (no `ssr: "await"`) + a boundary | live-lane loaders are masked at capture no matter how fast they settle |
|
|
852
851
|
| none of the above | nothing | it bakes — that is what the shell is for |
|
|
853
852
|
|
|
854
853
|
Why a handler promise cannot be a hole: every HIT replays the handler layer the
|
|
@@ -868,11 +867,11 @@ identifiers — served to anonymous visitors.)
|
|
|
868
867
|
- `ctx.use(H)(promise)` from a handler — a TOP-LEVEL pushed promise is awaited
|
|
869
868
|
at capture and BAKED into the shell (bounded by `ppr.captureTimeout`, 15s by
|
|
870
869
|
default).
|
|
871
|
-
- `ctx.use(H)(promise)` (or `.defer()`) from an `ssr:
|
|
870
|
+
- `ctx.use(H)(promise)` (or `.defer()`) from an `ssr: "await"` LOADER, or a
|
|
872
871
|
loader it awaits — awaited at capture and BAKED the same way, judged by the
|
|
873
872
|
value it settles to, as `push(await promise)` would be (#1182). One that
|
|
874
873
|
rejects, or does not settle within `ppr.captureTimeout`, refuses the
|
|
875
|
-
capture, as a handler's does. A loader without `ssr:
|
|
874
|
+
capture, as a handler's does. A loader without `ssr: "await"` never runs at
|
|
876
875
|
capture, so its pushes are never in the shell.
|
|
877
876
|
- `ctx.use(H)({ x: promise })` from a HANDLER — also baked. The capture waits
|
|
878
877
|
for every promise nested in a handler's push, at any depth of plain objects,
|
|
@@ -881,24 +880,24 @@ identifiers — served to anonymous visitors.)
|
|
|
881
880
|
or class instance is not waited for, and if it has not settled when the handles are encoded the
|
|
882
881
|
capture is refused.
|
|
883
882
|
- `ctx.use(H)({ x: promise })` from a LOADER — nesting = liveness. A bake-lane
|
|
884
|
-
(`ssr:
|
|
883
|
+
(`ssr: "await"`) loader's container settles and bakes, while the nested promise
|
|
885
884
|
is masked at capture regardless of settle timing and streams fresh to the
|
|
886
885
|
consumer's `<Suspense>` on every HIT (the same mask as bake-lane loader data,
|
|
887
886
|
at any depth of plain objects, arrays and JSX props; a promise inside a
|
|
888
887
|
`Map`, `Set`, or class instance is not masked). A live-lane
|
|
889
|
-
loader never runs at capture unless a handler or an `ssr:
|
|
888
|
+
loader never runs at capture unless a handler or an `ssr: "await"` loader
|
|
890
889
|
awaits it; either
|
|
891
890
|
way its pushes, and those of the loaders it awaits, are live on every HIT.
|
|
892
891
|
|
|
893
892
|
### Want a hole for already-resolved data?
|
|
894
893
|
|
|
895
|
-
Put it in a loader without `ssr:
|
|
894
|
+
Put it in a loader without `ssr: "await"`: `loader(() => Promise.resolve(x))`
|
|
896
895
|
under `loading()` or an inline `<Suspense>`. Live-lane loaders are masked at
|
|
897
896
|
capture and fresh on every serve, no matter how fast the value settles.
|
|
898
897
|
|
|
899
|
-
### The bake lane: `loader(Def, { ssr:
|
|
898
|
+
### The bake lane: `loader(Def, { ssr: "await" })`
|
|
900
899
|
|
|
901
|
-
A loader registered with `{ ssr:
|
|
900
|
+
A loader registered with `{ ssr: "await" }` EXECUTES during capture (the capture
|
|
902
901
|
gate holds open for its real latency, bounded by `ppr.captureTimeout`, 15s by
|
|
903
902
|
default). The flag's document promise — "this loader's data is in the HTML
|
|
904
903
|
before first flush" — maps to the frozen prelude under ppr. Its settled
|
|
@@ -920,12 +919,12 @@ export const StorefrontContextLoader = createLoader(async (ctx) => {
|
|
|
920
919
|
});
|
|
921
920
|
|
|
922
921
|
layout(StoreLayout, () => [
|
|
923
|
-
loader(StorefrontContextLoader, { ssr:
|
|
922
|
+
loader(StorefrontContextLoader, { ssr: "await" }), // the flag selects the bake lane
|
|
924
923
|
path("/", HomePage, { name: "home", ppr: true }),
|
|
925
924
|
]);
|
|
926
925
|
```
|
|
927
926
|
|
|
928
|
-
The lane is decided PER LOADER, by its own `ssr:
|
|
927
|
+
The lane is decided PER LOADER, by its own `ssr: "await"` flag — `loading()` on
|
|
929
928
|
the same entry, a parent, or a child never changes it. `loading()` only decides
|
|
930
929
|
where a live loader's hole sits, and it IS valid on layout and parallel
|
|
931
930
|
entries, not just routes.
|
|
@@ -935,7 +934,7 @@ entries, not just routes.
|
|
|
935
934
|
What a bake-lane loader does on a HIT depends on whether its recorded
|
|
936
935
|
container carried holes. A client navigation that replays a shell captured by
|
|
937
936
|
a document request (`x-rango-ppr-replay: HIT`) does the same, for every
|
|
938
|
-
`ssr:
|
|
937
|
+
`ssr: "await"` loader, including one on an entry with `loading()` (before, a
|
|
939
938
|
navigation ran every loader and served its fresh values). A snapshot captured
|
|
940
939
|
by a navigation alone carries no pins, and its replay runs these loaders fresh.
|
|
941
940
|
|
|
@@ -955,7 +954,7 @@ by a navigation alone carries no pins, and its replay runs these loaders fresh.
|
|
|
955
954
|
a document HIT the payload then carries the error where the HTML has the
|
|
956
955
|
baked value, and React renders that part again on the client (one
|
|
957
956
|
hydration mismatch, the one known residual of a HIT). Give it its own cache,
|
|
958
|
-
`loader(Def, { ssr:
|
|
957
|
+
`loader(Def, { ssr: "await" }, () => [cache({ ttl })])`, so that run reads
|
|
959
958
|
through the loader cache.
|
|
960
959
|
|
|
961
960
|
Whether a return "has promises" is decided ONCE, at capture, by the shape of
|
|
@@ -972,20 +971,20 @@ its settled value, and a HIT serves that value without running the loader.
|
|
|
972
971
|
The granularity is the WHOLE loader: a bake-lane loader returning
|
|
973
972
|
`{ product, reviews: promise }` runs its entire body on every HIT, the product
|
|
974
973
|
fetch included, even though the captured product overlays its result. Split
|
|
975
|
-
it into a promise-free `ssr:
|
|
974
|
+
it into a promise-free `ssr: "await"` loader plus a live loader for the
|
|
976
975
|
per-request part, or give it its own `cache()` so that run reads through it:
|
|
977
976
|
|
|
978
977
|
```tsx
|
|
979
|
-
loader(ProductLoader, { ssr:
|
|
978
|
+
loader(ProductLoader, { ssr: "await" }), // promise-free: baked, not run on a HIT
|
|
980
979
|
loader(ReviewsLoader), // live: the hole, under loading()
|
|
981
980
|
```
|
|
982
981
|
|
|
983
982
|
**A hole never reads the shell's copy of a value.** The shell stores its
|
|
984
|
-
recorded segments and the `ssr:
|
|
983
|
+
recorded segments and the `ssr: "await"` loaders' baked containers, and no
|
|
985
984
|
`"use cache"` or loader `cache()` value. Every cache read on a HIT goes to
|
|
986
985
|
the store: a live loader's, a nested promise's, and the body of an
|
|
987
|
-
`ssr:
|
|
988
|
-
same `"use cache"` key as an `ssr:
|
|
986
|
+
`ssr: "await"` loader that runs on the HIT. So a live loader that reads the
|
|
987
|
+
same `"use cache"` key as an `ssr: "await"` loader shows the store's current
|
|
989
988
|
value once it refreshes, while the baked container keeps the capture's. If a
|
|
990
989
|
hole needs a stable value, give it its own cache (`"use cache"` with a
|
|
991
990
|
profile, or a loader `cache()`); the shell never provides one.
|
|
@@ -995,7 +994,7 @@ data.** A loader's title, meta or breadcrumb describes the data next to it,
|
|
|
995
994
|
so both come from the same run on every way a shell can be replayed: a
|
|
996
995
|
document HIT, a client navigation, a prefetch. The capture records the
|
|
997
996
|
settled, thenable-free handle pushes of every loader body it runs (the
|
|
998
|
-
prelude rendered them): the `ssr:
|
|
997
|
+
prelude rendered them): the `ssr: "await"` loader's own, those of the loaders
|
|
999
998
|
it awaits with `ctx.use()`, and those its own `cache()` entry replays. What
|
|
1000
999
|
a replay does with a loader's recorded pushes follows what it does with that
|
|
1001
1000
|
loader's data:
|
|
@@ -1014,12 +1013,12 @@ of the loaders it awaits, so they show its live value, even when a bake-lane
|
|
|
1014
1013
|
loader running on the replay awaits it. On a document HIT the page hydrates
|
|
1015
1014
|
with the shell's copy and the hole's value takes its place after hydration
|
|
1016
1015
|
("Handles on a shell HIT" below). An
|
|
1017
|
-
`ssr:
|
|
1016
|
+
`ssr: "await"` loader is served from the shell only while the entry holds its
|
|
1018
1017
|
pin: a snapshot captured by a navigation alone carries none, and a capture
|
|
1019
1018
|
over `ppr.maxSnapshotBytes` stores the navigation half only, without them.
|
|
1020
1019
|
There a navigation runs the loader fresh, and its pushes are that run's, like
|
|
1021
1020
|
its data. A document HIT is served only from an entry that holds a pin for
|
|
1022
|
-
every `ssr:
|
|
1021
|
+
every `ssr: "await"` loader its HTML baked, so a document never hydrates one
|
|
1023
1022
|
loader's run over HTML rendered from the capture's value.
|
|
1024
1023
|
|
|
1025
1024
|
A `Prerender` + `ppr` page follows the same table. Its handler layer comes
|
|
@@ -1027,7 +1026,7 @@ from the prerendered payload, which never holds a loader push (loaders do not
|
|
|
1027
1026
|
run at build), so the shell entry records the loader pushes its HTML rendered
|
|
1028
1027
|
on its own, and a HIT restores them as above (#1057).
|
|
1029
1028
|
|
|
1030
|
-
A dependency the route does not register, which an `ssr:
|
|
1029
|
+
A dependency the route does not register, which an `ssr: "await"` loader
|
|
1031
1030
|
awaited at capture outside any live loader, is on neither lane: its settled
|
|
1032
1031
|
pushes stay as the prelude rendered them, even where a live loader also
|
|
1033
1032
|
awaits it, while its data is fresh. (They follow the run instead when the
|
|
@@ -1041,7 +1040,7 @@ own `cache()` entry when it hits) with the run's value, on a document HIT and
|
|
|
1041
1040
|
on a client navigation alike. On a document HIT it arrives after hydration.
|
|
1042
1041
|
|
|
1043
1042
|
**Handles on a shell HIT.** The shell holds what its capture's document
|
|
1044
|
-
rendered: the handlers' pushes and the `ssr:
|
|
1043
|
+
rendered: the handlers' pushes and the `ssr: "await"` loaders' pushes, a
|
|
1045
1044
|
promise (`push(promise)`, `.defer()`) resolved. A pushed promise is judged by
|
|
1046
1045
|
the value it settles to, as `push(await promise)` would be (#1182). A
|
|
1047
1046
|
live-lane loader's push is never in a shell.
|
|
@@ -1049,10 +1048,10 @@ live-lane loader's push is never in a shell.
|
|
|
1049
1048
|
Four things follow, in this order of scope:
|
|
1050
1049
|
|
|
1051
1050
|
1. **What is in a shell's HTML.** The capture awaits a promise an
|
|
1052
|
-
`ssr:
|
|
1051
|
+
`ssr: "await"` loader (or a loader it awaits) pushes, under
|
|
1053
1052
|
`ppr.captureTimeout`, and bakes the value. One that settles to a value
|
|
1054
1053
|
holding a promise is live: the shell renders no element for it. A loader
|
|
1055
|
-
without `ssr:
|
|
1054
|
+
without `ssr: "await"` never runs at capture, so nothing of it is ever in
|
|
1056
1055
|
the shell.
|
|
1057
1056
|
2. **A pushed promise that fails refuses the capture.** One that rejects, or
|
|
1058
1057
|
does not settle within `ppr.captureTimeout`, keeps the route on MISS, as
|
|
@@ -1069,16 +1068,16 @@ Four things follow, in this order of scope:
|
|
|
1069
1068
|
inside the `loading()` or `<Suspense>` of a loader that pushes after an
|
|
1070
1069
|
`await` hydrates clean.
|
|
1071
1070
|
|
|
1072
|
-
| On the HIT's request
|
|
1073
|
-
|
|
|
1074
|
-
| a handler's push, a pinned `ssr:
|
|
1075
|
-
| a push by an `ssr:
|
|
1076
|
-
| a live-lane loader's push
|
|
1071
|
+
| On the HIT's request | In the HIT's HTML | The client |
|
|
1072
|
+
| -------------------------------------------------------------------------------------------- | ---------------------- | ----------------------------- |
|
|
1073
|
+
| a handler's push, a pinned `ssr: "await"` loader's push (`push(promise)` and `.defer()` too) | yes (the shell has it) | hydrates with it |
|
|
1074
|
+
| a push by an `ssr: "await"` loader whose value holds a promise | no | gets it right after hydration |
|
|
1075
|
+
| a live-lane loader's push | no | gets it right after hydration |
|
|
1077
1076
|
|
|
1078
|
-
So a title an `ssr:
|
|
1077
|
+
So a title an `ssr: "await"` loader pushes to `Meta`, promise or not, is in
|
|
1079
1078
|
every HIT's `<head>`: a crawler and a link preview see it. It bakes with the
|
|
1080
1079
|
shell and is invalidated with it (its TTL, its tags, `updateTag()`). A value
|
|
1081
|
-
that must stay per request comes from a loader without `ssr:
|
|
1080
|
+
that must stay per request comes from a loader without `ssr: "await"`, which
|
|
1082
1081
|
nothing has to read:
|
|
1083
1082
|
|
|
1084
1083
|
```ts
|
|
@@ -1087,7 +1086,7 @@ const StockNote = createLoader(async (ctx) => {
|
|
|
1087
1086
|
return null;
|
|
1088
1087
|
});
|
|
1089
1088
|
path("/product/:id", ProductPage, { name: "product", ppr: true }, () => [
|
|
1090
|
-
loader(Product, { ssr:
|
|
1089
|
+
loader(Product, { ssr: "await" }), // bakes: its data and its pushes
|
|
1091
1090
|
loader(StockNote), // live lane: runs on every request
|
|
1092
1091
|
]);
|
|
1093
1092
|
```
|
|
@@ -1128,8 +1127,8 @@ Four hard edges (each e2e/unit-pinned):
|
|
|
1128
1127
|
- **Identity refuses.** `cookies()`/`headers()`/`ctx.request.headers` inside a bake-lane loader
|
|
1129
1128
|
throws during capture and the capture REFUSES (deterministic, once-per-key
|
|
1130
1129
|
warned) — identity can never bake into the shared shell. Drop the loader's
|
|
1131
|
-
`ssr:
|
|
1132
|
-
identity-dependent part into a separate loader without `ssr:
|
|
1130
|
+
`ssr: "await"` flag (the live lane is exempt; give it a boundary) or move the
|
|
1131
|
+
identity-dependent part into a separate loader without `ssr: "await"`. A
|
|
1133
1132
|
nested promise does not help here: its body still runs during capture and
|
|
1134
1133
|
trips the guard. The same refusal covers everything else the capture waits
|
|
1135
1134
|
for (a handler, a promise it passes or pushes, an async server component, a
|
|
@@ -1155,7 +1154,7 @@ Four hard edges (each e2e/unit-pinned):
|
|
|
1155
1154
|
document GETs, and on soft navigations that replay a shell captured by a
|
|
1156
1155
|
document request (`x-rango-ppr-replay: HIT`). Live loaders and their
|
|
1157
1156
|
`"use cache"` reads remain fresh. That IS the bake lane's meaning; if a value
|
|
1158
|
-
must be fresh on every serve, it belongs on the live lane (no `ssr:
|
|
1157
|
+
must be fresh on every serve, it belongs on the live lane (no `ssr: "await"`) or
|
|
1159
1158
|
in a nested promise.
|
|
1160
1159
|
|
|
1161
1160
|
### `prefetch: false` on a `ppr` route
|
|
@@ -1167,11 +1166,11 @@ the fallback are deferred. A live loader registered with `{ prefetch: false }` i
|
|
|
1167
1166
|
skipped the same way, and the click that adopts the prefetch sends one fill
|
|
1168
1167
|
request for it.
|
|
1169
1168
|
|
|
1170
|
-
An `ssr:
|
|
1169
|
+
An `ssr: "await"` loader (the bake lane) registered with `{ prefetch: false }`
|
|
1171
1170
|
is deferred too, unless the prefetch serves it from a pin of a shell a
|
|
1172
1171
|
document request captured:
|
|
1173
1172
|
|
|
1174
|
-
| The prefetch | The flagged `ssr:
|
|
1173
|
+
| The prefetch | The flagged `ssr: "await"` loader |
|
|
1175
1174
|
| ------------------------------------------------------ | --------------------------------------------------------------------- |
|
|
1176
1175
|
| Replays a shell a document request captured (`HIT`) | Served from its pin, not deferred, never run; the click sends nothing |
|
|
1177
1176
|
| Replays a navigation-only shell, or misses (cold page) | Deferred: the click sends one fill that runs it |
|
|
@@ -1195,7 +1194,7 @@ refuses and the page stays an eternal MISS. The question is which parts of
|
|
|
1195
1194
|
their data should bake vs stay live. Your levers, in order of preference:
|
|
1196
1195
|
|
|
1197
1196
|
1. **Flag the loader and shape the return value.**
|
|
1198
|
-
`loader(SessionContextLoader, { ssr:
|
|
1197
|
+
`loader(SessionContextLoader, { ssr: "await" })` puts it on the bake lane:
|
|
1199
1198
|
shared/config data returns as plain values (bakes, pinned per shell);
|
|
1200
1199
|
per-request data returns as NESTED promises consumed under the widget's own
|
|
1201
1200
|
`<Suspense>` (live holes). No `loading()`, no restructuring. One wall: a
|
|
@@ -1238,7 +1237,7 @@ their data should bake vs stay live. Your levers, in order of preference:
|
|
|
1238
1237
|
]),
|
|
1239
1238
|
```
|
|
1240
1239
|
|
|
1241
|
-
Slot-owned loaders without `ssr:
|
|
1240
|
+
Slot-owned loaders without `ssr: "await"` are masked at capture and
|
|
1242
1241
|
GUARANTEED fresh per serve — use this where the bake lane's pinning
|
|
1243
1242
|
(capture-time data for the shell's lifetime) is not acceptable, at the cost
|
|
1244
1243
|
of a widget-sized fallback in the shell. The slot handler must hand the
|
|
@@ -1255,7 +1254,7 @@ their data should bake vs stay live. Your levers, in order of preference:
|
|
|
1255
1254
|
and the shell is tag-invalidatable.
|
|
1256
1255
|
|
|
1257
1256
|
The identity rule, stated once: per-user data on a PPR page lives in a
|
|
1258
|
-
live-lane loader (no `ssr:
|
|
1257
|
+
live-lane loader (no `ssr: "await"`) consumed CLIENT-side with `useLoader` under
|
|
1259
1258
|
`loading()` or an inline `<Suspense>`, or in a NESTED promise inside a
|
|
1260
1259
|
bake-lane loader (a hole, fresh per request). A nested promise's body still
|
|
1261
1260
|
runs at capture, so it must not call `cookies()`/`headers()` or read
|
|
@@ -1283,7 +1282,7 @@ and (in dev, past 2s) the bake-cost warning naming the source.
|
|
|
1283
1282
|
|
|
1284
1283
|
Three levers, in preference order:
|
|
1285
1284
|
|
|
1286
|
-
1. **Make it live**: move the value into a live-lane loader (no `ssr:
|
|
1285
|
+
1. **Make it live**: move the value into a live-lane loader (no `ssr: "await"`)
|
|
1287
1286
|
read with `useLoader` under `loading()` or an inline `<Suspense>` — a hole,
|
|
1288
1287
|
per request, zero capture cost. Inside a bake-lane loader, return the slow
|
|
1289
1288
|
part as a nested promise instead. Nesting a promise inside a HANDLER's push
|
|
@@ -1292,7 +1291,7 @@ Three levers, in preference order:
|
|
|
1292
1291
|
tag-invalidatable), but the capture replays the cached value instead of
|
|
1293
1292
|
re-executing the expensive body (mixed-chain). Choose this for shared,
|
|
1294
1293
|
expensive-to-compute shell content.
|
|
1295
|
-
3. **Drop `ssr:
|
|
1294
|
+
3. **Drop `ssr: "await"`** (loaders only): moves the loader to the live lane —
|
|
1296
1295
|
masked at capture, fresh per serve — at the cost of a fallback in the shell
|
|
1297
1296
|
(a live loader needs `loading()` or an inline `<Suspense>` above its
|
|
1298
1297
|
reader).
|
|
@@ -1304,10 +1303,10 @@ actually paid. A
|
|
|
1304
1303
|
Meta pushed by a LIVE-lane loader is different: those loaders are masked at
|
|
1305
1304
|
capture, so the push happens at request time and applies client-side
|
|
1306
1305
|
(`metadata.handlesLate`) — it is never in the cached shell's head, by
|
|
1307
|
-
construction. A bake-lane (`ssr:
|
|
1306
|
+
construction. A bake-lane (`ssr: "await"`) loader executes at capture, so its
|
|
1308
1307
|
settled pushes are shell material like a handler's.
|
|
1309
1308
|
|
|
1310
|
-
`loader(Def, { ssr:
|
|
1309
|
+
`loader(Def, { ssr: "await" })` (the document-render await, `/loader`) is the
|
|
1311
1310
|
bake-lane switch under PPR, not a knob for normal renders only: the capture
|
|
1312
1311
|
render awaits the flagged loader too, so its settled non-promise data — handle
|
|
1313
1312
|
pushes included — freezes into the stored shell and recurs as capture cost on
|
|
@@ -1326,7 +1325,7 @@ replay runs it fresh.
|
|
|
1326
1325
|
| Middleware chain | runs (full) | **NOT re-run** — inherits the request's post-middleware context | runs (full) — commit point is after it |
|
|
1327
1326
|
| `router.match` | runs | re-runs under a derived context | runs (behind the flushed prelude) |
|
|
1328
1327
|
| Handlers | run | run on UNCACHED segments (`cache()`d segments replay); everything they produce — promises, async server components, handle pushes, awaited loaders — settles before the freeze (`ppr.captureTimeout`) | **never run** — the handler layer is replayed from the shell's recorded segments |
|
|
1329
|
-
| Loaders | run **fresh** | LIVE lane (no `ssr:
|
|
1328
|
+
| Loaders | run **fresh** | LIVE lane (no `ssr: "await"`): MASKED; BAKE lane (`ssr: "await"`): execute + snapshot-pin | live: run **fresh**; bake: pinned, runs only if its record has holes |
|
|
1330
1329
|
| Flight render | full | full, from the capture's own recorded segments (the same bytes every HIT replays) | full (hydration needs the whole payload — no Flight resume) |
|
|
1331
1330
|
| HTML production | full fizz | `prerender` + abort → prelude + postponed | `resume` only the holes — O(paths to holes) |
|
|
1332
1331
|
| Shell store | schedules a bg capture | `putShell(key, …)` | `getShell(key)`; a stale/SWR hit also schedules a recapture |
|
|
@@ -1345,8 +1344,8 @@ load-bearing: `cookies()`, `headers()`, `ctx.request.headers`,
|
|
|
1345
1344
|
`{ cache: false }` variable read THROW during a capture render, and the capture
|
|
1346
1345
|
is refused even when the code catches that throw, so identity can never leak
|
|
1347
1346
|
into a shared shell through them. Live-lane loaders (every loader without
|
|
1348
|
-
`ssr:
|
|
1349
|
-
capture, they never run there. One a handler or an `ssr:
|
|
1347
|
+
`ssr: "await"`) are exempt when only a client component reads them: masked at
|
|
1348
|
+
capture, they never run there. One a handler or an `ssr: "await"` loader awaits
|
|
1350
1349
|
runs at capture, and its identity reads refuse it.
|
|
1351
1350
|
|
|
1352
1351
|
## allReady: the SEO/bot story
|
|
@@ -1376,8 +1375,8 @@ a handler, a promise it passes or pushes, an async server component, a
|
|
|
1376
1375
|
bake-lane loader, and a loader a handler awaits (`await ctx.use(Loader)`). The
|
|
1377
1376
|
capture is refused even if your code catches the throw, and the route keeps
|
|
1378
1377
|
serving MISSes with a once-per-key warning. `ctx.dynamic()` called there
|
|
1379
|
-
refuses it too. The live lane (no `ssr:
|
|
1380
|
-
component reads it; one a handler or an `ssr:
|
|
1378
|
+
refuses it too. The live lane (no `ssr: "await"`) stays exempt when only a client
|
|
1379
|
+
component reads it; one a handler or an `ssr: "await"` loader awaits runs at
|
|
1381
1380
|
capture, and its identity reads refuse it.
|
|
1382
1381
|
|
|
1383
1382
|
**(c) Residual hazard — middleware-derived per-user state.** A NORMAL `ctx`
|
|
@@ -1390,7 +1389,7 @@ middleware-provided session object (`ctx.get("session")`) never calls
|
|
|
1390
1389
|
`cookies()` itself, so whatever it returns as settled container data is
|
|
1391
1390
|
photographed as the CAPTURING user's state. If the value is per-user:
|
|
1392
1391
|
shell-cache only public/shared pages, keep per-user content in live-lane
|
|
1393
|
-
loaders (no `ssr:
|
|
1392
|
+
loaders (no `ssr: "await"`) or nested promises in bake-lane loader data — NOT in
|
|
1394
1393
|
handler output or plain bake-lane container data — mark the variable
|
|
1395
1394
|
`{ cache: false }` so a stray shell read refuses the capture, or key per
|
|
1396
1395
|
variant at the CDN tier. A variable a middleware sets before `throw rewrite()`
|
|
@@ -1456,13 +1455,13 @@ path(
|
|
|
1456
1455
|
);
|
|
1457
1456
|
```
|
|
1458
1457
|
|
|
1459
|
-
| Field | Default | Notes
|
|
1460
|
-
| ------------------ | ------- |
|
|
1461
|
-
| `ttl` | `300` | shell freshness window in seconds (`ppr: true` uses the default)
|
|
1462
|
-
| `swr` | — | stale window: serve the stale shell + background recapture
|
|
1463
|
-
| `tags` | — | operational tags UNIONED with the tags the capture collects — see "Invalidation" below
|
|
1464
|
-
| `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:
|
|
1465
|
-
| `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
|
|
1458
|
+
| Field | Default | Notes |
|
|
1459
|
+
| ------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1460
|
+
| `ttl` | `300` | shell freshness window in seconds (`ppr: true` uses the default) |
|
|
1461
|
+
| `swr` | — | stale window: serve the stale shell + background recapture |
|
|
1462
|
+
| `tags` | — | operational tags UNIONED with the tags the capture collects — see "Invalidation" below |
|
|
1463
|
+
| `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: "await"` 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: "await" data" |
|
|
1464
|
+
| `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 |
|
|
1466
1465
|
|
|
1467
1466
|
A shell never outlives the route `cache()` entry it was captured from: under
|
|
1468
1467
|
a route `cache()`, the shell is fresh no longer than that entry is (at most
|
|
@@ -1511,8 +1510,8 @@ whether from a `"use cache"` function, a `cache()` segment, or a
|
|
|
1511
1510
|
render-callable `cacheTag()` in a plain server component (no
|
|
1512
1511
|
`"use cache"`/`cache()` in its tree) — plus the tags of loaders on the BAKE
|
|
1513
1512
|
lane (they execute during capture and their data is in the shell). A tag
|
|
1514
|
-
recorded outside what the shell renders is not on it. A live-lane loader's tags (every loader without `ssr:
|
|
1515
|
-
attach only when a handler or an `ssr:
|
|
1513
|
+
recorded outside what the shell renders is not on it. A live-lane loader's tags (every loader without `ssr: "await"`)
|
|
1514
|
+
attach only when a handler or an `ssr: "await"` loader consumes its value with
|
|
1516
1515
|
`ctx.use()`, which bakes it; one read only by the client stays off, since it is masked at capture and
|
|
1517
1516
|
its hole is already live. `ppr.tags` adds
|
|
1518
1517
|
operational tags the render cannot know (a tenant id, a deploy marker).
|
|
@@ -1537,18 +1536,18 @@ the holes and streams a fresh payload, but does NOT evict the server shell.
|
|
|
1537
1536
|
Shell-baked data stays stale until the shell's TTL unless you tag-invalidate it
|
|
1538
1537
|
(`updateTag` on a shell tag). Data baked into the shell WITHOUT a tag cannot be
|
|
1539
1538
|
evicted by tag at all — move always-fresh data into a live-lane loader (no
|
|
1540
|
-
`ssr:
|
|
1539
|
+
`ssr: "await"`) or a nested promise in bake-lane loader data.
|
|
1541
1540
|
|
|
1542
1541
|
## Pitfalls
|
|
1543
1542
|
|
|
1544
1543
|
- **A bake-lane loader that reads `cookies()`/`headers()`/`ctx.request.headers`**: the capture is
|
|
1545
1544
|
REFUSED (deterministic, once-per-key warned) — the route keeps serving
|
|
1546
|
-
MISSes. Move identity onto the live lane (drop `ssr:
|
|
1545
|
+
MISSes. Move identity onto the live lane (drop `ssr: "await"`, or split the read into
|
|
1547
1546
|
a separate unflagged loader; give the reader a boundary). A nested promise
|
|
1548
1547
|
does not help here: its body still runs during capture.
|
|
1549
1548
|
- **A bake-lane container that must be fresh per document GET**: it is
|
|
1550
1549
|
snapshot-pinned for the shell's lifetime by design. Use the live lane
|
|
1551
|
-
(no `ssr:
|
|
1550
|
+
(no `ssr: "await"`) or a nested promise instead.
|
|
1552
1551
|
- **Handler output or a bake-lane loader slower than `ppr.captureTimeout`
|
|
1553
1552
|
(15s by default)**: the capture stores nothing rather than a partial shell
|
|
1554
1553
|
(the warning says the output "did not settle within ppr.captureTimeout").
|
|
@@ -1569,7 +1568,7 @@ evicted by tag at all — move always-fresh data into a live-lane loader (no
|
|
|
1569
1568
|
- **Per-user value in shell material**: baked into the shared shell —
|
|
1570
1569
|
deterministically, not by race (handler output, promises included, is
|
|
1571
1570
|
awaited at capture; awaited/resolved values bake everywhere). Put per-user
|
|
1572
|
-
data in a live-lane loader (no `ssr:
|
|
1571
|
+
data in a live-lane loader (no `ssr: "await"`) or a nested promise in
|
|
1573
1572
|
bake-lane loader data — plain BAKE-lane loader data bakes just like handler
|
|
1574
1573
|
material.
|
|
1575
1574
|
- **The session-object bake trap (the guard cannot save you here)**: the
|
|
@@ -1594,7 +1593,7 @@ evicted by tag at all — move always-fresh data into a live-lane loader (no
|
|
|
1594
1593
|
`return { user: session.user }` bakes it into the shared shell like any
|
|
1595
1594
|
other settled value — deterministically, not by race. Wrap it in a promise
|
|
1596
1595
|
(even an already-resolved one) or put the loader on the live lane (drop
|
|
1597
|
-
`ssr:
|
|
1596
|
+
`ssr: "await"`).
|
|
1598
1597
|
|
|
1599
1598
|
- **Theme on a HIT is default-then-corrected**: the shell's `initialTheme` is
|
|
1600
1599
|
the no-cookie default (`defaultTheme`), whoever captured it, and the resume
|
|
@@ -1612,7 +1611,7 @@ evicted by tag at all — move always-fresh data into a live-lane loader (no
|
|
|
1612
1611
|
the capture read it also rides the shell, so `updateTag` on that tag drops the
|
|
1613
1612
|
shell too (next request MISSes and recaptures). An untagged entry has no such
|
|
1614
1613
|
link. If a shell region needs to be fresh, put it under a hole — a live-lane
|
|
1615
|
-
loader (no `ssr:
|
|
1614
|
+
loader (no `ssr: "await"`) behind `loading()` or an inline `<Suspense>`, or a
|
|
1616
1615
|
nested promise in bake-lane loader data (holes are never pinned) — or make
|
|
1617
1616
|
the SHELL itself invalidatable by tagging
|
|
1618
1617
|
it: tag the cached read, call `cacheTag(...)` from the shell-material render
|
|
@@ -1628,7 +1627,7 @@ evicted by tag at all — move always-fresh data into a live-lane loader (no
|
|
|
1628
1627
|
cached under its own profile; once the entry expires or is invalidated and
|
|
1629
1628
|
refreshes, the hole shows the refreshed value next to the shell's old one. The
|
|
1630
1629
|
shell stores no `"use cache"` value, only its recorded handler output and the
|
|
1631
|
-
`ssr:
|
|
1630
|
+
`ssr: "await"` loaders' baked containers. A hole reads the store even when a
|
|
1632
1631
|
bake-lane loader read the same entry at capture: the baked container keeps
|
|
1633
1632
|
the capture's value, and the hole shows the current entry.
|
|
1634
1633
|
- **Uncached nondeterminism in server output is frozen, not drifting**: a raw
|
|
@@ -1652,7 +1651,7 @@ evicted by tag at all — move always-fresh data into a live-lane loader (no
|
|
|
1652
1651
|
- **HIT status is committed at the flush**: a failing hole cannot become a
|
|
1653
1652
|
500/redirect after the first shell byte — error UI renders inline via
|
|
1654
1653
|
Suspense/error boundaries (the same property any streamed SSR page has after
|
|
1655
|
-
its shell flushes). A MISS has sent nothing yet: an `ssr:
|
|
1654
|
+
its shell flushes). A MISS has sent nothing yet: an `ssr: "await"` loader that
|
|
1656
1655
|
throws answers 500 there with its route's `errorBoundary()` (or the default
|
|
1657
1656
|
one), and the capture stores nothing.
|
|
1658
1657
|
- **A shell component that throws during capture refuses it**: an async
|