@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
@@ -261,7 +261,7 @@ function chainLoaderEntries(entry: EntryData): readonly LoaderEntry[] {
261
261
 
262
262
  /**
263
263
  * The lane of each loader the matched chain `entries` registers: "live" (no
264
- * `ssr: false`) or "bake". A loader registered on both is "live". A PPR
264
+ * `ssr: "await"`) or "bake". A loader registered on both is "live". A PPR
265
265
  * capture credits a push to the first registered loader around it when that
266
266
  * one is live (shell-capture.ts deriveShellCaptureContext).
267
267
  */
@@ -302,7 +302,7 @@ function servedPins(
302
302
 
303
303
  /**
304
304
  * This request serves `loaderEntry` from a pin (servedPins), so its body does
305
- * not decide the value: resolveLoaderData serves a pin to an `ssr: false`
305
+ * not decide the value: resolveLoaderData serves a pin to an `ssr: "await"`
306
306
  * registration only. A prefetch does not defer such a loader
307
307
  * (prefetch-deferral.ts defersLoader): nothing would run, and the pin is the
308
308
  * value the document shows.
@@ -325,14 +325,14 @@ export function servesLoaderPin(
325
325
  * only and the store asks while loader bodies run.
326
326
  *
327
327
  * Each side applies its own lane to the pin. The value is per registration
328
- * (resolveLoaderData: an `ssr: false` registration is served its pin). The
329
- * pushes are per loader: one the route also registers without `ssr: false`
328
+ * (resolveLoaderData: an `ssr: "await"` registration is served its pin). The
329
+ * pushes are per loader: one the route also registers without `ssr: "await"`
330
330
  * is a hole whatever its other registration's pin says, because the capture
331
331
  * credited its pushes to the live registration's run (routeLoaderLanes).
332
332
  *
333
333
  * A dependency the route does not register has no pin of its own, and the
334
334
  * record does not name the loader that ran it at capture: its copies stand
335
- * only while every `ssr: false` loader is pinned (exact when the pins are
335
+ * only while every `ssr: "await"` loader is pinned (exact when the pins are
336
336
  * all there or all gone).
337
337
  */
338
338
  export function loaderPins(
@@ -482,7 +482,7 @@ function recordOwnerKey(cachedLoaderId: string): () => string {
482
482
  * never run under a shell capture), so the lane is decided here, per LOADER,
483
483
  * never by the entry's loading():
484
484
  *
485
- * - BAKE lane: `loader(Def, { ssr: false })` (LoaderEntry `bake`: the
485
+ * - BAKE lane: `loader(Def, { ssr: "await" })` (LoaderEntry `bake`: the
486
486
  * caller passes a `bakeSegmentKey` for it on document and navigation
487
487
  * evaluations alike; a capture, a document evaluation, also has
488
488
  * awaitBeforeFlush). The loader EXECUTES at capture (the
@@ -504,7 +504,7 @@ function recordOwnerKey(cachedLoaderId: string): () => string {
504
504
  * (loader-mask.ts) and postpones at the reader's boundary — loading() or an
505
505
  * inline Suspense — then streams fresh per request. A masked read with NO
506
506
  * boundary above it root-postpones and the <body> sanity gate refuses the
507
- * shell (eternal-MISS warning). A route whose loaders are ALL `ssr: false`
507
+ * shell (eternal-MISS warning). A route whose loaders are ALL `ssr: "await"`
508
508
  * needs no loading(): nothing masks.
509
509
  */
510
510
  export function resolveLoaderData<TEnv>(
@@ -540,7 +540,7 @@ export function resolveLoaderData<TEnv>(
540
540
  return createMaskedLoaderPromise();
541
541
  }
542
542
 
543
- // `bake`: the pin is the `ssr: false` registration's. The caller's key is
543
+ // `bake`: the pin is the `ssr: "await"` registration's. The caller's key is
544
544
  // passed for an unflagged loader of an entry without loading() too.
545
545
  if (bakeSegmentKey && loaderEntry.bake) {
546
546
  const recorded = servedPins(reqCtx)?.get(loaderEntry.loader.$$id);
@@ -283,7 +283,7 @@ function collectDeferrable(
283
283
  for (const loaderEntry of entry.loader ?? []) {
284
284
  const own = counts(loaderEntry.prefetch);
285
285
  if (!own && behind.length === 0) continue;
286
- // An ssr: false loader on a ppr route is in scope too: whether this
286
+ // An ssr: "await" loader on a ppr route is in scope too: whether this
287
287
  // request serves it from a shell pin is per request (defersLoader).
288
288
  loaders.set(loaderEntry, own ? true : behind);
289
289
  }
@@ -472,7 +472,7 @@ export async function resolveOwnerLoaders<TEnv>(
472
472
  loaderEntry,
473
473
  ctx,
474
474
  ctx.pathname,
475
- // The bake key rides for flagged loaders too (`bake`): ssr: false
475
+ // The bake key rides for flagged loaders too (`bake`): ssr: "await"
476
476
  // bakes at capture regardless of the entry's loading() lane
477
477
  // (loader-cache.ts capture branch), its HIT-tail seed overlay needs
478
478
  // the same key, and on a navigation it pins exactly as on the
@@ -510,7 +510,7 @@ export async function resolveOwnerLoaders<TEnv>(
510
510
  ? buildLoaderErrorContext(ctx)
511
511
  : { ...buildLoaderErrorContext(ctx), isPartial: true };
512
512
 
513
- // FRESH: awaitBeforeFlush (loader(Def, { ssr: false })). A document render
513
+ // FRESH: awaitBeforeFlush (loader(Def, { ssr: "await" })). A document render
514
514
  // awaits these loaders before returning, so their data is settled, their
515
515
  // handle pushes beat the barrier snapshot, and a thrown notFound()'s status
516
516
  // write deterministically precedes Response construction. The ids must
@@ -554,7 +554,7 @@ export async function resolveOwnerLoaders<TEnv>(
554
554
  registerDeferredLoader(ctx, loader.$$id, shortCode, () =>
555
555
  kickoff(loaderEntry, segmentId),
556
556
  );
557
- // A ppr shell record this request restored keeps an ssr: false
557
+ // A ppr shell record this request restored keeps an ssr: "await"
558
558
  // loader's pushes as copies that wait for its run (loaderPins "hole").
559
559
  // No run is made here: drop them, the fill sends the run's pushes.
560
560
  _getRequestContext()?._handleStore?.settleLoaderRun(loader.$$id);
@@ -58,7 +58,7 @@ export interface SegmentResolutionDeps<TEnv = any> {
58
58
  env?: TEnv;
59
59
  isPartial?: boolean;
60
60
  },
61
- /** An `ssr: false` loader on a document or capture render. */
61
+ /** An `ssr: "await"` loader on a document or capture render. */
62
62
  useDefaultErrorBoundary?: boolean,
63
63
  ) => Promise<LoaderDataResult<T>>;
64
64
  trackHandler: <T>(
@@ -210,12 +210,12 @@ export const SHELL_CAPTURE_TASK_HARD_CAP_MS = 25_000;
210
210
  * (ShellCaptureDebugEvent.refusal, `caches.refusal` on a `router.prerender()`
211
211
  * result): `identity` (a request-scoped read: cookies(), headers(), the
212
212
  * theme, a `{ cache: false }` variable), `dynamic` (`ctx.dynamic()`),
213
- * `loader` (a `{ ssr: false }` loader rejected, settled with
213
+ * `loader` (a `{ ssr: "await" }` loader rejected, settled with
214
214
  * redirect()/notFound(), or a promise it pushed rejected),
215
215
  * `no-record` (no doc record: the route's cache() refused the write, or the
216
216
  * handler output failed), `handles` (a pushed
217
217
  * handle value did not finish encoding), `size` (over the store's value
218
- * limit, or `ssr: false` loader pins over `ppr.maxSnapshotBytes`: a
218
+ * limit, or `ssr: "await"` loader pins over `ppr.maxSnapshotBytes`: a
219
219
  * navigation-only entry was stored in place of the document shell),
220
220
  * `record-expired` (the route cache() record
221
221
  * the capture wrote ran out first), `invalidated` (a tag of the shell was
@@ -241,7 +241,7 @@ export type ShellCaptureRefusal =
241
241
  * rule: resolveLoaderData (router/segment-resolution/loader-cache.ts).
242
242
  */
243
243
  export const PPR_LANE_HINT: string =
244
- "Lane rule: only loader(Def, { ssr: false }) executes at capture and bakes its " +
244
+ 'Lane rule: only loader(Def, { ssr: "await" }) executes at capture and bakes its ' +
245
245
  "non-promise data; every other loader is live (masked at capture, fresh per " +
246
246
  "request) and needs loading() or an inline <Suspense> above its reader. See the " +
247
247
  '/ppr skill, "The loader lane rule" (node_modules/@rangojs/router/skills/ppr/SKILL.md).';
@@ -391,15 +391,15 @@ function warnNullCaptureOnce(
391
391
  (cause
392
392
  ? `Cause: ${cause}. Every capture waits for the handler layer to finish and ` +
393
393
  'bakes it; make the slow part cheaper (cache() or "use cache"), raise ' +
394
- "ppr.captureTimeout, or move it into a live loader (no ssr: false) read under " +
394
+ 'ppr.captureTimeout, or move it into a live loader (no ssr: "await") read under ' +
395
395
  "loading() or an inline <Suspense>.\n"
396
396
  : "Causes, told apart by whether the route ever flips to HIT:\n" +
397
397
  " 1. Cold-start warmup (dev module transform, or a cold worker): the capture raced " +
398
398
  "an unfinished shell render. This SELF-HEALS — the route flips to HIT once a later " +
399
399
  "request warms the modules.\n" +
400
400
  " 2. Something suspends above <body> with no Suspense boundary and never settles " +
401
- "within the capture window: a live loader (no ssr: false) read without loading() or " +
402
- "an inline <Suspense>, or a loader(Def, { ssr: false }) or top-level push (e.g. Meta) " +
401
+ 'within the capture window: a live loader (no ssr: "await") read without loading() or ' +
402
+ 'an inline <Suspense>, or a loader(Def, { ssr: "await" }) or top-level push (e.g. Meta) ' +
403
403
  "slower than ppr.captureTimeout. The boundary belongs to the entry or component " +
404
404
  "that owns the data.\n") +
405
405
  (pendingStacks && pendingStacks.length > 0
@@ -565,8 +565,8 @@ function warnSnapshotOverCapOnce(
565
565
  key,
566
566
  () =>
567
567
  `[rango] Shell capture for "${key}" recorded ${snapshotBytes} bytes of ` +
568
- `ssr: false loader pins, over the ${capBytes}-byte cap: the document ` +
569
- "shell was not stored; raise maxSnapshotBytes or shrink the ssr: false " +
568
+ `ssr: "await" loader pins, over the ${capBytes}-byte cap: the document ` +
569
+ 'shell was not stored; raise maxSnapshotBytes or shrink the ssr: "await" ' +
570
570
  "data. Documents of this route are a MISS until then; client " +
571
571
  "navigations still replay its handler layer.",
572
572
  );
@@ -599,9 +599,9 @@ function warnBakeCostOnce(key: string, source: string, ms: number): void {
599
599
  "of the route and occupies the per-isolate capture queue; the served " +
600
600
  "response never shows it. To keep it baked but cheap, wrap the work in " +
601
601
  'cache() or "use cache"; to make it per-request, load it in a live ' +
602
- "loader (no ssr: false) read under loading() or an inline <Suspense>; for " +
603
- "a loader(Def, { ssr: false }), return the slow part as a nested promise " +
604
- "or drop ssr: false. " +
602
+ 'loader (no ssr: "await") read under loading() or an inline <Suspense>; for ' +
603
+ 'a loader(Def, { ssr: "await" }), return the slow part as a nested promise ' +
604
+ 'or drop ssr: "await". ' +
605
605
  PPR_LANE_HINT,
606
606
  );
607
607
  }
@@ -1747,7 +1747,7 @@ export async function settleCaptureRecord(
1747
1747
  capture.key,
1748
1748
  `${describeLoaderPromisePush(rejected)} rejected during capture; a ` +
1749
1749
  "failed value must not bake into the shared shell. Catch it and push " +
1750
- "a fallback, or push it from a loader without ssr: false.",
1750
+ 'a fallback, or push it from a loader without ssr: "await".',
1751
1751
  PPR_LANE_HINT,
1752
1752
  );
1753
1753
  return { kind: "refused", refusal: "loader" };
@@ -2209,7 +2209,7 @@ export function deriveShellCaptureContext(
2209
2209
  // cache() record's restore (handle-snapshot.ts restoreHandles) lands
2210
2210
  // here too: a value it restores as a placeholder keeps that loader as
2211
2211
  // owner, so a HIT restores it as that loader's (pushRestored).
2212
- // - DSL-LOADER pushes. Only bake-lane { ssr: false } loaders and the
2212
+ // - DSL-LOADER pushes. Only bake-lane { ssr: "await" } loaders and the
2213
2213
  // loaders they await via ctx.use execute at capture (live loaders are
2214
2214
  // masked), their pushes are in the captured HTML (<head> title/meta,
2215
2215
  // useHandle echoes), and a HIT does not run a promise-free bake-lane
@@ -2547,7 +2547,7 @@ async function captureAndStoreShell(
2547
2547
  warnCaptureRefusedOnce(
2548
2548
  capture.key,
2549
2549
  `the loader for segment "${segmentKey}" rejected during capture; its error UI must not bake into the shared shell. ` +
2550
- "Fix the loader, or drop its ssr: false to move it to the live lane.",
2550
+ 'Fix the loader, or drop its ssr: "await" to move it to the live lane.',
2551
2551
  PPR_LANE_HINT,
2552
2552
  );
2553
2553
  return refuse(stats, "loader");
@@ -2564,7 +2564,7 @@ async function captureAndStoreShell(
2564
2564
  warnCaptureRefusedOnce(
2565
2565
  capture.key,
2566
2566
  `the loader for segment "${segmentKey}" settled with redirect()/notFound() during capture; a request-specific signal must not bake into the shared shell. ` +
2567
- "Drop its ssr: false to move it to the live lane, or move the decision into middleware.",
2567
+ 'Drop its ssr: "await" to move it to the live lane, or move the decision into middleware.',
2568
2568
  PPR_LANE_HINT,
2569
2569
  );
2570
2570
  return refuse(stats, "loader");
@@ -294,7 +294,7 @@ function wrapDefaultOutletContent(
294
294
  }
295
295
 
296
296
  /**
297
- * Per-loader stream map for read-site suspension. { ssr: false } loaders were
297
+ * Per-loader stream map for read-site suspension. { ssr: "await" } loaders were
298
298
  * awaited before flush (resolveOwnerLoaders), so deliver the SETTLED result, not the
299
299
  * settled promise: the read site decodes synchronously instead of use()ing a
300
300
  * Flight chunk whose fulfilled-at-read-time status is a scheduling race the
@@ -844,7 +844,7 @@ export async function renderSegments(
844
844
  // shell-cache slot-use: masked loaderData is what pins
845
845
  // "srv badge pending...") and, on reuse, leaves a stale stream
846
846
  // map that ignores the post-action aggregate (mini @cart).
847
- // Flagged (ssr:false) delivery still holds: when every owned
847
+ // Flagged (ssr: "await") delivery still holds: when every owned
848
848
  // loader paid the pre-flush await, settle the aggregate so
849
849
  // LoaderResolver decodes the array without a Flight-chunk use().
850
850
  const settleParallelAggregate = settlesLoaders(
@@ -860,7 +860,7 @@ export async function renderSegments(
860
860
  p.awaitedLoaderIds = undefined;
861
861
  if (segDebug) {
862
862
  segDebugLog(
863
- `segment ${id}: parallel ${p.id} loaders awaited (forceAwait/action/ssr:false)`,
863
+ `segment ${id}: parallel ${p.id} loaders awaited (forceAwait/action/ssr: "await")`,
864
864
  {
865
865
  loaderIds: p.loaderIds,
866
866
  ms: Math.round(performance.now() - parallelAwaitStart),
@@ -154,7 +154,7 @@ export type LoaderEntry = {
154
154
  cache?: EntryCacheConfig;
155
155
  /**
156
156
  * Document renders await this loader before segment resolution returns
157
- * (loader(Def, { ssr: false })), so its data, handle pushes, and
157
+ * (loader(Def, { ssr: "await" })), so its data, handle pushes, and
158
158
  * thrown notFound()/redirect() deterministically precede first flush.
159
159
  * Resolved at DSL-evaluation time from ctx.isSSR — entries are cached
160
160
  * per-isSSR (router/manifest.ts cache key), so the flag is already
@@ -164,7 +164,7 @@ export type LoaderEntry = {
164
164
  */
165
165
  awaitBeforeFlush?: true;
166
166
  /**
167
- * loader(Def, { ssr: false }) on every DSL evaluation, document and
167
+ * loader(Def, { ssr: "await" }) on every DSL evaluation, document and
168
168
  * navigation: the loader's PPR bake lane (loader-cache.ts
169
169
  * resolveLoaderData). The bake/seed key rides on it, so a client
170
170
  * navigation's shell replay pins exactly the loaders a document HIT pins,
@@ -913,7 +913,7 @@ export function getIsolatedLazyParent(
913
913
  * called from both.
914
914
  */
915
915
 
916
- /** loader(Def, { ssr: false }): a document awaits it before the first flush. */
916
+ /** loader(Def, { ssr: "await" }): a document awaits it before the first flush. */
917
917
  export function documentModeLoader(entry: LoaderEntry): LoaderEntry {
918
918
  return entry.bake ? { ...entry, awaitBeforeFlush: true } : entry;
919
919
  }
@@ -1213,11 +1213,11 @@ export function requestReadCaptureFix(
1213
1213
  capture:
1214
1214
  `The captured shell is served to every user of this URL, so ` +
1215
1215
  `request-scoped data read here would leak one user's ${what} to ` +
1216
- `others. Read it inside a loader without ssr: false and consume it ` +
1216
+ `others. Read it inside a loader without ssr: "await" and consume it ` +
1217
1217
  `with useLoader, e.g. createLoader(async () => ` +
1218
1218
  `getUser(cookies().get("session")?.value)). ${PPR_LANE_HINT}`,
1219
1219
  warning:
1220
- "Read it in a loader without ssr: false and consume it with useLoader " +
1220
+ 'Read it in a loader without ssr: "await" and consume it with useLoader ' +
1221
1221
  "under loading() or an inline <Suspense> (a live hole). A promise the " +
1222
1222
  "handler passes or pushes does not help: the capture waits for it.",
1223
1223
  };
@@ -195,7 +195,7 @@ export function guardRequestHeaders(request: Request): Request {
195
195
  /** The fix for a theme read that a cache() boundary or a ppr capture refuses. */
196
196
  const THEME_READ_FIX =
197
197
  "On ppr and cache() routes, read the theme with useTheme() in a client " +
198
- "component, or in a live loader (no ssr: false) with cookies().get(<storageKey>). " +
198
+ 'component, or in a live loader (no ssr: "await") with cookies().get(<storageKey>). ' +
199
199
  "The <html> theme class needs neither: the theme script sets it before paint. " +
200
200
  "See the /theme skill (node_modules/@rangojs/router/skills/theme/SKILL.md).";
201
201
 
@@ -863,7 +863,7 @@ export interface RequestContext<
863
863
 
864
864
  /**
865
865
  * @internal Loader IDs ($$id) whose entries carry `awaitBeforeFlush`
866
- * (loader(Def, { ssr: false })): segment resolution awaits these
866
+ * (loader(Def, { ssr: "await" })): segment resolution awaits these
867
867
  * before returning, so the render barrier cannot resolve until they settle.
868
868
  * rendered() checks this set to fail fast — a flagged loader awaiting the
869
869
  * barrier is a guaranteed cycle, not a race. Registered by resolveOwnerLoaders
package/src/ssr/index.tsx CHANGED
@@ -232,7 +232,7 @@ export interface SSRDependencies<TEnv = unknown> {
232
232
  *
233
233
  * When UNSET, createSSRHandler and createShellCaptureHandler auto-raise to
234
234
  * Number.MAX_SAFE_INTEGER for document renders and shell captures whose
235
- * matched chain has a `loader(Def, { ssr: false })` entry (payload segments
235
+ * matched chain has a `loader(Def, { ssr: "await" })` entry (payload segments
236
236
  * carry `awaitBeforeFlush`): the loader was awaited before first flush
237
237
  * precisely so its content ships in-place, and React 19.2's Fizz would
238
238
  * otherwise move any completed boundary over ~500 bytes to an end-of-stream
@@ -441,7 +441,7 @@ function trackDocumentClientLoads<T>(decode: () => T): {
441
441
  const SSR_FALSE_FIRST_FLUSH_TURNS = 3;
442
442
 
443
443
  /**
444
- * True when the matched chain has a `loader(Def, { ssr: false })` entry: its
444
+ * True when the matched chain has a `loader(Def, { ssr: "await" })` entry: its
445
445
  * payload segments carry `awaitBeforeFlush` (resolveOwnerLoaders).
446
446
  */
447
447
  function hasAwaitBeforeFlush(payload: {
@@ -453,7 +453,7 @@ function hasAwaitBeforeFlush(payload: {
453
453
  /**
454
454
  * The progressiveChunkSize renderHTML and captureShellHTML pass to Fizz (see
455
455
  * SSRDependencies.progressiveChunkSize): the explicit option, else the
456
- * ssr:false auto-raise.
456
+ * ssr: "await" auto-raise.
457
457
  */
458
458
  function documentChunkSize(
459
459
  explicit: number | undefined,
@@ -463,7 +463,7 @@ function documentChunkSize(
463
463
  }
464
464
 
465
465
  /**
466
- * ssr: false documents only: before the first flush, give Fizz the turns a
466
+ * ssr: "await" documents only: before the first flush, give Fizz the turns a
467
467
  * task waiting on an already-settling thenable needs, so a boundary that
468
468
  * waits on nothing else completes in place (#1138).
469
469
  *
@@ -953,7 +953,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
953
953
 
954
954
  const bootstrap = await resolveBootstrap(deps);
955
955
 
956
- // ssr:false auto-raise (see SSRDependencies.progressiveChunkSize).
956
+ // ssr: "await" auto-raise (see SSRDependencies.progressiveChunkSize).
957
957
  // Awaiting the payload here is latency-neutral: fizz cannot emit even
958
958
  // <html> until this same promise settles. A rejection lands in the
959
959
  // catch below — the same reportRenderError path a shell error takes.
@@ -1149,7 +1149,7 @@ export function createShellCaptureHandler<TEnv = unknown>(
1149
1149
  origin: opts.origin,
1150
1150
  });
1151
1151
 
1152
- // ssr:false auto-raise (SSRDependencies.progressiveChunkSize); resume()
1152
+ // ssr: "await" auto-raise (SSRDependencies.progressiveChunkSize); resume()
1153
1153
  // inherits it from the postponed state. A rejected or late payload
1154
1154
  // skips it and fails or degrades below as before.
1155
1155
  const awaitsBeforeFlush = await Promise.race([
@@ -150,7 +150,7 @@ function createSsrEventController(opts: {
150
150
  * Deserialize the Flight stream branch into the payload SsrRoot renders,
151
151
  * fragment expansion included. Factored out of SsrRoot so createSSRHandler and
152
152
  * createShellCaptureHandler can create the promise BEFORE fizz starts and read
153
- * metadata that must influence the fizz call itself (the ssr:false
153
+ * metadata that must influence the fizz call itself (the ssr: "await"
154
154
  * progressiveChunkSize auto-raise) — awaiting it there is latency-neutral
155
155
  * because fizz cannot emit even <html> until this same promise settles.
156
156
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dev-only diagnostic for the { ssr: false } SSR-completeness opt-in.
2
+ * Dev-only diagnostic for the { ssr: "await" } SSR-completeness opt-in.
3
3
  *
4
4
  * A document render that awaits flagged loaders before first flush is usually
5
5
  * expected to produce an SSR-complete boundary — but the await is scoped per
@@ -37,7 +37,7 @@ export function warnAwaitedSsrSuspension(
37
37
 
38
38
  if (awaitedLoaderIds.includes(loaderId)) {
39
39
  console.error(
40
- `[rango] Loader "${loaderId}" is flagged { ssr: false } and was awaited ` +
40
+ `[rango] Loader "${loaderId}" is flagged { ssr: "await" } and was awaited ` +
41
41
  `before first flush, but its useLoader read observed a still-pending ` +
42
42
  `promise during SSR. If its Suspense fallback appears in the SSR'd HTML, ` +
43
43
  `please report it to @rangojs/router with a reproduction.`,
@@ -46,12 +46,12 @@ export function warnAwaitedSsrSuspension(
46
46
  }
47
47
 
48
48
  console.warn(
49
- `[rango] The document render awaited { ssr: false } loader(s) ` +
49
+ `[rango] The document render awaited { ssr: "await" } loader(s) ` +
50
50
  `${JSON.stringify(awaitedLoaderIds)} before first flush, but ` +
51
51
  `useLoader("${loaderId}") still suspended during SSR. "${loaderId}" is not ` +
52
52
  `flagged, so the Suspense boundary above that read streams as its fallback ` +
53
53
  `and the content under it stays hidden until JS runs. If the boundary must ` +
54
- `be SSR-complete, flag "${loaderId}" with { ssr: false } too, or move its ` +
54
+ `be SSR-complete, flag "${loaderId}" with { ssr: "await" } too, or move its ` +
55
55
  `read behind its own boundary. If it streams deliberately, ignore this.`,
56
56
  );
57
57
  }
@@ -75,7 +75,7 @@ export type LoaderContext<
75
75
  * later ones stream to the client and apply post-hydration (document lane)
76
76
  * or progressively (navigation/action lanes). To guarantee a loader's
77
77
  * handles are in the SSR'd document, register it as
78
- * `loader(Def, { ssr: false })` so the document render awaits it
78
+ * `loader(Def, { ssr: "await" })` so the document render awaits it
79
79
  * (see {@link LoaderOptions}).
80
80
  *
81
81
  * @example
@@ -175,29 +175,29 @@ export type LoaderFn<
175
175
  /**
176
176
  * SSR delivery for a DSL-registered loader: `loader(Def, { ssr })`.
177
177
  *
178
- * Default (omitted / `true`): the loader streams on every render. Its data,
179
- * its `ctx.use(Handle)` pushes, and any `notFound()`/`redirect()` it throws
180
- * may land AFTER the document Response is constructed, so none of them are
181
- * guaranteed to be in the SSR'd HTML.
178
+ * Default (omitted / `"stream"`): the loader streams on every render. Its
179
+ * data, its `ctx.use(Handle)` pushes, and any `notFound()`/`redirect()` it
180
+ * throws may land AFTER the document Response is constructed, so none of
181
+ * them are guaranteed to be in the SSR'd HTML.
182
182
  *
183
- * `ssr: false` turns SSR streaming off for this loader — the same knob
184
- * `loading(fallback, { ssr: false })` is for the fallback: on a DOCUMENT
185
- * request the loader is awaited before first flush, so no fallback paints for
186
- * it (`useLoader` still suspends, but on an already-settled promise). Client
187
- * navigations keep streaming it. This is the SSR-completeness opt-in. Choose
188
- * it when the loader feeds something that must exist in the document:
189
- * `<head>` meta via a handle, or a real 404 status (an awaited `notFound()`
190
- * deterministically precedes Response construction, where the streamed
191
- * default only wins that race opportunistically). Under ppr it is also the
192
- * BAKE lane: the loader executes at shell capture and its settled return is
183
+ * `ssr: "await"` makes the server renders that produce HTML await this
184
+ * loader: on a DOCUMENT request the loader is awaited before first flush, so
185
+ * no fallback paints for it (`useLoader` still suspends, but on an
186
+ * already-settled promise); a ppr shell capture, at runtime or at build time
187
+ * for a Prerender + ppr route, awaits it too. Client navigations keep
188
+ * streaming it. This is the SSR-completeness opt-in. Choose it when the
189
+ * loader feeds something that must exist in the document: `<head>` meta via
190
+ * a handle, or a real 404 status (an awaited `notFound()` deterministically
191
+ * precedes Response construction, where the streamed default only wins
192
+ * that race opportunistically). Under ppr it is also the BAKE lane: the loader executes at shell capture and its settled return is
193
193
  * shell material (nested promises stay live holes), and so are its handle
194
194
  * pushes, a pushed promise or `.defer()` included: the capture awaits it and
195
195
  * every HIT serves the value it settled to (#1182). A pushed promise that
196
196
  * rejects, or misses `ppr.captureTimeout`, keeps the route on MISS. A value
197
- * that must stay per request belongs to a loader without `ssr: false` — see
197
+ * that must stay per request belongs to a loader without `ssr: "await"` — see
198
198
  * the ppr docs.
199
199
  *
200
- * Scoped per LOADER, not per segment: an `ssr: false` loader alongside a
200
+ * Scoped per LOADER, not per segment: an `ssr: "await"` loader alongside a
201
201
  * deliberately streaming sibling awaits only itself, and the sibling keeps
202
202
  * streaming behind its `loading()`/Suspense boundary. Watch the scoping when
203
203
  * the boundary must be SSR-complete: ONE unflagged read under it still
@@ -206,7 +206,7 @@ export type LoaderFn<
206
206
  * site naming the unflagged loader (ssr-suspension-warning.ts).
207
207
  */
208
208
  export type LoaderOptions = {
209
- ssr?: boolean;
209
+ ssr?: "stream" | "await";
210
210
  /**
211
211
  * `prefetch: false` keeps this loader out of every `<Link>` prefetch: a
212
212
  * prefetch (a partial request with `X-Rango-Prefetch`) does not execute
@@ -236,7 +236,7 @@ export type LoaderOptions = {
236
236
  * Limits: a handler that awaits the loader with `ctx.use()` makes it run in
237
237
  * the prefetch anyway; the loader cannot call `ctx.rendered()` (it throws
238
238
  * on every request); on a `ppr` route the flag is ignored for an
239
- * `ssr: false` loader (the bake lane), with a dev warning. In the fill
239
+ * `ssr: "await"` loader (the bake lane), with a dev warning. In the fill
240
240
  * request only deferred work and middleware run, so values a held handler
241
241
  * would have set with `ctx.set()` are not visible.
242
242
  */
@@ -164,7 +164,7 @@ export interface ResolvedSegment {
164
164
  loaderData?: any; // For loaders: the resolved data from loader execution
165
165
  /**
166
166
  * True when this loader was awaited before first flush
167
- * (loader(Def, { ssr: false })). Stamped by resolveOwnerLoaders on
167
+ * (loader(Def, { ssr: "await" })). Stamped by resolveOwnerLoaders on
168
168
  * document AND shell-capture renders — capture bakes flagged loaders and
169
169
  * awaits the same lane. Feeds segment-system's settled-value delivery and
170
170
  * the dev SSR suspension warning (ssr-suspension-warning.ts).
@@ -185,7 +185,7 @@ export interface ResolvedSegment {
185
185
  loaderIds?: string[]; // IDs ($$id) of loaders for this segment
186
186
  /**
187
187
  * Per-loader UNDECODED results for a layout/route stream map.
188
- * Flagged (ssr:false) entries are settled values; unflagged siblings
188
+ * Flagged (ssr: "await") entries are settled values; unflagged siblings
189
189
  * stay promises. Parallel slots do not use this channel — they pin
190
190
  * loading() / live-lane holes via use(loaderDataPromise).
191
191
  */
@@ -356,10 +356,10 @@ export type PathHelpers<TEnv> = {
356
356
  /**
357
357
  * Attach a data loader to the current route/layout.
358
358
  *
359
- * Pass `{ ssr: false }` — the same knob as loading(fallback, { ssr:
360
- * false }) — to await this loader before first flush on DOCUMENT requests
361
- * (see {@link LoaderOptions}): the opt-in for loaders whose data, handle
362
- * pushes, or thrown notFound()/redirect() must be in the SSR'd HTML.
359
+ * Pass `{ ssr: "await" }` to await this loader before first flush on
360
+ * DOCUMENT requests (see {@link LoaderOptions}): the opt-in for loaders
361
+ * whose data, handle pushes, or thrown notFound()/redirect() must be in the
362
+ * SSR'd HTML.
363
363
  * Per-loader: a streaming sibling keeps streaming. Under a `ppr` route the
364
364
  * flag BAKES: the loader executes at shell capture and its settled return
365
365
  * freezes into the shell (nested promises stay live holes) — the pre-flush
@@ -67,7 +67,7 @@ export interface PartialPrerenderProps {
67
67
  * Upper bound (serialized UTF-8 bytes) on the loader pins in the capture
68
68
  * data snapshot: the bake-lane loader records. The doc record (the handler
69
69
  * layer every HIT replays) is exempt. A document HIT is served only from an
70
- * entry that holds a pin for every `ssr: false` loader its HTML baked, so
70
+ * entry that holds a pin for every `ssr: "await"` loader its HTML baked, so
71
71
  * over the cap no document shell is stored: documents render in full
72
72
  * (MISS), and client navigations still replay the handler layer and run
73
73
  * those loaders fresh. Reported once per key. Defaults to 8 MiB. The whole
@@ -188,7 +188,7 @@ export async function runShellPrerenderPhase(
188
188
  injectRSCPayload: htmlStream.injectRSCPayload,
189
189
  headScripts: "preinit",
190
190
  // Build-captured shells honor the explicit outlining budget, or the
191
- // ssr:false auto-raise without one (createShellCaptureHandler): the
191
+ // ssr: "await" auto-raise without one (createShellCaptureHandler): the
192
192
  // capture value rides the postponed state into every runtime resume().
193
193
  ...(s.opts?.progressiveChunkSize !== undefined && {
194
194
  progressiveChunkSize: s.opts.progressiveChunkSize,
@@ -190,7 +190,7 @@ interface RangoBaseOptions {
190
190
  * which is why React outlines by default.
191
191
  *
192
192
  * When UNSET, document renders and PPR shell captures whose matched chain
193
- * has a `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
193
+ * has a `loader(Def, { ssr: "await" })` entry auto-raise to MAX_SAFE_INTEGER —
194
194
  * the loader was awaited before first flush precisely so its content ships
195
195
  * in-place, and outlining the boundary it feeds would defeat that. A shell
196
196
  * HIT inherits the captured value, so it matches the live render. Setting