@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
@@ -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,
@@ -381,7 +381,7 @@ middleware `ctx` passed in); under `cache()` loaders can still read it because a
381
381
  DSL loader re-runs on every hit (a `"use cache"` function a loader calls cannot,
382
382
  and a loader bound with its own `cache()` only with a `key()`, which switches
383
383
  the check off, so the key must include the value). On a `ppr` route only a
384
- loader without `ssr: false` that no handler awaits may read it: a read the shell
384
+ loader without `ssr: "await"` that no handler awaits may read it: a read the shell
385
385
  capture waits for refuses the capture. A middleware `ctx` passed to a
386
386
  `"use cache"` function keys the call by host, pathname, params and search, plus
387
387
  the route name once the route is matched (a global middleware before `next()`
@@ -556,7 +556,7 @@ direct visit does. A shell keyed by the visitor's URL
556
556
  that URL.
557
557
 
558
558
  **Middleware only, by design.** `rewrite()` is a middleware tool. A route
559
- handler or a loader (any loader, `ssr: false` included) cannot rewrite: its
559
+ handler or a loader (any loader, `ssr: "await"` included) cannot rewrite: its
560
560
  throw is an error whose message points you to route middleware. A rewrite
561
561
  that depends on data goes in the route's middleware, reading the data
562
562
  through a cached function the loader shares, so the lookup runs once:
@@ -40,7 +40,7 @@ Common reasons to migrate:
40
40
  build-time rendering instead of mixing rendering and caching behind conventions.
41
41
  See: `/prerender`
42
42
  - **Partial prerendering, shipped** — the `ppr` path option caches a page's
43
- HTML shell and resumes its holes per request: loaders without `ssr: false`,
43
+ HTML shell and resumes its holes per request: loaders without `ssr: "await"`,
44
44
  read under `loading()` or an inline `<Suspense>`. Handler output bakes into
45
45
  the shell. It maps to Next's `experimental_ppr`, stable and per-route, but
46
46
  holes come from loaders, not from any `<Suspense>`.
@@ -100,7 +100,7 @@ its call site with the real Rango API:
100
100
  `next/dynamic`'s `{ ssr: false }` keeps a component out of server rendering:
101
101
  render it only after mount in a `"use client"` component (a `useEffect` flag,
102
102
  with `React.lazy()` if its code should split out). Do not map it to
103
- `loader(L, { ssr: false })`, which does the opposite: the server settles that
103
+ `loader(L, { ssr: "await" })`, which does the opposite: the server settles that
104
104
  loader before the first flush, and under `ppr` it bakes into the shell.
105
105
 
106
106
  If an import has no row here and no obvious Rango equivalent, stop and surface
@@ -533,7 +533,7 @@ function ProductPage(ctx: HandlerContext) {
533
533
  );
534
534
  }
535
535
 
536
- // Rango, live price: a loader without ssr: false, read with
536
+ // Rango, live price: a loader without ssr: "await", read with
537
537
  // useLoader(LivePriceLoader) in a client component. loading() (or an inline
538
538
  // <Suspense> around the reader) is the hole boundary (/ppr → The loader lane
539
539
  // rule).
@@ -560,9 +560,9 @@ Differences that matter during migration:
560
560
  component, a nested promise in a handle it pushes, a loader it awaits — is
561
561
  awaited at capture (bounded by `ppr.captureTimeout`) and served frozen for
562
562
  the shell's lifetime; a shell HIT never runs a handler. Keep the parts that
563
- must be fresh in loaders without `ssr: false`, read with `useLoader` under
564
- `loading()` or an inline `<Suspense>`; inside an `ssr: false` loader, return
565
- them as nested promises. For loaders, `ssr: false` (not `loading()`) selects
563
+ must be fresh in loaders without `ssr: "await"`, read with `useLoader` under
564
+ `loading()` or an inline `<Suspense>`; inside an `ssr: "await"` loader, return
565
+ them as nested promises. For loaders, `ssr: "await"` (not `loading()`) selects
566
566
  the lane — see `/ppr` → The loader lane rule.
567
567
  - **Shell freshness is explicit.** Next's PPR shell is fixed until the next
568
568
  build; Rango's has `ttl`/`swr`/`tags` per route, and `updateTag()` /
@@ -577,7 +577,7 @@ Differences that matter during migration:
577
577
  `ctx.request.headers`, a `{ cache: false }` variable, and `ctx.dynamic()` refuse it anywhere the
578
578
  capture waits: a handler, a promise it passes or pushes, an async component,
579
579
  a loader it awaits. Per-user reads must move into a live loader (no
580
- `ssr: false`; a nested promise does not help). A normal `ctx.get()` value is
580
+ `ssr: "await"`; a nested promise does not help). A normal `ctx.get()` value is
581
581
  not guarded and bakes as the capturing request's value. The refusal surfaces
582
582
  at migration time, which is the point.
583
583
  - **A store is required.** PPR needs the app-level `createRouter({ cache })`
