@rangojs/router 0.34.1 → 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.
Files changed (95) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/{chunk-4HHFQSDC.js → chunk-3CQXDX3A.js} +5 -5
  3. package/dist/bin/{client-urls-projection-4Q5EQDAL.js → client-urls-projection-2GBDM5YK.js} +1 -1
  4. package/dist/bin/rango.js +1 -1
  5. package/dist/bin/{runtime-discovery-RY6NMQM3.js → runtime-discovery-PA3V3IFQ.js} +2 -2
  6. package/dist/bin/{temp-rsc-server-F7AHOPV4.js → temp-rsc-server-WDXEHDY7.js} +2 -2
  7. package/dist/types/cache/shell-snapshot.d.ts +1 -1
  8. package/dist/types/cache/types.d.ts +1 -1
  9. package/dist/types/client-urls/server-projection.d.ts +1 -1
  10. package/dist/types/client-urls/types.d.ts +6 -6
  11. package/dist/types/decode-loader-results.d.ts +1 -1
  12. package/dist/types/errors.d.ts +3 -3
  13. package/dist/types/handles/meta.d.ts +2 -2
  14. package/dist/types/loader-store.d.ts +1 -1
  15. package/dist/types/outlet-context.d.ts +1 -1
  16. package/dist/types/route-definition/helpers-types.d.ts +5 -5
  17. package/dist/types/router/loader-resolution.d.ts +1 -1
  18. package/dist/types/router/segment-resolution/loader-cache.d.ts +7 -7
  19. package/dist/types/router/types.d.ts +1 -1
  20. package/dist/types/rsc/shell-capture-constants.d.ts +2 -2
  21. package/dist/types/server/context.d.ts +3 -3
  22. package/dist/types/server/request-context.d.ts +1 -1
  23. package/dist/types/ssr/index.d.ts +1 -1
  24. package/dist/types/ssr/ssr-root.d.ts +1 -1
  25. package/dist/types/ssr-suspension-warning.d.ts +1 -1
  26. package/dist/types/types/loader-types.d.ts +19 -19
  27. package/dist/types/types/segments.d.ts +2 -2
  28. package/dist/types/urls/path-helper-types.d.ts +4 -4
  29. package/dist/types/urls/pattern-types.d.ts +1 -1
  30. package/dist/types/vite/plugin-types.d.ts +1 -1
  31. package/dist/vite/index.js +7 -7
  32. package/package.json +1 -1
  33. package/skills/app-structure/e-commerce.md +1 -1
  34. package/skills/breadcrumbs/SKILL.md +5 -5
  35. package/skills/cache-guide/SKILL.md +4 -4
  36. package/skills/caching/SKILL.md +1 -1
  37. package/skills/catalog.json +2 -2
  38. package/skills/client-urls/SKILL.md +20 -20
  39. package/skills/comparison/references/framework-comparison.md +6 -6
  40. package/skills/defer-hydration/SKILL.md +2 -2
  41. package/skills/handler-use/SKILL.md +5 -5
  42. package/skills/hooks/data.md +1 -1
  43. package/skills/hooks/handle-and-actions.md +9 -9
  44. package/skills/loader/SKILL.md +52 -47
  45. package/skills/middleware/SKILL.md +2 -2
  46. package/skills/migrate-nextjs/SKILL.md +10 -10
  47. package/skills/migrate-react-router/SKILL.md +1 -1
  48. package/skills/migrate-react-router/component-migration.md +2 -2
  49. package/skills/migrate-react-router/data-and-actions.md +2 -2
  50. package/skills/migrate-react-router/route-mapping.md +2 -2
  51. package/skills/parallel/SKILL.md +2 -2
  52. package/skills/ppr/SKILL.md +91 -92
  53. package/skills/prerender/SKILL.md +5 -5
  54. package/skills/rango/SKILL.md +7 -7
  55. package/skills/route/SKILL.md +1 -1
  56. package/skills/router-setup/SKILL.md +2 -2
  57. package/skills/scripts/SKILL.md +3 -3
  58. package/skills/shell-manifest/SKILL.md +3 -3
  59. package/skills/testing/serve-shell-request.md +1 -1
  60. package/skills/theme/SKILL.md +2 -2
  61. package/skills/typesafety/env-and-bindings.md +2 -2
  62. package/skills/use-cache/SKILL.md +1 -1
  63. package/skills/ux-architecture/SKILL.md +7 -7
  64. package/src/cache/shell-snapshot.ts +1 -1
  65. package/src/cache/types.ts +1 -1
  66. package/src/client-urls/client-urls.ts +16 -9
  67. package/src/client-urls/server-projection.ts +4 -4
  68. package/src/client-urls/types.ts +6 -6
  69. package/src/decode-loader-results.ts +1 -1
  70. package/src/errors.ts +9 -9
  71. package/src/handles/meta.ts +2 -2
  72. package/src/loader-store.ts +1 -1
  73. package/src/outlet-context.ts +1 -1
  74. package/src/route-definition/dsl-helpers.ts +4 -8
  75. package/src/route-definition/helpers-types.ts +5 -5
  76. package/src/router/loader-resolution.ts +4 -4
  77. package/src/router/segment-resolution/loader-cache.ts +8 -8
  78. package/src/router/segment-resolution/prefetch-deferral.ts +1 -1
  79. package/src/router/segment-resolution/revalidation.ts +3 -3
  80. package/src/router/types.ts +1 -1
  81. package/src/rsc/shell-capture-constants.ts +3 -3
  82. package/src/rsc/shell-capture.ts +12 -12
  83. package/src/segment-system.tsx +3 -3
  84. package/src/server/context.ts +5 -5
  85. package/src/server/cookie-store.ts +1 -1
  86. package/src/server/request-context.ts +1 -1
  87. package/src/ssr/index.tsx +6 -6
  88. package/src/ssr/ssr-root.tsx +1 -1
  89. package/src/ssr-suspension-warning.ts +4 -4
  90. package/src/types/loader-types.ts +19 -19
  91. package/src/types/segments.ts +2 -2
  92. package/src/urls/path-helper-types.ts +4 -4
  93. package/src/urls/pattern-types.ts +1 -1
  94. package/src/vite/discovery/shell-prerender-phase.ts +1 -1
  95. package/src/vite/plugin-types.ts +1 -1
@@ -102,11 +102,11 @@ Two pushes from one segment accumulate in push order (`Shop › product`).
102
102
  Delivery follows the loader race model: a push that beats the handler barrier
