@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.
Files changed (103) 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-2ZTYRZ3V.js → runtime-discovery-PA3V3IFQ.js} +2 -2
  6. package/dist/bin/{temp-rsc-server-EUNKRFJD.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 +20 -8
  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/match-context.d.ts +12 -0
  19. package/dist/types/router/navigation-snapshot.d.ts +17 -0
  20. package/dist/types/router/segment-resolution/loader-cache.d.ts +7 -7
  21. package/dist/types/router/types.d.ts +1 -1
  22. package/dist/types/rsc/shell-capture-constants.d.ts +2 -2
  23. package/dist/types/server/context.d.ts +3 -3
  24. package/dist/types/server/request-context.d.ts +1 -1
  25. package/dist/types/ssr/index.d.ts +1 -1
  26. package/dist/types/ssr/ssr-root.d.ts +1 -1
  27. package/dist/types/ssr-suspension-warning.d.ts +1 -1
  28. package/dist/types/types/loader-types.d.ts +19 -19
  29. package/dist/types/types/segments.d.ts +2 -2
  30. package/dist/types/urls/path-helper-types.d.ts +4 -4
  31. package/dist/types/urls/pattern-types.d.ts +1 -1
  32. package/dist/types/vite/plugin-types.d.ts +1 -1
  33. package/dist/vite/index.js +7 -7
  34. package/package.json +1 -1
  35. package/skills/app-structure/e-commerce.md +1 -1
  36. package/skills/breadcrumbs/SKILL.md +5 -5
  37. package/skills/cache-guide/SKILL.md +4 -4
  38. package/skills/caching/SKILL.md +1 -1
  39. package/skills/catalog.json +2 -2
  40. package/skills/client-urls/SKILL.md +20 -20
  41. package/skills/comparison/references/framework-comparison.md +6 -6
  42. package/skills/defer-hydration/SKILL.md +2 -2
  43. package/skills/handler-use/SKILL.md +5 -5
  44. package/skills/hooks/data.md +5 -2
  45. package/skills/hooks/handle-and-actions.md +9 -9
  46. package/skills/loader/SKILL.md +52 -47
  47. package/skills/middleware/SKILL.md +2 -2
  48. package/skills/migrate-nextjs/SKILL.md +10 -10
  49. package/skills/migrate-react-router/SKILL.md +1 -1
  50. package/skills/migrate-react-router/component-migration.md +2 -2
  51. package/skills/migrate-react-router/data-and-actions.md +2 -2
  52. package/skills/migrate-react-router/route-mapping.md +2 -2
  53. package/skills/parallel/SKILL.md +2 -2
  54. package/skills/ppr/SKILL.md +91 -92
  55. package/skills/prerender/SKILL.md +5 -5
  56. package/skills/rango/SKILL.md +7 -7
  57. package/skills/route/SKILL.md +1 -1
  58. package/skills/router-setup/SKILL.md +2 -2
  59. package/skills/scripts/SKILL.md +3 -3
  60. package/skills/shell-manifest/SKILL.md +3 -3
  61. package/skills/testing/serve-shell-request.md +1 -1
  62. package/skills/theme/SKILL.md +2 -2
  63. package/skills/typesafety/env-and-bindings.md +2 -2
  64. package/skills/use-cache/SKILL.md +1 -1
  65. package/skills/ux-architecture/SKILL.md +7 -7
  66. package/src/cache/shell-snapshot.ts +1 -1
  67. package/src/cache/types.ts +1 -1
  68. package/src/client-urls/client-urls.ts +16 -9
  69. package/src/client-urls/server-projection.ts +4 -4
  70. package/src/client-urls/types.ts +6 -6
  71. package/src/decode-loader-results.ts +1 -1
  72. package/src/errors.ts +9 -9
  73. package/src/handles/meta.ts +2 -2
  74. package/src/loader-store.ts +29 -13
  75. package/src/outlet-context.ts +1 -1
  76. package/src/route-definition/dsl-helpers.ts +4 -8
  77. package/src/route-definition/helpers-types.ts +5 -5
  78. package/src/router/loader-resolution.ts +4 -4
  79. package/src/router/match-api.ts +2 -0
  80. package/src/router/match-context.ts +22 -1
  81. package/src/router/match-middleware/cache-lookup.ts +17 -8
  82. package/src/router/match-middleware/intercept-resolution.ts +3 -3
  83. package/src/router/navigation-snapshot.ts +39 -8
  84. package/src/router/segment-resolution/loader-cache.ts +8 -8
  85. package/src/router/segment-resolution/prefetch-deferral.ts +1 -1
  86. package/src/router/segment-resolution/revalidation.ts +3 -3
  87. package/src/router/types.ts +1 -1
  88. package/src/rsc/shell-capture-constants.ts +3 -3
  89. package/src/rsc/shell-capture.ts +12 -12
  90. package/src/segment-system.tsx +3 -3
  91. package/src/server/context.ts +5 -5
  92. package/src/server/cookie-store.ts +1 -1
  93. package/src/server/request-context.ts +1 -1
  94. package/src/ssr/index.tsx +6 -6
  95. package/src/ssr/ssr-root.tsx +1 -1
  96. package/src/ssr-suspension-warning.ts +4 -4
  97. package/src/types/loader-types.ts +19 -19
  98. package/src/types/segments.ts +2 -2
  99. package/src/urls/path-helper-types.ts +4 -4
  100. package/src/urls/pattern-types.ts +1 -1
  101. package/src/use-loader.tsx +5 -3
  102. package/src/vite/discovery/shell-prerender-phase.ts +1 -1
  103. package/src/vite/plugin-types.ts +1 -1
@@ -153,10 +153,10 @@ export type PathHelpers<TEnv> = {
153
153
  /**
154
154
  * Attach a data loader to the current route/layout.
155
155
  *
156
- * Pass `{ ssr: false }` — the same knob as loading(fallback, { ssr:
157
- * false }) — to await this loader before first flush on DOCUMENT requests
158
- * (see {@link LoaderOptions}): the opt-in for loaders whose data, handle
159
- * pushes, or thrown notFound()/redirect() must be in the SSR'd HTML.
156
+ * Pass `{ ssr: "await" }` to await this loader before first flush on
157
+ * DOCUMENT requests (see {@link LoaderOptions}): the opt-in for loaders
158
+ * whose data, handle pushes, or thrown notFound()/redirect() must be in the
159
+ * SSR'd HTML.
160
160
  * Per-loader: a streaming sibling keeps streaming. Under a `ppr` route the
161
161
  * flag BAKES: the loader executes at shell capture and its settled return
162
162
  * freezes into the shell (nested promises stay live holes) — the pre-flush
@@ -61,7 +61,7 @@ export interface PartialPrerenderProps {
61
61
  * Upper bound (serialized UTF-8 bytes) on the loader pins in the capture
62
62
  * data snapshot: the bake-lane loader records. The doc record (the handler
63
63
  * layer every HIT replays) is exempt. A document HIT is served only from an
64
- * entry that holds a pin for every `ssr: false` loader its HTML baked, so
64
+ * entry that holds a pin for every `ssr: "await"` loader its HTML baked, so
65
65
  * over the cap no document shell is stored: documents render in full
66
66
  * (MISS), and client navigations still replay the handler layer and run
67
67
  * those loaders fresh. Reported once per key. Defaults to 8 MiB. The whole
@@ -166,7 +166,7 @@ interface RangoBaseOptions {
166
166
  * which is why React outlines by default.
167
167
  *
168
168
  * When UNSET, document renders and PPR shell captures whose matched chain
169
- * has a `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
169
+ * has a `loader(Def, { ssr: "await" })` entry auto-raise to MAX_SAFE_INTEGER —
170
170
  * the loader was awaited before first flush precisely so its content ships
171
171
  * in-place, and outlining the boundary it feeds would defeat that. A shell
172
172
  * HIT inherits the captured value, so it matches the live render. Setting
@@ -4604,7 +4604,7 @@ function routerKeyPrefix(routerId2) {
4604
4604
 
4605
4605
  // src/rsc/shell-capture-constants.ts
4606
4606
  var SHELL_CAPTURE_MAX_WAIT_MS = 15e3;
4607
- var PPR_LANE_HINT = 'Lane rule: only loader(Def, { ssr: false }) executes at capture and bakes its non-promise data; every other loader is live (masked at capture, fresh per request) and needs loading() or an inline <Suspense> above its reader. See the /ppr skill, "The loader lane rule" (node_modules/@rangojs/router/skills/ppr/SKILL.md).';
4607
+ var PPR_LANE_HINT = 'Lane rule: only loader(Def, { ssr: "await" }) executes at capture and bakes its non-promise data; every other loader is live (masked at capture, fresh per request) and needs loading() or an inline <Suspense> above its reader. See the /ppr skill, "The loader lane rule" (node_modules/@rangojs/router/skills/ppr/SKILL.md).';
4608
4608
 
4609
4609
  // src/cache/cache-exec-scope.ts
4610
4610
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -4633,8 +4633,8 @@ var LOADER_BODY_SCOPE_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:loader-bo
4633
4633
  var loaderBodyScopeALS = globalThis[LOADER_BODY_SCOPE_KEY] ??= new AsyncLocalStorage2();
4634
4634
  function requestReadCaptureFix(what) {
4635
4635
  return {
4636
- capture: `The captured shell is served to every user of this URL, so request-scoped data read here would leak one user's ${what} to others. Read it inside a loader without ssr: false and consume it with useLoader, e.g. createLoader(async () => getUser(cookies().get("session")?.value)). ${PPR_LANE_HINT}`,
4637
- warning: "Read it in a loader without ssr: false and consume it with useLoader under loading() or an inline <Suspense> (a live hole). A promise the handler passes or pushes does not help: the capture waits for it."
4636
+ capture: `The captured shell is served to every user of this URL, so request-scoped data read here would leak one user's ${what} to others. Read it inside a loader without ssr: "await" and consume it with useLoader, e.g. createLoader(async () => getUser(cookies().get("session")?.value)). ${PPR_LANE_HINT}`,
4637
+ warning: 'Read it in a loader without ssr: "await" and consume it with useLoader under loading() or an inline <Suspense> (a live hole). A promise the handler passes or pushes does not help: the capture waits for it.'
4638
4638
  };
4639
4639
  }
4640
4640
  var NON_CACHEABLE_READ = {
@@ -6074,7 +6074,7 @@ var RAW_COOKIES_READ = {
6074
6074
  verb: "called",
6075
6075
  fix: COOKIES_READ.fix
6076
6076
  };
6077
- var THEME_READ_FIX = "On ppr and cache() routes, read the theme with useTheme() in a client component, or in a live loader (no ssr: false) with cookies().get(<storageKey>). The <html> theme class needs neither: the theme script sets it before paint. See the /theme skill (node_modules/@rangojs/router/skills/theme/SKILL.md).";
6077
+ var THEME_READ_FIX = 'On ppr and cache() routes, read the theme with useTheme() in a client component, or in a live loader (no ssr: "await") with cookies().get(<storageKey>). The <html> theme class needs neither: the theme script sets it before paint. See the /theme skill (node_modules/@rangojs/router/skills/theme/SKILL.md).';
6078
6078
  var THEME_READ = {
6079
6079
  verb: "read",
6080
6080
  fix: {
@@ -6289,7 +6289,7 @@ function serializeRoute(route2, options, ordinals) {
6289
6289
  });
6290
6290
  const awaitedLoaderIndices = indicesWhere(
6291
6291
  route2.loaders,
6292
- ({ ssr }) => ssr === false
6292
+ ({ ssr }) => ssr === "await"
6293
6293
  );
6294
6294
  const transition3 = serializeTransition(route2);
6295
6295
  return Object.freeze({
@@ -7344,7 +7344,7 @@ function resolveVersionsFrom(table, routerId2) {
7344
7344
  // package.json
7345
7345
  var package_default = {
7346
7346
  name: "@rangojs/router",
7347
- version: "0.34.0",
7347
+ version: "0.35.0",
7348
7348
  description: "Django-inspired RSC router with composable URL patterns",
7349
7349
  keywords: [
7350
7350
  "react",
@@ -14567,7 +14567,7 @@ async function runShellPrerenderPhase(s, builder, versionsTable) {
14567
14567
  injectRSCPayload: htmlStream.injectRSCPayload,
14568
14568
  headScripts: "preinit",
14569
14569
  // Build-captured shells honor the explicit outlining budget, or the
14570
- // ssr:false auto-raise without one (createShellCaptureHandler): the
14570
+ // ssr: "await" auto-raise without one (createShellCaptureHandler): the
14571
14571
  // capture value rides the postponed state into every runtime resume().
14572
14572
  ...s.opts?.progressiveChunkSize !== void 0 && {
14573
14573
  progressiveChunkSize: s.opts.progressiveChunkSize
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -379,7 +379,7 @@ product) and the pages render entirely from loaders, mount the shop as a
379
379
  `clientUrls()` group, `include("/shop", shopUrls, { name: "shop" })`:
380
380
  navigations inside it render at the click, and each loader's `revalidate()`
381
381
  holds its data across them. A group has no handlers, so a `ppr` group route
382
- holds its client components' HTML and its `ssr: false` loaders' data, and
382
+ holds its client components' HTML and its `ssr: "await"` loaders' data, and
383
383
  every other loader is a hole; handler output baked from a tagged
384
384
  `"use cache"` read, as in the catalog above, needs `urls()` routes. The router
385
385
  repository's `tests/vite-rsc-demo/src/urls/client-shop.client.tsx` is a full
@@ -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
@@ -261,7 +261,10 @@ tag (`"account"`) covers everything, while a finer tag (`"orders"`) targets a
261
261
  subset. Sharing within a group is opt-in via `key`: members that share a `key`
262
262
  share one value (and one fetch); a grouped reader **without** a `key` gets its own
263
263
  private bucket, so a group refresh updates only that read and never leaks into
264
- unrelated unkeyed reads of the same loader. A bucket may belong to several groups
264
+ unrelated unkeyed reads of the same loader. That private bucket lives as long as
265
+ its read is mounted, and a hidden React `<Activity>` counts as unmounted: when it
266
+ shows again, the read is back on the route's data until the next refresh. To keep
267
+ a refreshed value across an `<Activity>` hide, give the read a `key`. A bucket may belong to several groups
265
268
  at once (one read tagged with multiple names, or different reads tagging the same
266
269
  keyed bucket with different names). Keep parameterized loaders on the single-loader
267
270
  `key` — a plain-GET group refresh sends no params.
@@ -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>`