@@ -890,7 +890,7 @@ const HomePage: Handler<"home"> = (ctx) => {
890
890
 
891
891
  `generateMetadata({ params })` — DATA-derived, document-blocking metadata —
892
892
  maps to a Meta push from the LOADER that owns the data, plus
893
- `{ ssr: false }` for the blocking-until-in-head part:
893
+ `{ ssr: "await" }` for the blocking-until-in-head part:
894
894
 
895
895
  ```typescript
896
896
  // Next.js: export async function generateMetadata({ params }) {
@@ -907,7 +907,7 @@ export const ProductLoader = createLoader(async (ctx) => {
907
907
  });
908
908
 
909
909
  path("/product/:slug", ProductPage, { name: "product" }, () => [
910
- loader(ProductLoader, { ssr: false }),
910
+ loader(ProductLoader, { ssr: "await" }),
911
911
  ]);
912
912
  ```
913
913
 
@@ -1008,7 +1008,7 @@ See `/theme` for full API including system detection and cookie persistence.
1008
1008
  10. [ ] Convert loading/error files to `loading()` / `errorBoundary()`
1009
1009
  11. [ ] Migrate API routes to `path.json()` / `path.text()`
1010
1010
  12. [ ] Update metadata to use `Meta` handle + `<Html.Meta />` in document head
1011
- (`generateMetadata` → loader push + `{ ssr: false }`)
1011
+ (`generateMetadata` → loader push + `{ ssr: "await" }`)
1012
1012
  13. [ ] Replace `next-themes` with `theme: true` in createRouter (see `/theme`)
1013
1013
  14. [ ] Map rendering-mode segment config: `revalidate = N` → `cache({ ttl })`,
1014
1014
  `force-static` → `Static()`/`Prerender()`, `experimental_ppr` → the
@@ -34,7 +34,7 @@ Common reasons to migrate:
34
34
  - **Live data layer** — `createLoader()` and `loader()` keep data fresh
35
35
  independently of cached UI. A route can serve cached segments while loaders
36
36
  still resolve live on every request (on a `ppr` route, a loader registered
37
- with `ssr: false` bakes into the cached HTML shell instead; see `/ppr`).
37
+ with `ssr: "await"` bakes into the cached HTML shell instead; see `/ppr`).
38
38
  See: `/loader`, `/caching`, `/cache-guide`
39
39
  - **Explicit caching model** — `cache()` DSL, `revalidate()`, `use cache`, and
40
40
  custom cache stores make data and render caching a first-class part of the router.
@@ -153,9 +153,9 @@ const HomePage: Handler<"home"> = (ctx) => {
153
153
 
154
154
  RR's data-derived `meta({ data })` maps to the same push from the LOADER that
155
155
  owns the data — `ctx.use(Meta)({ title: data.name })` in the loader body, with
156
- `loader(Def, { ssr: false })` when the title must be in the SSR'd
156
+ `loader(Def, { ssr: "await" })` when the title must be in the SSR'd
157
157
  head. See `/loader` → "Writing Handles from Loaders". The loader flag is
158
- unrelated to RR7's `ssr: false` (SPA mode), which has no route-wide Rango
158
+ unrelated to RR7's `ssr: "await"` (SPA mode), which has no route-wide Rango
159
159
  equivalent: it makes the server settle that loader before the first flush.
160
160
 
161
161
  Add `<Html.Meta />` in the Document component's `<head>`:
@@ -48,8 +48,8 @@ Use it only when you need capabilities beyond what the handler provides:
48
48
  document loads, never an HTTP 302 — pre-stream 302s move to middleware)
49
49
  - **`meta({ data })` / `handle` exports** — data-derived page metadata becomes
50
50
  a handle push from the loader body (`ctx.use(Meta)({ title: data.name })`),
51
- with `loader(L, { ssr: false })` when it must be in the SSR'd head (not
52
- RR7's `ssr: false` SPA mode, which has no route-wide Rango equivalent: the
51
+ with `loader(L, { ssr: "await" })` when it must be in the SSR'd head (not
52
+ RR7's `ssr: "await"` SPA mode, which has no route-wide Rango equivalent: the
53
53
  loader flag makes the server settle that loader before the first flush)
54
54
 
55
55
  If the React Router loader just fetches data for its page component AND the
@@ -141,10 +141,10 @@ The loader-shaped variant is equally valid — and closer to the RR module when
141
141
  the loader carried authority. A `createLoader()` body can throw `notFound()`
142
142
  for the missing product AND push the data-derived meta itself
143
143
  (`ctx.use(Meta)({ title: product.name })`); register it with
144
- `loader(ProductLoader, { ssr: false })` when the 404 status and
144
+ `loader(ProductLoader, { ssr: "await" })` when the 404 status and
145
145
  title must be in the document deterministically (the flag makes the server
146
146
  settle that loader before the first flush; it is unrelated to RR7's
147
- `ssr: false` SPA mode, which has no route-wide Rango equivalent). See
147
+ `ssr: "await"` SPA mode, which has no route-wide Rango equivalent). See
148
148
  `/loader` → "Loader Authority" and "Writing Handles from Loaders". (One RR
149
149
  habit that does NOT carry over: a loader `throw redirect()` is a client-side
150
150
  navigate on document loads, never an HTTP 302 — pre-stream 302s belong in
@@ -152,10 +152,10 @@ layout, and `Meta` keeps the last value per key, so the override wins.
152
152
  > just to reach a `ctx.use(Meta)` call: under `cache()` the slot's output is
153
153
  > stored with the segment, and on a `ppr` route it bakes into the shell and a
154
154
  > HIT replays it without running the slot. The loader can push `Meta` itself
155
- > (`/loader` → "Writing Handles from Loaders"). Without `ssr: false` the
155
+ > (`/loader` → "Writing Handles from Loaders"). Without `ssr: "await"` the
156
156
  > loader runs per request, but on a `ppr` route its push is not in the
157
157
  > shell's `<head>` (it applies after hydration). Add
158
- > `loader(Def, { ssr: false })` when the meta must be in the SSR'd head; on a
158
+ > `loader(Def, { ssr: "await" })` when the meta must be in the SSR'd head; on a
159
159
  > `ppr` route that bakes the push into the shell, and a HIT serves it without
160
160
  > running the loader when its value has no promises. Keep the `@meta` slot for
161
161
  > metadata that is NOT loader-derived (templates, static descriptions,