103
103
  is in the SSR'd document; a push after a slow fetch streams and applies
104
104
  client-side (`useHandle` re-renders when it lands). To guarantee document
105
- delivery, register the loader as `loader(Def, { ssr: false })` —
105
+ delivery, register the loader as `loader(Def, { ssr: "await" })` —
106
106
  see `/loader` → "Writing Handles from Loaders". On a `ppr` route the race is
107
- decided at capture: a loader without `ssr: false` never runs there, so its
107
+ decided at capture: a loader without `ssr: "await"` never runs there, so its
108
108
  crumb is not in the shell and applies client-side on every HIT; with
109
- `ssr: false` the crumb bakes into the shell (`/ppr` → "On a shell HIT").
109
+ `ssr: "await"` the crumb bakes into the shell (`/ppr` → "On a shell HIT").
110
110
  Under `cache()` loader crumbs are not stored with the cached segments; the
111
111
  loader re-pushes them on every hit, so they appear once even without an `href`.
112
112
  A loader with its own `cache()` keeps its crumbs on its own hits too: they are
@@ -147,7 +147,7 @@ in a `ppr` shell it is stored resolved: the cache write, or the shell capture
147
147
  (within `ppr.captureTimeout`), waits for a `content` promise and for a
148
148
  `.defer()` slot (below), and every HIT replays the crumb as it was resolved
149
149
  then, without running the handler. For per-request crumb content, push it
150
- from a loader without `ssr: false` (see "Pushing from Loaders").
150
+ from a loader without `ssr: "await"` (see "Pushing from Loaders").
151
151
 
152
152
  ### Deferred content (decide now, resolve from a deep component)
153
153
 
@@ -211,7 +211,7 @@ swap in — no blank, no pending entry. The hold-then-swap is deliberate: a hand
211
211
  undefined, the entry is simply dropped. Use `.defer()` only when even
212
212
  `label`/`href` are unknown at handler time AND the producer is a component,
213
213
  not a loader — a loader-produced crumb pushes directly from the loader body
214
- (add `{ ssr: false }` if it must be in the SSR'd document), and if
214
+ (add `{ ssr: "await" }` if it must be in the SSR'd document), and if
215
215
  you know `label`/`href` and only the `content` is async, push a concrete item
216
216
  with a `Promise` `content` field instead (the `content` field is a nested
217
217
  promise you resolve with `use()` in your component; no `.defer()` needed).
@@ -38,7 +38,7 @@ Quick rules:
38
38
  CDN caching (`/deployment-caching`).
39
39
  - A value that must be fresh on **every** request → a loader (never cached
40
40
  unless you opt in), read with `useLoader()`; on a `ppr` route, keep it off
41
- `ssr: false`. A handler inside a `cache()` or `ppr` boundary that awaits
41
+ `ssr: "await"`. A handler inside a `cache()` or `ppr` boundary that awaits
42
42
  `ctx.use(Loader)` renders the value it got on the miss, and every hit
43
43
  replays that output.
44
44
 
@@ -161,7 +161,7 @@ The `ppr` shell capture has its own, stricter guard: `cookies()`, `headers()`,
161
161
  `ctx.request.headers`, a theme read, a `{ cache: false }` variable read, and
162
162
  `ctx.dynamic()` refuse the
163
163
  capture anywhere it waits — handlers, promises they pass or push, async server
164
- components, `ssr: false` loaders, and loaders a handler awaits (no loader
164
+ components, `ssr: "await"` loaders, and loaders a handler awaits (no loader
165
165
  exemption there). The route then serves uncached; see `/ppr`.
166
166
 
167
167
  ### Cross-deploy safety: version-segmented store keys
@@ -490,7 +490,7 @@ So `"use cache: short"` (60s) inside `cache({ ttl: 600 })` yields ~600s freshnes
490
490
  on hits, **not** 60s. This is not a bug: setting `cache({ ttl: 600 })` declares
491
491
  "this subtree may be ~600s stale." **If a value must be fresher than its
492
492
  enclosing segment, put it in a loader** read with `useLoader()` (without
493
- `ssr: false` on a `ppr` route). `debugPerformance` prints cache hits per
493
+ `ssr: "await"` on a `ppr` route). `debugPerformance` prints cache hits per
494
494
  layer, so the actual per-request behavior is observable.
495
495
 
496
496
  Keys and tags cross the nesting too:
@@ -643,7 +643,7 @@ This means `cache()` gives you cached UI + fresh data by default, where the
643
643
  data is read outside the cached output: `useLoader()` in a client component.
644
644
  A handler inside the boundary that awaits `ctx.use(Loader)` renders the value
645
645
  it got on the miss, and every hit replays that output. On a `ppr` route an
646
- `ssr: false` loader is the other exception: it bakes into the shell (`/ppr`).
646
+ `ssr: "await"` loader is the other exception: it bakes into the shell (`/ppr`).
647
647
  To also cache a loader's data, explicitly opt in with
648
648
  `loader(Fn, () => [cache({...})])`.
649
649
  That entry is keyed by loader, host, path and params, not by the route's
@@ -707,7 +707,7 @@ times out, or whose snapshot is truncated or corrupt (also evicted), cannot
707
707
  replay the captured segment record, so it degrades: the entry is replaced and
708
708
  recaptured and the page reloads once into a cache-miss render (`/ppr`). The
709
709
  snapshot holds only what a HIT reads: every HIT replays the handler layer from
710
- the captured segment record and serves an `ssr: false` loader's baked
710
+ the captured segment record and serves an `ssr: "await"` loader's baked
711
711
  container, so no `"use cache"` or loader `cache()` item is stored in it. A
712
712
  live hole, and any loader that runs on the HIT, reads those from the store
713
713
  under their own ttl/swr and tags.
@@ -41,7 +41,7 @@
41
41
  },
42
42
  {
43
43
  "name": "client-urls",
44
- "description": "Define client-component route groups with clientUrls() in a \"use client\" module — no handlers, useLoader at read sites, server-run revalidate predicates, loader-thrown notFound/redirect, loader handle writes, and the ssr:false SSR-completeness opt-in. Use when a route group's pages are client components, when building high-navigation-speed UIs (dashboards, admin panels, settings) where transitions must feel instant, when moving data reads from handler ctx.use() to useLoader at consumption sites, or when asking how routes defined in a \"use client\" file work.",
44
+ "description": "Define client-component route groups with clientUrls() in a \"use client\" module — no handlers, useLoader at read sites, server-run revalidate predicates, loader-thrown notFound/redirect, loader handle writes, and the ssr:\"await\" SSR-completeness opt-in. Use when a route group's pages are client components, when building high-navigation-speed UIs (dashboards, admin panels, settings) where transitions must feel instant, when moving data reads from handler ctx.use() to useLoader at consumption sites, or when asking how routes defined in a \"use client\" file work.",
45
45
  "argumentHint": "[setup]",
46
46
  "path": "skills/client-urls/SKILL.md"
47
47
  },
@@ -143,7 +143,7 @@
143
143
  },
144
144
  {
145
145
  "name": "loader",
146
- "description": "Define data loaders with createLoader and register them on routes with loader(). Use when a route needs per-request data that stays fresh and streams while the page renders, a client component reads server data with useLoader, a loader should re-run after specific actions (revalidate), be cached (cache), be callable from the client (fetchable loaders), throw notFound()/redirect(), write page meta/breadcrumbs handles, must be settled in the SSR'd document (ssr:false), or must stay out of link prefetches (prefetch:false).",
146
+ "description": "Define data loaders with createLoader and register them on routes with loader(). Use when a route needs per-request data that stays fresh and streams while the page renders, a client component reads server data with useLoader, a loader should re-run after specific actions (revalidate), be cached (cache), be callable from the client (fetchable loaders), throw notFound()/redirect(), write page meta/breadcrumbs handles, must be settled in the SSR'd document (ssr:\"await\"), or must stay out of link prefetches (prefetch:false).",
147
147
  "argumentHint": "[loader]",
148
148
  "path": "skills/loader/SKILL.md"
149
149
  },
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: client-urls
3
- description: Define client-component route groups with clientUrls() in a "use client" module — no handlers, useLoader at read sites, server-run revalidate predicates, loader-thrown notFound/redirect, loader handle writes, and the ssr:false SSR-completeness opt-in. Use when a route group's pages are client components, when building high-navigation-speed UIs (dashboards, admin panels, settings) where transitions must feel instant, when moving data reads from handler ctx.use() to useLoader at consumption sites, or when asking how routes defined in a "use client" file work.
3
+ description: Define client-component route groups with clientUrls() in a "use client" module — no handlers, useLoader at read sites, server-run revalidate predicates, loader-thrown notFound/redirect, loader handle writes, and the ssr:"await" SSR-completeness opt-in. Use when a route group's pages are client components, when building high-navigation-speed UIs (dashboards, admin panels, settings) where transitions must feel instant, when moving data reads from handler ctx.use() to useLoader at consumption sites, or when asking how routes defined in a "use client" file work.
4
4
  argument-hint: "[setup]"
5
5
  ---
6
6
 
@@ -132,7 +132,7 @@ flow into the generated route map, so `href()`, `ctx.reverse()` and
132
132
  | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
133
133
  | `path()` | Options are `name`, `search`, `trailingSlash`, `ppr` (shell caching — see `/ppr` → The loader lane rule); no response variants. Use callback may contain `loader()`, `loading()`, `transition()` |
134
134
  | `layout()` | `layout(Component, () => [...])` — children are required and must contain at least one `path()`; may also hold `loader()`, `loading()`, `intercept()`, nested `layout()` |
135
- | `loader()` | `loader(Def, use?)` or `loader(Def, { ssr: false, prefetch: false \| "intent" }, use?)` — see below |
135
+ | `loader()` | `loader(Def, use?)` or `loader(Def, { ssr: "await", prefetch: false \| "intent" }, use?)` — see below |
136
136
  | `loading()` | Route-level boundary around the optimistic render; inline `<Suspense>` at read sites keeps the destination's chrome visible while only the reads wait |
137
137
  | `revalidate()` | Valid **inside a loader() use callback only**; runs on the server (the build hoists it out of the `"use client"` module) |
138
138
  | `transition()` | Inside a `path()` use callback only (at most one). ViewTransition config plus a browser-run `when` predicate (see `/view-transitions`); same-route navs in a group already hold without it |
@@ -185,11 +185,11 @@ export default clientUrls(({ path }) => [
185
185
  ]);
186
186
  ```
187
187
 
188
- | Component mounted by | Its `.use` may return |
189
- | -------------------- | ---------------------------------------------- |
190
- | `path()` | `loader()`, `loading()`, `transition()` |
191
- | `layout()` | `loader()`, `loading()` |
192
- | `intercept()` | `loader()` (without `ssr: false`), `loading()` |
188
+ | Component mounted by | Its `.use` may return |
189
+ | -------------------- | ------------------------------------------------ |
190
+ | `path()` | `loader()`, `loading()`, `transition()` |
191
+ | `layout()` | `loader()`, `loading()` |
192
+ | `intercept()` | `loader()` (without `ssr: "await"`), `loading()` |
193
193
 
194
194
  - **Merge order is the server's.** The component's items come first and the
195
195
  mount site's own items second: an explicit `loading()` or `transition()`
@@ -511,7 +511,7 @@ The next action or navigation that reruns the loader replaces the message.
511
511
 
512
512
  A failure returned as data is an ordinary value, so whatever stores the
513
513
  loader's value stores it too: a `cache()` on the loader serves it for its TTL,
514
- a `ppr` shell bakes an `ssr: false` loader's value, and a prerender stores it
514
+ a `ppr` shell bakes an `ssr: "await"` loader's value, and a prerender stores it
515
515
  at build time. A thrown error is never stored (a stale entry keeps serving).
516
516
  To cache the expensive part, cache the call the loader makes (`"use cache"` on
517
517
  that function: a call that throws stores nothing) and turn the throw into
@@ -545,7 +545,7 @@ export const ProductLoader = createLoader(async (ctx) => {
545
545
  On a document load, `notFound()` streams the resolved not-found UI in the
546
546
  envelope and _opportunistically_ sets a real 404 status — the status write only
547
547
  wins if the rejection settles before the document Response is constructed (a
548
- fast, pre-fetch existence check usually wins; see `ssr: false` below
548
+ fast, pre-fetch existence check usually wins; see `ssr: "await"` below
549
549
  for the deterministic version). On navigations the 404 UI swaps in with the
550
550
  URL preserved. A loader `redirect()` is always a client-side navigate — there
551
551
  is no document-lane 302 from loaders; pre-stream redirect authority belongs to
@@ -557,10 +557,10 @@ push. Reads moved to `ctx.get(handle)`, gated behind `await ctx.rendered()`.
557
557
  Delivery follows the race model: pushes that settle before the handler barrier
558
558
  ride the SSR handle snapshot; later ones stream and apply post-hydration
559
559
  (document loads) or progressively (navigations). On a `ppr` shell HIT a loader
560
- without `ssr: false` never ran during the capture, so its pushes are never in
560
+ without `ssr: "await"` never ran during the capture, so its pushes are never in
561
561
  the shell and always apply client-side. See `/loader` for the full contract.
562
562
 
563
- ## `ssr: false` — the SSR-completeness opt-in
563
+ ## `ssr: "await"` — the SSR-completeness opt-in
564
564
 
565
565
  By default every loader streams on every render, so nothing a slow loader
566
566
  produces is _guaranteed_ to be in the SSR'd HTML — data SSRs as the Suspense
@@ -570,7 +570,7 @@ document — `<head>` meta via a handle, or a real 404 status — flag it:
570
570
 
571
571
  ```ts
572
572
  path("/product/:slug", ProductPage, { name: "product" }, () => [
573
- loader(ProductLoader, { ssr: false }, () => [
573
+ loader(ProductLoader, { ssr: "await" }, () => [
574
574
  revalidate(({ currentParams, nextParams, defaultShouldRevalidate }) =>
575
575
  currentParams.slug !== nextParams.slug ? defaultShouldRevalidate : false,
576
576
  ),
@@ -579,9 +579,9 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
579
579
  ]),
580
580
  ```
581
581
 
582
- The knob mirrors `loading(fallback, { ssr: false })` — SSR delivery is off
583
- for this loader, so nothing of it is left to stream in the document: document
584
- renders await this loader before first flush (data settled — no fallback
582
+ The value says what the server render does with the loader: `"stream"` (the
583
+ default) streams it, `"await"` awaits it. Document renders await a flagged
584
+ loader before first flush (data settled — no fallback
585
585
  paints; handle pushes beat the barrier snapshot; a thrown `notFound()` is a
586
586
  deterministic real 404, no warm-up race); client navigations stream exactly as
587
587
  before. Scoped per loader — the flagged loader awaits only itself, siblings
@@ -613,12 +613,12 @@ Pinned end to end by dev + production e2e in the router repository.
613
613
  ships its fallback and swaps the content in with a script, which a browser
614
614
  without JavaScript never runs. That includes a client component a loader's
615
615
  answer draws under an inline `<Suspense>`: a fast loader's answer usually
616
- makes the first flush, and `ssr: false` guarantees the loader's data does. It
616
+ makes the first flush, and `ssr: "await"` guarantees the loader's data does. It
617
617
  does not cover the client component's module: the first document a build's
618
618
  fresh server renders can still be waiting for it. The dev server matches a
619
619
  build here: when the shell is ready, it waits for the client components the
620
620
  payload has named and that are still loading, and for nothing else. On a
621
- document with an `ssr: false` loader, a part React finishes a moment after the
621
+ document with an `ssr: "await"` loader, a part React finishes a moment after the
622
622
  shell, such as a `useActionState` form (React suspends it once to encode its
623
623
  action), is in the first flush too: the router gives React up to three more
624
624
  turns before it, and none when the document is already complete.
@@ -665,7 +665,7 @@ rest of the destination; otherwise it is held. A same-route click
665
665
  prefetch of the intercept's target defers it, and the click opens the
666
666
  modal at once with one fill for it; a hover's runs a loader marked
667
667
  `"intent"`.
668
- - With `ssr: false` on a `ppr` group route the flag works as on a server
668
+ - With `ssr: "await"` on a `ppr` group route the flag works as on a server
669
669
  route: deferred unless the prefetch serves the loader from a pin of a
670
670
  document-captured shell (`/ppr` → "`prefetch: false` on a `ppr` route").
671
671
  - Test it with `serveShellRequest` in the Flight project: mount the module
@@ -726,7 +726,7 @@ a hard load of the target URL renders the full route.
726
726
  matches) never renders as a group route: the server ships the competing
727
727
  routes with the group.
728
728
  - **Two parallel loaders with equal latency look "SSR'd" together.** Loaders
729
- kick off in parallel, so awaiting one (`ssr: false`) gives
729
+ kick off in parallel, so awaiting one (`ssr: "await"`) gives
730
730
  same-or-faster siblings time to settle coincidentally. Do not read "it was
731
731
  in the HTML once" as a guarantee — only the flagged loader is guaranteed.
732
732
  - **Hook semantics shift inside a group.** `usePathname` is ABSOLUTE (mount
@@ -786,7 +786,7 @@ The router repository (not shipped in this package) carries a canonical
786
786
  consumer at `tests/vite-rsc-demo/src/urls/client-shop.client.tsx`: layout +
787
787
  index + product routes, param-sensitive predicates, action-scoped cart
788
788
  revalidation, loader-thrown `notFound()`/`redirect()`, loader-written
789
- Meta/Breadcrumbs, viewport prefetch on cards, and the `ssr: false`
789
+ Meta/Breadcrumbs, viewport prefetch on cards, and the `ssr: "await"`
790
790
  fixtures — with e2e suites next to it pinning each contract in dev and
791
791
  production. The sections above are self-contained; the app is corroboration,
792
792
  not required reading.
@@ -638,17 +638,17 @@ this resource-aware policy layer.
638
638
 
639
639
  `createLoader(fn)` / `loader()` define **live-by-default** data units: they are
640
640
  excluded from an enclosing segment cache and resolve on every request unless the
641
- loader itself explicitly opts into `cache()`. A loader without `ssr: false` may
641
+ loader itself explicitly opts into `cache()`. A loader without `ssr: "await"` may
642
642
  safely read `cookies()`, `headers()`, request context, and `env` because its
643
643
  execution is outside the cached shell; a loader with its own `cache()` must put
644
644
  those reads in its `key()`, or its miss fails and stores nothing. On a `ppr`
645
- route an `ssr: false` loader bakes into the shell, so an identity read there
645
+ route an `ssr: "await"` loader bakes into the shell, so an identity read there
646
646
  refuses the capture. Loaders run in parallel, stream independently under
647
647
  `loading()` boundaries, compose server-side via `ctx.use(OtherLoader)`, and
648
648
  carry route-level authority: they can throw `notFound()`/`redirect()`, WRITE
649
649
  handles (`ctx.use(Meta)({ title })` — data-derived page metadata pushed from the
650
650
  data's producer), read handle data after the render settles (`ctx.get(handle)`
651
- behind `await ctx.rendered()`), and opt into `loader(Def, { ssr: false })` so a
651
+ behind `await ctx.rendered()`), and opt into `loader(Def, { ssr: "await" })` so a
652
652
  document render awaits them before first flush (deterministic SSR'd data, meta,
653
653
  and 404 status). Reads happen through `useLoader` in a client component
654
654
  (including its SSR pass) or `useFetchLoader` for standalone client fetches.
@@ -715,12 +715,12 @@ that no loader-before-render has:
715
715
 
716
716
  - it is the **cache-safety escape hatch** — the only place request-coupled reads
717
717
  are allowed inside a cache scope, because it is the part guaranteed to re-run
718
- (a loader without `ssr: false` and without its own unkeyed `cache()`);
718
+ (a loader without `ssr: "await"` and without its own unkeyed `cache()`);
719
719
  - it is **standalone and composable**, not route-coupled — one `createLoader()`
720
720
  read by many segments, composed via `ctx.use`, or exposed as a fetchable endpoint;
721
721
  - it **streams as a hole, not a gate** — concurrent, Suspense-resolved under
722
722
  `loading()`, so the shell never blocks on it (and gating is a deliberate
723
- per-loader opt-in — `{ ssr: false }` awaits ONE loader on document
723
+ per-loader opt-in — `{ ssr: "await" }` awaits ONE loader on document
724
724
  renders while the rest keep streaming — not the model's default);
725
725
  - it is **client-addressable and refreshable** independent of navigation (`key`,
726
726
  `refreshGroup`, `useRefreshLoaders`), behaving like a built-in, server-defined
@@ -798,7 +798,7 @@ shell.
798
798
 
799
799
  This is where the taint and loader designs meet. Loaders are the sanctioned
800
800
  dynamic holes: they run outside the enclosing segment cache and may read the
801
- current request (except, on a `ppr` route, an `ssr: false` loader or a loader a
801
+ current request (except, on a `ppr` route, an `ssr: "await"` loader or a loader a
802
802
  handler awaits, which the capture runs and refuses on such a read), while the
803
803
  shared shell remains protected. Next.js also isolates runtime APIs from ordinary
804
804
  `"use cache"`, and React's optional taint APIs protect values at the
@@ -121,8 +121,8 @@ payload from that record (`settleCaptureRecord` in `src/rsc/shell-capture.ts`).
121
121
  A boundary whose content is only handler output is therefore expected to bake
122
122
  its content as a completed boundary instead of postponing. What still
123
123
  postpones at capture is a boundary over live data: a loader without
124
- `ssr: false` read with `useLoader` under `loading()` or an inline
125
- `<Suspense>`, or a promise nested in an `ssr: false` loader's value. Run the
124
+ `ssr: "await"` read with `useLoader` under `loading()` or an inline
125
+ `<Suspense>`, or a promise nested in an `ssr: "await"` loader's value. Run the
126
126
  prelude check in "Verifying and measuring" on your page rather than assuming
127
127
  either shape.
128
128
 
@@ -417,11 +417,11 @@ The composition rules are this skill's: the component's items first, the
417
417
  mount site's explicit items second, `loading()` and `transition()` last-wins,
418
418
  loaders accumulate. What differs:
419
419
 
420
- | Mount site in `clientUrls()` | The component's `.use` may return |
421
- | ---------------------------- | ------------------------------------------ |
422
- | `path()` | `loader`, `loading`, `transition` |
423
- | `layout()` | `loader`, `loading` |
424
- | `intercept()` | `loader` (without `ssr: false`), `loading` |
420
+ | Mount site in `clientUrls()` | The component's `.use` may return |
421
+ | ---------------------------- | -------------------------------------------- |
422
+ | `path()` | `loader`, `loading`, `transition` |
423
+ | `layout()` | `loader`, `loading` |
424
+ | `intercept()` | `loader` (without `ssr: "await"`), `loading` |
425
425
 
426
426
  - No `route`, `layout`, `intercept` or `include` items: a group's routes and
427
427
  its generated types come from the builder's own items.
@@ -28,7 +28,7 @@ boundary (or the route's `loading()`) — it does NOT render with
28
28
  `isLoading: true`. Put a boundary above every read whose loader can be slow;
29
29
  `isLoading` covers later refetches (`load()`, key/group refreshes). Once the
30
30
  component renders, `data` is present. (On document loads a loader registered
31
- with `{ ssr: false }` is already settled at first paint, so its
31
+ with `{ ssr: "await" }` is already settled at first paint, so its
32
32
  reads never suspend there — see `/loader`.)
33
33
 
34
34
  **`isLoading` covers navigations and `load()` only.** When a server action
@@ -33,9 +33,9 @@ const lastCrumb = useHandle(Breadcrumbs, (data) => data.at(-1));
33
33
  Writers: handlers AND loader bodies — `ctx.use(Handle)` returns the push
34
34
  function in each (middleware has no handle APIs). Loader pushes follow a delivery race (settle before
35
35
  the handler barrier → in the SSR'd document; later → applied client-side), and
36
- `loader(Def, { ssr: false })` makes them deterministic in the
37
- document. On a `ppr` shell HIT, a loader without `ssr: false` never ran during
38
- the capture, so its pushes always apply client-side; with `ssr: false` they
36
+ `loader(Def, { ssr: "await" })` makes them deterministic in the
37
+ document. On a `ppr` shell HIT, a loader without `ssr: "await"` never ran during
38
+ the capture, so its pushes always apply client-side; with `ssr: "await"` they
39
39
  bake into the shell. See `/loader` → "Writing Handles from Loaders". `useHandle`
40
40
  re-renders on every handle update, late loader pushes included.
41
41
 
@@ -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. 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 right after the page's commit: without view transitions in the same task, before paint; with view transitions when the transition's `ready` resolves |
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: "await"` 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 right after the page's commit: without view transitions in the same task, before paint; with view transitions when the transition's `ready` resolves |
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>`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: loader
3
- description: Define data loaders with createLoader and register them on routes with loader(). Use when a route needs per-request data that stays fresh and streams while the page renders, a client component reads server data with useLoader, a loader should re-run after specific actions (revalidate), be cached (cache), be callable from the client (fetchable loaders), throw notFound()/redirect(), write page meta/breadcrumbs handles, must be settled in the SSR'd document (ssr:false), or must stay out of link prefetches (prefetch:false).
3
+ description: Define data loaders with createLoader and register them on routes with loader(). Use when a route needs per-request data that stays fresh and streams while the page renders, a client component reads server data with useLoader, a loader should re-run after specific actions (revalidate), be cached (cache), be callable from the client (fetchable loaders), throw notFound()/redirect(), write page meta/breadcrumbs handles, must be settled in the SSR'd document (ssr:"await"), or must stay out of link prefetches (prefetch:false).
4
4
  argument-hint: "[loader]"
5
5
  ---
6
6
 
@@ -8,7 +8,7 @@ argument-hint: "[loader]"
8
8
 
9
9
  A loader is a server function created with `createLoader()` and registered on a
10
10
  route segment with `loader()`. It runs fresh on every request (on a `ppr` route,
11
- one registered with `ssr: false` is baked into the shell instead), streams its
11
+ one registered with `ssr: "await"` is baked into the shell instead), streams its
12
12
  result to the client, and client components read it with `useLoader()`. This
13
13
  skill covers defining, registering, consuming, revalidating, caching, and
14
14
  client-fetching loaders.
@@ -130,15 +130,15 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
130
130
  DSL loaders are the **live data layer**: they resolve fresh on every request,
131
131
  even when the route is inside a `cache()` boundary, so `cache()` gives you
132
132
  cached UI + fresh data by default. See "Loaders: The Live Data Layer" below.
133
- The exception is a `ppr` route's `ssr: false` loader: it bakes into the shell,
133
+ The exception is a `ppr` route's `ssr: "await"` loader: it bakes into the shell,
134
134
  and a shell HIT runs it only when its return carries promises.
135
135
 
136
136
  ### Cache safety
137
137
 
138
- A DSL loader without `ssr: false` can read `createVar({ cache: false })`
138
+ A DSL loader without `ssr: "await"` can read `createVar({ cache: false })`
139
139
  variables: it runs on every request, `cache()` and PPR shell HITs included, so
140
140
  the read guard is bypassed for it. On a `ppr` route the capture runs an
141
- `ssr: false` loader, and a loader a handler awaits, and bakes their values into
141
+ `ssr: "await"` loader, and a loader a handler awaits, and bakes their values into
142
142
  the shell: a `cookies()`, `headers()`, `ctx.request.headers` or
143
143
  `{ cache: false }` read there refuses the capture, so the route keeps
144
144
  rendering without a shell (warned once per key).
@@ -191,7 +191,7 @@ the tree).
191
191
  - The same holds under a PPR shell capture (`/ppr`): handler consumption is the
192
192
  BAKED lane — the loader executes at capture and the rendered value is a
193
193
  capture-time copy served on every HIT (a HIT never runs the handler);
194
- `useLoader` of a loader without `ssr: false` is the live lane (an `ssr: false`
194
+ `useLoader` of a loader without `ssr: "await"` is the live lane (an `ssr: "await"`
195
195
  loader is the bake lane however it is read). Unlike `cache()`, an identity
196
196
  read inside that loader (`cookies()`, `headers()`, `ctx.request.headers`, a
197
197
  `{ cache: false }` variable) refuses the capture, so the route stays
@@ -226,26 +226,26 @@ never sends it (see `/hooks` `state.md`, "State on a document response").
226
226
 
227
227
  ### Full field surface
228
228
 
229
- | Field | Type | Notes |
230
- | ------------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
231
- | `params` | `TParams` | Merged route + explicit loader params; overridable by fetchable `load({ params })`. |
232
- | `routeParams` | `Record<string, string>` | Server-trusted route params from URL pattern matching; cannot be overridden. |
233
- | `request` | `Request` | The incoming `Request` (headers, method, body, `signal` for abort). |
234
- | `url` | `URL` | Request URL with internal `_rsc*` transport params stripped. Use this for application logic. |
235
- | `originalUrl` | `URL` | Request URL with every param intact, including `_rsc*`. Only for debugging or custom cache keying. |
236
- | `pathname` | `string` | URL pathname (shortcut for `ctx.url.pathname`). |
237
- | `searchParams` | `URLSearchParams` | Shortcut for `ctx.url.searchParams`. |
238
- | `search` | `ResolveSearchSchema<TSearch>` | Typed query params when a search schema is declared on the route; `{}` otherwise. |
239
- | `env` | `any` | Bindings from `createRouter<TEnv>()` (DB, KV, secrets, etc.). `createLoader` types it as `any`; annotate or cast at the use site. |
240
- | `waitUntil` | `(fn: () => Promise<void>)` | Run work after the response is sent. Takes a function, not a promise. Delegates to Cloudflare's `waitUntil`; fire-and-forget with error logging elsewhere. |
241
- | `executionContext` | `ExecutionContext \| undefined` | Raw Cloudflare `ExecutionContext` for libraries that need it; `undefined` off Cloudflare. Prefer `waitUntil`. |
242
- | `get` | `(key \| ContextVar \| handle)` | Reads middleware variables/context-vars — or READS a handle's collected data, after `await ctx.rendered()`. |
243
- | `use` | `(loader \| handle) => T` | Access another loader's data (Promise), or WRITE a handle: `ctx.use(Meta)({ title })` returns the push function — handler parity. Reads moved to `get`. |
244
- | `rendered` | `() => Promise<void>` | **Experimental.** DSL loaders only — waits for all non-loader segments (including `loading()` streaming handlers) to settle before reading handle data. Not with `ssr: false` (cycle; throws). |
245
- | `method` | `string` | HTTP method. `"GET"` for SSR loader runs; reflects real method for fetchable loaders. |
246
- | `body` | `TBody \| undefined` | Parsed request body for fetchable POST/PUT/PATCH/DELETE calls. |
247
- | `formData` | `FormData \| undefined` | Present when a fetchable loader is invoked via form submission. |
248
- | `reverse` | `AutofillAwareReverseFunction` | Generate type-checked URLs from route names (same scoped semantics as route handlers; params the matched route carries are optional). |
229
+ | Field | Type | Notes |
230
+ | ------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
231
+ | `params` | `TParams` | Merged route + explicit loader params; overridable by fetchable `load({ params })`. |
232
+ | `routeParams` | `Record<string, string>` | Server-trusted route params from URL pattern matching; cannot be overridden. |
233
+ | `request` | `Request` | The incoming `Request` (headers, method, body, `signal` for abort). |
234
+ | `url` | `URL` | Request URL with internal `_rsc*` transport params stripped. Use this for application logic. |
235
+ | `originalUrl` | `URL` | Request URL with every param intact, including `_rsc*`. Only for debugging or custom cache keying. |
236
+ | `pathname` | `string` | URL pathname (shortcut for `ctx.url.pathname`). |
237
+ | `searchParams` | `URLSearchParams` | Shortcut for `ctx.url.searchParams`. |
238
+ | `search` | `ResolveSearchSchema<TSearch>` | Typed query params when a search schema is declared on the route; `{}` otherwise. |
239
+ | `env` | `any` | Bindings from `createRouter<TEnv>()` (DB, KV, secrets, etc.). `createLoader` types it as `any`; annotate or cast at the use site. |
240
+ | `waitUntil` | `(fn: () => Promise<void>)` | Run work after the response is sent. Takes a function, not a promise. Delegates to Cloudflare's `waitUntil`; fire-and-forget with error logging elsewhere. |
241
+ | `executionContext` | `ExecutionContext \| undefined` | Raw Cloudflare `ExecutionContext` for libraries that need it; `undefined` off Cloudflare. Prefer `waitUntil`. |
242
+ | `get` | `(key \| ContextVar \| handle)` | Reads middleware variables/context-vars — or READS a handle's collected data, after `await ctx.rendered()`. |
243
+ | `use` | `(loader \| handle) => T` | Access another loader's data (Promise), or WRITE a handle: `ctx.use(Meta)({ title })` returns the push function — handler parity. Reads moved to `get`. |
244
+ | `rendered` | `() => Promise<void>` | **Experimental.** DSL loaders only — waits for all non-loader segments (including `loading()` streaming handlers) to settle before reading handle data. Not with `ssr: "await"` (cycle; throws). |
245
+ | `method` | `string` | HTTP method. `"GET"` for SSR loader runs; reflects real method for fetchable loaders. |
246
+ | `body` | `TBody \| undefined` | Parsed request body for fetchable POST/PUT/PATCH/DELETE calls. |
247
+ | `formData` | `FormData \| undefined` | Present when a fetchable loader is invoked via form submission. |
248
+ | `reverse` | `AutofillAwareReverseFunction` | Generate type-checked URLs from route names (same scoped semantics as route handlers; params the matched route carries are optional). |
249
249
 
250
250
  ### Example
251
251
 
@@ -556,14 +556,14 @@ served from cache. Route-level `cache()` caches rendered segments but never
556
556
  loader data: loaders are excluded when the segments are stored and re-resolved
557
557
  when they are served. Caching a loader's own data is a separate opt-in (see
558
558
  "Opting a Loader into Caching"). A `ppr` shell does store some loader data: an
559
- `ssr: false` loader's settled data bakes into the shell, and a HIT runs that
560
- loader only when its return carries promises (see "`ssr: false`" below and
559
+ `ssr: "await"` loader's settled data bakes into the shell, and a HIT runs that
560
+ loader only when its return carries promises (see "`ssr: "await"`" below and
561
561
  `/ppr`).
562
562
 
563
563
  Pre-rendering follows the same rule: the build-time Flight payload skips
564
564
  loaders (there is no real request context), and at runtime the worker resolves
565
565
  them fresh against the live database. The exception is a `Prerender` + `ppr`
566
- route: its build-time shell capture runs `ssr: false` loaders and bakes their
566
+ route: its build-time shell capture runs `ssr: "await"` loaders and bakes their
567
567
  settled data into the shell, like a runtime capture.
568
568
 
569
569
  ### Parallel and streaming — latency overlaps first paint
@@ -584,9 +584,9 @@ boundary a parallel loader blocks its parent, so add one to keep the overlap.)
584
584
  If you come from a framework where the loader is a blocking step that runs
585
585
  before the response is built, this is the shift to internalize: here the
586
586
  response starts streaming first and loader data fills in. (The one deliberate
587
- exception is per-loader: `loader(Def, { ssr: false })` awaits that
587
+ exception is per-loader: `loader(Def, { ssr: "await" })` awaits that
588
588
  loader before first flush on document renders other than a `ppr` shell HIT,
589
- where the shell flushes first — see "`ssr: false`" below.)
589
+ where the shell flushes first — see "`ssr: "await"`" below.)
590
590
 
591
591
  ### See it: `debugPerformance`
592
592
 
@@ -693,7 +693,7 @@ rejects) skips the whole entry, as above. Entries written
693
693
  before handles were recorded replay none until they are rewritten (expiry or
694
694
  SWR revalidation).
695
695
 
696
- On a `ppr` route the shell keeps a copy of an `ssr: false` loader's pushes
696
+ On a `ppr` route the shell keeps a copy of an `ssr: "await"` loader's pushes
697
697
  too, a pushed promise resolved (#1182). The two never disagree on the page:
698
698
  where a replay of the shell serves the loader's data from the shell, the
699
699
  shell's copy is the one shown and the entry only adds a push whose value
@@ -1005,12 +1005,12 @@ export const ProductLoader = createLoader(async (ctx) => {
1005
1005
 
1006
1006
  Semantics by lane:
1007
1007
 
1008
- | Signal | Document load, read inside `<Suspense>` or under `loading()` | Document load, read outside any `<Suspense>` | Client navigation |
1009
- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
1010
- | `notFound()` | Not-found UI resolves server-side (nearest `notFoundBoundary` → router option → default) and rides the envelope; the 404 STATUS is **opportunistic** — real only if the rejection beats Response construction. `ssr: false` (below) makes it deterministic. | A real 404; the browser renders the not-found UI with the layouts kept (the recovery document, below) | 404 UI swaps in, URL preserved, payload stays 200 |
1011
- | `redirect()` | 200 document, then a client-side replace to the target | A real 3xx (the redirect's own status) with `Location`; with `state`, the recovery document (200), then the client replace carries it | Redirect envelope navigates to the target |
1008
+ | Signal | Document load, read inside `<Suspense>` or under `loading()` | Document load, read outside any `<Suspense>` | Client navigation |
1009
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
1010
+ | `notFound()` | Not-found UI resolves server-side (nearest `notFoundBoundary` → router option → default) and rides the envelope; the 404 STATUS is **opportunistic** — real only if the rejection beats Response construction. `ssr: "await"` (below) makes it deterministic. | A real 404; the browser renders the not-found UI with the layouts kept (the recovery document, below) | 404 UI swaps in, URL preserved, payload stays 200 |
1011
+ | `redirect()` | 200 document, then a client-side replace to the target | A real 3xx (the redirect's own status) with `Location`; with `state`, the recovery document (200), then the client replace carries it | Redirect envelope navigates to the target |
1012
1012
 
1013
- With `loader(Def, { ssr: false })` the result is settled BEFORE the document
1013
+ With `loader(Def, { ssr: "await" })` the result is settled BEFORE the document
1014
1014
  flushes, so both signals are resolved server-side while the tree is built:
1015
1015
  `redirect()` replaces the whole page with the redirect carrier (still a 200
1016
1016
  document, the client replaces to the target on hydration) and `notFound()`
@@ -1102,7 +1102,7 @@ on document loads (`metadata.handlesLate`) or progressively on navigations.
1102
1102
  Handle values land on their own channel and never hold the page, so an effect that needs a late push keys on the handle value, not the URL (see [hooks](../hooks/handle-and-actions.md#when-handle-values-change-by-design)).
1103
1103
  A push before your slow fetch usually beats the barrier; a push derived from
1104
1104
  the fetched data usually does not. When it MUST be in the document, use
1105
- `ssr: false` below. A loader with its own `cache()` keeps its pushes on a hit —
1105
+ `ssr: "await"` below. A loader with its own `cache()` keeps its pushes on a hit —
1106
1106
  they are replayed from the entry (see "Opting a Loader into Caching"). On a
1107
1107
  `ppr` route the race does not apply to a document served from its shell:
1108
1108
  there every push a loader makes on that request applies post-hydration (see
@@ -1115,8 +1115,8 @@ handler reserved with `.defer()` instead (`/breadcrumbs`, "Deferred content").
1115
1115
 
1116
1116
  Under `cache()`, loader pushes are not stored with the cached segments: the
1117
1117
  loader re-runs on every hit and its live push is the only copy. (`ppr` keeps
1118
- the pushes of an `ssr: false` loader, and of the loaders it awaits, in the
1119
- shell, a pushed promise resolved, and a promise-free `ssr: false` loader does
1118
+ the pushes of an `ssr: "await"` loader, and of the loaders it awaits, in the
1119
+ shell, a pushed promise resolved, and a promise-free `ssr: "await"` loader does
1120
1120
  not run on a shell HIT — see `/ppr`.)
1121
1121
 
1122
1122
  Reads are the other direction and gated: `ctx.get(handle)` throws unless the
@@ -1124,7 +1124,7 @@ loader first does `await ctx.rendered()` (DSL-registered loaders only —
1124
1124
  handler-invoked loaders cannot use `rendered()`, and a handler already
1125
1125
  awaiting the loader via `ctx.use()` makes it a detected deadlock).
1126
1126
 
1127
- ## `ssr: false` — Guarantee a Loader in the Document
1127
+ ## `ssr: "await"` — Guarantee a Loader in the Document
1128
1128
 
1129
1129
  Streaming means nothing a slow loader produces is _guaranteed_ in the SSR'd
1130
1130
  HTML: its section SSRs as the fallback, a late handle push applies
@@ -1135,7 +1135,7 @@ use callback:
1135
1135
 
1136
1136
  ```typescript
1137
1137
  path("/product/:slug", ProductPage, { name: "product" }, () => [
1138
- loader(ProductLoader, { ssr: false }, () => [cache({ ttl: 60 })]),
1138
+ loader(ProductLoader, { ssr: "await" }, () => [cache({ ttl: 60 })]),
1139
1139
  loader(RelatedLoader), // untouched: still streams behind its boundary
1140
1140
  ]),
1141
1141
  ```
@@ -1143,9 +1143,14 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
1143
1143
  The options object is typed as `LoaderOptions`
1144
1144
  (`import type { LoaderOptions } from "@rangojs/router"`).
1145
1145
 
1146
- The knob mirrors `loading(fallback, { ssr: false })` — SSR delivery is off for
1147
- this loader, so nothing of it is left to stream in the document: document
1148
- renders await this loader before first flush — data is settled (`useLoader`
1146
+ The value says what the server render does with the loader: `"stream"` (the
1147
+ default, the same as leaving `ssr` out) streams it behind its boundary;
1148
+ `"await"` awaits it in the renders that produce HTML — the document render and
1149
+ a `ppr` shell capture (at runtime, or at build time on a `Prerender` + `ppr`
1150
+ route). (`loading(fallback, { ssr: false })` is a different option: there
1151
+ `false` means the fallback is not server-rendered.)
1152
+
1153
+ Document renders await this loader before first flush — data is settled (`useLoader`
1149
1154
  reads it synchronously, no fallback paints), handle pushes beat the barrier
1150
1155
  snapshot, and a thrown `notFound()` deterministically precedes Response
1151
1156
  construction (real 404, no warm-up race). Client navigations stream exactly as
@@ -1212,7 +1217,7 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
1212
1217
  ```
1213
1218
 
1214
1219
  (`prefetch` sits beside `ssr` in `LoaderOptions`; combine them as
1215
- `{ ssr: false, prefetch: false }` if you need both.)
1220
+ `{ ssr: "await", prefetch: false }` if you need both.)
1216
1221
 
1217
1222
  | Request | unflagged | `prefetch: false` | `prefetch: "intent"` |
1218
1223
  | ------------------------------------------------------------ | ---------- | ------------------------ | ------------------------ |
@@ -1404,7 +1409,7 @@ The limits:
1404
1409
  `intercept()` defers nothing. Each intercept loader also runs its own
1405
1410
  `revalidate()`, so an action from the modal re-runs only those that say so
1406
1411
  (see `/intercept`).
1407
- - **`ppr` and `ssr: false`.** On a `ppr` route an `ssr: false` loader is the
1412
+ - **`ppr` and `ssr: "await"`.** On a `ppr` route an `ssr: "await"` loader is the
1408
1413
  bake lane. A prefetch that replays a shell a document request captured
1409
1414
  serves it from its pin: it is not deferred, does not run, and the click
1410
1415
  sends nothing. Every other prefetch (a navigation-only shell, a cold page,