@rangojs/router 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/cache/cf/cf-cache-store.d.ts +30 -16
- package/dist/types/cache/shell-snapshot.d.ts +2 -2
- package/dist/types/cache/types.d.ts +26 -7
- package/dist/types/client-urls/server-projection.d.ts +4 -4
- package/dist/types/client-urls/types.d.ts +13 -12
- package/dist/types/route-definition/helpers-types.d.ts +6 -5
- package/dist/types/router/segment-resolution/loader-mask.d.ts +1 -1
- package/dist/types/rsc/shell-capture.d.ts +14 -4
- package/dist/types/rsc/types.d.ts +8 -0
- package/dist/types/server/context.d.ts +1 -1
- package/dist/types/server/request-context.d.ts +1 -1
- package/dist/types/ssr/index.d.ts +16 -0
- package/dist/types/ssr/ssr-root.d.ts +5 -0
- package/dist/types/types/loader-types.d.ts +24 -22
- package/dist/types/urls/path-helper-types.d.ts +8 -7
- package/dist/vite/index.js +8 -5
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +2 -2
- package/skills/catalog.json +2 -2
- package/skills/client-urls/SKILL.md +9 -8
- package/skills/cloudflare/SKILL.md +5 -3
- package/skills/comparison/references/framework-comparison.md +2 -2
- package/skills/hooks/data.md +1 -1
- package/skills/hooks/handle-and-actions.md +1 -1
- package/skills/loader/SKILL.md +29 -28
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/migrate-react-router/component-migration.md +1 -1
- package/skills/migrate-react-router/data-and-actions.md +1 -1
- package/skills/migrate-react-router/route-mapping.md +1 -1
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +12 -8
- package/skills/rango/SKILL.md +20 -20
- package/skills/router-setup/SKILL.md +1 -1
- package/skills/scripts/SKILL.md +1 -1
- package/skills/shell-manifest/SKILL.md +1 -1
- package/src/browser/react/Link.tsx +27 -4
- package/src/cache/cf/cf-cache-store.ts +118 -62
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/types.ts +27 -7
- package/src/client-urls/client-urls.ts +12 -9
- package/src/client-urls/server-projection.ts +6 -6
- package/src/client-urls/types.ts +13 -12
- package/src/route-definition/dsl-helpers.ts +7 -3
- package/src/route-definition/helpers-types.ts +6 -5
- package/src/router/loader-resolution.ts +3 -3
- package/src/router/segment-resolution/fresh.ts +2 -2
- package/src/router/segment-resolution/loader-cache.ts +1 -1
- package/src/router/segment-resolution/loader-mask.ts +1 -1
- package/src/rsc/rsc-rendering.ts +10 -0
- package/src/rsc/shell-build-manifest.ts +13 -6
- package/src/rsc/shell-capture.ts +32 -5
- package/src/rsc/ssr-setup.ts +4 -0
- package/src/rsc/types.ts +18 -2
- package/src/server/context.ts +1 -1
- package/src/server/request-context.ts +1 -1
- package/src/ssr/index.tsx +22 -2
- package/src/ssr/ssr-root.tsx +17 -1
- package/src/types/loader-types.ts +21 -19
- package/src/urls/path-helper-types.ts +8 -7
package/src/rsc/rsc-rendering.ts
CHANGED
|
@@ -155,6 +155,7 @@ function createShellCaptureDescriptor(
|
|
|
155
155
|
ctx: HandlerContext<any>,
|
|
156
156
|
key: string,
|
|
157
157
|
searchSeed: string,
|
|
158
|
+
originSeed: string,
|
|
158
159
|
pprConfig: ResolvedPprConfig,
|
|
159
160
|
store: SegmentCacheStore<any>,
|
|
160
161
|
navigationOnly?: true,
|
|
@@ -162,6 +163,11 @@ function createShellCaptureDescriptor(
|
|
|
162
163
|
return {
|
|
163
164
|
key,
|
|
164
165
|
searchSeed,
|
|
166
|
+
// Derived from the same request URL as the key (the callers compute
|
|
167
|
+
// both next to each other, so they cannot drift): seeding the origin
|
|
168
|
+
// keeps origin-dependent static markup (Link's data-external)
|
|
169
|
+
// identical across capture, resume, and browser hydration.
|
|
170
|
+
originSeed,
|
|
165
171
|
buildVersion: ctx.version,
|
|
166
172
|
ttl: pprConfig.ttl,
|
|
167
173
|
swr: pprConfig.swr,
|
|
@@ -519,6 +525,7 @@ function* shellServePlan<TEnv>(
|
|
|
519
525
|
ctx,
|
|
520
526
|
key,
|
|
521
527
|
shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
528
|
+
url.origin,
|
|
522
529
|
pprConfig,
|
|
523
530
|
store,
|
|
524
531
|
);
|
|
@@ -1004,6 +1011,7 @@ function scheduleNavigationShellCapture<TEnv>(
|
|
|
1004
1011
|
partialCaptureKey ??
|
|
1005
1012
|
buildNavigationShellKey(url, reqCtx._searchParamsFilter),
|
|
1006
1013
|
shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
1014
|
+
url.origin,
|
|
1007
1015
|
pprConfig,
|
|
1008
1016
|
store,
|
|
1009
1017
|
true,
|
|
@@ -1524,6 +1532,8 @@ function serveShellHit(
|
|
|
1524
1532
|
// The shell key's own search — identical to the capture seed for
|
|
1525
1533
|
// this key, so the resume tree matches the captured tree.
|
|
1526
1534
|
search: shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
1535
|
+
// The HIT request's origin — same host as the capture's (key-scoped).
|
|
1536
|
+
origin: url.origin,
|
|
1527
1537
|
}),
|
|
1528
1538
|
);
|
|
1529
1539
|
};
|
|
@@ -306,20 +306,27 @@ export async function lookupBuildShell(
|
|
|
306
306
|
const entry = record.entry;
|
|
307
307
|
if (record.tags && record.tags.length > 0) {
|
|
308
308
|
const check = store.isTagsInvalidatedSince;
|
|
309
|
-
if (typeof check !== "function") {
|
|
309
|
+
if (typeof check !== "function" || store.tagHistoryInert) {
|
|
310
310
|
// A tagged build entry on a store that cannot answer "was this tag
|
|
311
311
|
// invalidated since the build" must not serve: updateTag() could
|
|
312
|
-
// never evict it.
|
|
312
|
+
// never evict it. That covers a store missing the method AND one
|
|
313
|
+
// whose answers have no durable history behind them
|
|
314
|
+
// (SegmentCacheStore.tagHistoryInert — a KV-less CFCacheStore's
|
|
315
|
+
// memo-only answers would let the immutable asset resurrect on the
|
|
316
|
+
// next request). Declared intent that cannot be honored deserves a
|
|
313
317
|
// diagnostic; the route keeps runtime-capture semantics.
|
|
314
318
|
const key = buildShellManifestKey(url.pathname);
|
|
315
319
|
if (!warnedTagCheckUnsupported.has(key)) {
|
|
316
320
|
warnedTagCheckUnsupported.add(key);
|
|
317
321
|
console.warn(
|
|
318
322
|
`[rango] Build-time shell for "${url.pathname}" carries cache tags, but ` +
|
|
319
|
-
"the app cache store
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
+
"the app cache store cannot answer tag-invalidation history durably " +
|
|
324
|
+
(typeof check !== "function"
|
|
325
|
+
? "(isTagsInvalidatedSince() is not implemented), "
|
|
326
|
+
: "(no durable tag history — a CFCacheStore without a KV namespace), ") +
|
|
327
|
+
"so updateTag() could not evict it. The entry is not served; the route " +
|
|
328
|
+
"keeps runtime shell capture. Use MemorySegmentCacheStore, a KV-backed " +
|
|
329
|
+
"CFCacheStore, or VercelCacheStore (or add the method to your custom store).",
|
|
323
330
|
);
|
|
324
331
|
}
|
|
325
332
|
return null;
|
package/src/rsc/shell-capture.ts
CHANGED
|
@@ -509,9 +509,10 @@ export interface ShellCaptureDebugEvent {
|
|
|
509
509
|
* the capture was not attempted
|
|
510
510
|
* - skip-capacity: the isolate capture queue is full; a later request may retry
|
|
511
511
|
* - skip-inert-store: the resolved store's shell family is missing or
|
|
512
|
-
* declared inert (SegmentCacheStore.shellFamilyInert — a
|
|
513
|
-
*
|
|
514
|
-
* background render was not
|
|
512
|
+
* declared inert (SegmentCacheStore.shellFamilyInert — a custom store
|
|
513
|
+
* whose backing tier is unavailable; built-in stores never declare it);
|
|
514
|
+
* nothing could store the result, so the background render was not
|
|
515
|
+
* scheduled at all
|
|
515
516
|
* - skip-queue-timeout: the capture waited past CAPTURE_QUEUE_WAIT_BUDGET_MS
|
|
516
517
|
* behind other captures and was dropped unrun (no backoff — the route is
|
|
517
518
|
* not doomed, the isolate was busy; a later request re-probes). Carries
|
|
@@ -554,7 +555,7 @@ export interface ShellCaptureDebugEvent {
|
|
|
554
555
|
/** A bake-lane loader settled into a shell that uses TTL/SWR-only invalidation. */
|
|
555
556
|
untaggedBake?: true;
|
|
556
557
|
/** Outcome reported by a store that supports shell-write acknowledgements. */
|
|
557
|
-
storeWrite?: "stored" | "invalidated";
|
|
558
|
+
storeWrite?: "stored" | "invalidated" | "uncacheable";
|
|
558
559
|
/** Consecutive failure count in the key's backoff entry, when one exists. */
|
|
559
560
|
backoffFailures?: number;
|
|
560
561
|
/** Ms remaining in the key's backoff window, when one exists. */
|
|
@@ -916,6 +917,15 @@ export interface ShellCaptureDescriptor {
|
|
|
916
917
|
* Omitted (build-time bare-pathname captures) = search-less, same as "".
|
|
917
918
|
*/
|
|
918
919
|
searchSeed?: string;
|
|
920
|
+
/**
|
|
921
|
+
* The scheduling request's origin, seeding the capture render's SSR store
|
|
922
|
+
* location. Shell keys are host-scoped, so the resume pass's request
|
|
923
|
+
* agrees modulo protocol drift; the seed keeps origin-dependent static
|
|
924
|
+
* markup (Link's data-external) identical across capture, resume, and
|
|
925
|
+
* browser hydration. Omitted (build-time host-agnostic captures) falls
|
|
926
|
+
* back to the internal host.
|
|
927
|
+
*/
|
|
928
|
+
originSeed?: string;
|
|
919
929
|
/**
|
|
920
930
|
* The RSC handler's build version (HandlerContext.version), stamped into the
|
|
921
931
|
* stored entry as ShellCacheEntry.buildVersion — the serve-side
|
|
@@ -1005,7 +1015,8 @@ export function scheduleShellCapture(
|
|
|
1005
1015
|
// serialized queue (a promise-heavy route bakes for seconds per MISS),
|
|
1006
1016
|
// starving captures that CAN store. Skip scheduling entirely when the
|
|
1007
1017
|
// resolved store (same defaulting as captureAndStoreShell) has no shell
|
|
1008
|
-
// family or declared it inert (
|
|
1018
|
+
// family or declared it inert (a custom-store escape hatch; built-in
|
|
1019
|
+
// stores never declare it — a KV-less CFCacheStore stores L1-only).
|
|
1009
1020
|
const scheduledStore = descriptor.store ?? reqCtx._cacheStore;
|
|
1010
1021
|
if (!scheduledStore?.putShell || scheduledStore.shellFamilyInert) {
|
|
1011
1022
|
publishCaptureDebugEvent(descriptor, { key, outcome: "skip-inert-store" });
|
|
@@ -1765,6 +1776,7 @@ async function captureAndStoreShell(
|
|
|
1765
1776
|
// The shell key's own search seeds the capture render's store —
|
|
1766
1777
|
// static-part search reads bake what the key names.
|
|
1767
1778
|
search: capture.searchSeed,
|
|
1779
|
+
origin: capture.originSeed,
|
|
1768
1780
|
}),
|
|
1769
1781
|
);
|
|
1770
1782
|
} catch (error) {
|
|
@@ -2028,6 +2040,21 @@ async function captureAndStoreShell(
|
|
|
2028
2040
|
);
|
|
2029
2041
|
return "refused";
|
|
2030
2042
|
}
|
|
2043
|
+
if (storeWrite === "uncacheable") {
|
|
2044
|
+
// The store declared the entry unstorable under its current
|
|
2045
|
+
// configuration — every retry would refuse identically, so back
|
|
2046
|
+
// the key off like any refused capture instead of burning a full
|
|
2047
|
+
// serialized render per MISS. Store-neutral on purpose: the
|
|
2048
|
+
// acknowledging store emits its own diagnostic naming the specific
|
|
2049
|
+
// limit (CFCacheStore warns about the over-limit Cache-Tag set).
|
|
2050
|
+
warnCaptureRefusedOnce(
|
|
2051
|
+
capture.key,
|
|
2052
|
+
"the store cannot cache this shell under its current configuration and acknowledged " +
|
|
2053
|
+
"the write as permanently refusable, so the key is backed off. See the cache store's " +
|
|
2054
|
+
"own warning for the specific limit and remedy.",
|
|
2055
|
+
);
|
|
2056
|
+
return "refused";
|
|
2057
|
+
}
|
|
2031
2058
|
} catch (error) {
|
|
2032
2059
|
// Best-effort: a failed put must never throw out of the background task.
|
|
2033
2060
|
reportCacheError(
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -120,6 +120,10 @@ export function createSsrHtmlStage<TEnv>(
|
|
|
120
120
|
...options.render,
|
|
121
121
|
streamMode,
|
|
122
122
|
search: options.url.search,
|
|
123
|
+
// origin: same out-of-band channel — seeds the SSR store location
|
|
124
|
+
// so origin-dependent markup (Link's data-external) agrees with the
|
|
125
|
+
// browser's window.location across hydration.
|
|
126
|
+
origin: options.url.origin,
|
|
123
127
|
}),
|
|
124
128
|
...(options.init && { init: options.init }),
|
|
125
129
|
};
|
package/src/rsc/types.ts
CHANGED
|
@@ -187,6 +187,12 @@ export interface SSRRenderOptions {
|
|
|
187
187
|
* search; see ShellCaptureOptions.search in the SSR entry).
|
|
188
188
|
*/
|
|
189
189
|
search?: string;
|
|
190
|
+
/**
|
|
191
|
+
* The live request's origin (`url.origin`), seeding the SSR store
|
|
192
|
+
* location so origin-dependent markup (Link's data-external) agrees with
|
|
193
|
+
* the browser across hydration. Absent on host-agnostic build captures.
|
|
194
|
+
*/
|
|
195
|
+
origin?: string;
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
/**
|
|
@@ -208,7 +214,12 @@ export interface SSRModule {
|
|
|
208
214
|
*/
|
|
209
215
|
captureShellHTML?: (
|
|
210
216
|
rscStream: ReadableStream<Uint8Array>,
|
|
211
|
-
options: {
|
|
217
|
+
options: {
|
|
218
|
+
quiesce: Promise<void>;
|
|
219
|
+
maxWaitMs?: number;
|
|
220
|
+
search?: string;
|
|
221
|
+
origin?: string;
|
|
222
|
+
},
|
|
212
223
|
) => Promise<{ prelude: Uint8Array; postponed: string | null } | null>;
|
|
213
224
|
|
|
214
225
|
/**
|
|
@@ -221,7 +232,12 @@ export interface SSRModule {
|
|
|
221
232
|
*/
|
|
222
233
|
resumeShellHTML?: (
|
|
223
234
|
rscStream: ReadableStream<Uint8Array>,
|
|
224
|
-
options: {
|
|
235
|
+
options: {
|
|
236
|
+
postponed: string | null;
|
|
237
|
+
nonce?: string;
|
|
238
|
+
search?: string;
|
|
239
|
+
origin?: string;
|
|
240
|
+
},
|
|
225
241
|
) => Promise<ReadableStream<Uint8Array>>;
|
|
226
242
|
}
|
|
227
243
|
|
package/src/server/context.ts
CHANGED
|
@@ -108,7 +108,7 @@ export type LoaderEntry = {
|
|
|
108
108
|
cache?: EntryCacheConfig;
|
|
109
109
|
/**
|
|
110
110
|
* Document renders await this loader before segment resolution returns
|
|
111
|
-
* (loader(Def, {
|
|
111
|
+
* (loader(Def, { ssr: false })), so its data, handle pushes, and
|
|
112
112
|
* thrown notFound()/redirect() deterministically precede first flush.
|
|
113
113
|
* Resolved at DSL-evaluation time from ctx.isSSR — entries are cached
|
|
114
114
|
* per-isSSR (router/manifest.ts cache key), so the flag is already
|
|
@@ -620,7 +620,7 @@ export interface RequestContext<
|
|
|
620
620
|
|
|
621
621
|
/**
|
|
622
622
|
* @internal Loader IDs ($$id) whose entries carry `awaitBeforeFlush`
|
|
623
|
-
* (loader(Def, {
|
|
623
|
+
* (loader(Def, { ssr: false })): segment resolution awaits these
|
|
624
624
|
* before returning, so the render barrier cannot resolve until they settle.
|
|
625
625
|
* rendered() checks this set to fail fast — a flagged loader awaiting the
|
|
626
626
|
* barrier is a guaranteed cycle, not a race. Registered by resolveLoaders
|
package/src/ssr/index.tsx
CHANGED
|
@@ -114,6 +114,14 @@ export interface SSRRenderOptions {
|
|
|
114
114
|
* only (runtime captures own the search variants, seeded per key).
|
|
115
115
|
*/
|
|
116
116
|
search?: string;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The live request's origin, seeding the SSR store location so
|
|
120
|
+
* origin-dependent markup (Link's data-external) agrees with the
|
|
121
|
+
* browser's window.location across hydration. Absent on the build-time
|
|
122
|
+
* prerender pass (host-agnostic bare captures).
|
|
123
|
+
*/
|
|
124
|
+
origin?: string;
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
/**
|
|
@@ -363,6 +371,13 @@ interface ShellCaptureOptions {
|
|
|
363
371
|
* the shell's own key. MUST equal the resume pass's seed for the same key.
|
|
364
372
|
*/
|
|
365
373
|
search?: string;
|
|
374
|
+
/**
|
|
375
|
+
* The capture request's origin. Shell keys are host-scoped, so the resume
|
|
376
|
+
* pass's request agrees modulo protocol drift; seeding it keeps
|
|
377
|
+
* origin-dependent static markup (Link's data-external) identical across
|
|
378
|
+
* capture, resume, and browser hydration.
|
|
379
|
+
*/
|
|
380
|
+
origin?: string;
|
|
366
381
|
}
|
|
367
382
|
|
|
368
383
|
/**
|
|
@@ -391,6 +406,8 @@ interface ShellResumeOptions {
|
|
|
391
406
|
* postponed holes.
|
|
392
407
|
*/
|
|
393
408
|
search?: string;
|
|
409
|
+
/** The HIT request's origin — same host as the capture's (key-scoped). */
|
|
410
|
+
origin?: string;
|
|
394
411
|
}
|
|
395
412
|
|
|
396
413
|
/**
|
|
@@ -472,7 +489,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
472
489
|
rscStream: ReadableStream<Uint8Array>,
|
|
473
490
|
options?: SSRRenderOptions,
|
|
474
491
|
): Promise<ReadableStream<Uint8Array>> {
|
|
475
|
-
const { nonce, formState, streamMode, search } = options ?? {};
|
|
492
|
+
const { nonce, formState, streamMode, search, origin } = options ?? {};
|
|
476
493
|
|
|
477
494
|
try {
|
|
478
495
|
// Tee the stream:
|
|
@@ -490,6 +507,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
490
507
|
// same key => same seed, so the resume tree matches the captured
|
|
491
508
|
// tree while static-part search reads render what the key names.
|
|
492
509
|
search,
|
|
510
|
+
origin,
|
|
493
511
|
});
|
|
494
512
|
|
|
495
513
|
// Get bootstrap script content
|
|
@@ -594,6 +612,7 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
594
612
|
// The shell key's own search: static-part search reads render what
|
|
595
613
|
// the key names, and the resume pass seeds the identical string.
|
|
596
614
|
search: opts.search,
|
|
615
|
+
origin: opts.origin,
|
|
597
616
|
});
|
|
598
617
|
|
|
599
618
|
// Bootstrap load raced against the deadline. A load that never resolves
|
|
@@ -767,7 +786,7 @@ export function createShellResumeHandler<TEnv = unknown>(
|
|
|
767
786
|
rscStream: ReadableStream<Uint8Array>,
|
|
768
787
|
opts: ShellResumeOptions,
|
|
769
788
|
): Promise<ReadableStream<Uint8Array>> {
|
|
770
|
-
const { postponed, nonce, search } = opts;
|
|
789
|
+
const { postponed, nonce, search, origin } = opts;
|
|
771
790
|
|
|
772
791
|
try {
|
|
773
792
|
if (postponed === null) {
|
|
@@ -800,6 +819,7 @@ export function createShellResumeHandler<TEnv = unknown>(
|
|
|
800
819
|
// Same seed as the capture pass for this key: the resume tree must
|
|
801
820
|
// match the captured tree above the holes, search reads included.
|
|
802
821
|
search,
|
|
822
|
+
origin,
|
|
803
823
|
});
|
|
804
824
|
|
|
805
825
|
// EAGER injection (resume-only): the stored prelude — a complete document
|
package/src/ssr/ssr-root.tsx
CHANGED
|
@@ -70,13 +70,22 @@ function createSsrEventController(opts: {
|
|
|
70
70
|
* names and both passes agree byte-for-byte.
|
|
71
71
|
*/
|
|
72
72
|
search?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Origin seeding the store location. Live fizz passes the request's real
|
|
75
|
+
* origin so origin-dependent markup (Link's data-external compares link
|
|
76
|
+
* origins against it) agrees with the browser's window.location; ppr
|
|
77
|
+
* capture/resume pass their request's origin (shell keys are host-scoped,
|
|
78
|
+
* so the two agree modulo protocol drift). Absent (build-time bare
|
|
79
|
+
* captures, tests) falls back to the historical internal host.
|
|
80
|
+
*/
|
|
81
|
+
origin?: string;
|
|
73
82
|
params?: Record<string, string>;
|
|
74
83
|
handleData?: HandleData;
|
|
75
84
|
matched?: string[];
|
|
76
85
|
}): EventController {
|
|
77
86
|
const location = new URL(
|
|
78
87
|
`${opts.pathname}${opts.search ?? ""}`,
|
|
79
|
-
"http://localhost",
|
|
88
|
+
opts.origin ?? "http://localhost",
|
|
80
89
|
);
|
|
81
90
|
let params = opts.params ?? {};
|
|
82
91
|
const rawMatched = opts.matched ?? [];
|
|
@@ -162,6 +171,11 @@ export interface SsrRootOptions {
|
|
|
162
171
|
* the resume tree matches the captured tree above the postponed holes.
|
|
163
172
|
*/
|
|
164
173
|
search?: string;
|
|
174
|
+
/**
|
|
175
|
+
* Origin seeding the SSR store location (see createSsrEventController):
|
|
176
|
+
* live fizz passes the request's origin; capture/resume pass theirs.
|
|
177
|
+
*/
|
|
178
|
+
origin?: string;
|
|
165
179
|
}
|
|
166
180
|
|
|
167
181
|
/**
|
|
@@ -188,6 +202,7 @@ export function createSsrRootComponent(opts: SsrRootOptions): React.FC {
|
|
|
188
202
|
nonce,
|
|
189
203
|
onPayloadSettled,
|
|
190
204
|
search,
|
|
205
|
+
origin,
|
|
191
206
|
} = opts;
|
|
192
207
|
|
|
193
208
|
let payload: Promise<RscPayload> | undefined;
|
|
@@ -238,6 +253,7 @@ export function createSsrRootComponent(opts: SsrRootOptions): React.FC {
|
|
|
238
253
|
eventController: createSsrEventController({
|
|
239
254
|
pathname,
|
|
240
255
|
search,
|
|
256
|
+
origin,
|
|
241
257
|
params: resolved.metadata?.params,
|
|
242
258
|
handleData,
|
|
243
259
|
matched: resolved.metadata?.matched,
|
|
@@ -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, {
|
|
78
|
+
* `loader(Def, { ssr: false })` so the document render awaits it
|
|
79
79
|
* (see {@link LoaderOptions}).
|
|
80
80
|
*
|
|
81
81
|
* @example
|
|
@@ -174,29 +174,31 @@ export type LoaderFn<
|
|
|
174
174
|
> = (ctx: LoaderContext<TParams, TEnv>) => Promise<T> | T;
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
|
-
*
|
|
177
|
+
* SSR delivery for a DSL-registered loader: `loader(Def, { ssr })`.
|
|
178
178
|
*
|
|
179
|
-
* Default (omitted): the loader streams on every render. Its data,
|
|
180
|
-
* `ctx.use(Handle)` pushes, and any `notFound()`/`redirect()` it throws
|
|
181
|
-
* AFTER the document Response is constructed, so none of them are
|
|
182
|
-
* be in the SSR'd HTML.
|
|
179
|
+
* Default (omitted / `true`): the loader streams on every render. Its data,
|
|
180
|
+
* its `ctx.use(Handle)` pushes, and any `notFound()`/`redirect()` it throws
|
|
181
|
+
* may land AFTER the document Response is constructed, so none of them are
|
|
182
|
+
* guaranteed to be in the SSR'd HTML.
|
|
183
183
|
*
|
|
184
|
-
* `
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
184
|
+
* `ssr: false` turns SSR streaming off for this loader — the same knob
|
|
185
|
+
* `loading(fallback, { ssr: false })` is for the fallback: on a DOCUMENT
|
|
186
|
+
* request the loader is awaited before first flush, so no fallback paints for
|
|
187
|
+
* it (`useLoader` still suspends, but on an already-settled promise). Client
|
|
188
|
+
* navigations keep streaming it. This is the SSR-completeness opt-in. Choose
|
|
189
|
+
* it when the loader feeds something that must exist in the document:
|
|
190
|
+
* `<head>` meta via a handle, or a real 404 status (an awaited `notFound()`
|
|
191
|
+
* deterministically precedes Response construction, where the streamed
|
|
192
|
+
* default only wins that race opportunistically). Under ppr it is also the
|
|
193
|
+
* BAKE lane: the loader executes at shell capture and its settled return is
|
|
194
|
+
* shell material (nested promises stay live holes) — see the ppr docs.
|
|
193
195
|
*
|
|
194
|
-
* Scoped per LOADER, not per segment:
|
|
195
|
-
*
|
|
196
|
-
* `loading()`/Suspense boundary.
|
|
196
|
+
* Scoped per LOADER, not per segment: an `ssr: false` loader alongside a
|
|
197
|
+
* deliberately streaming sibling awaits only itself, and the sibling keeps
|
|
198
|
+
* streaming behind its `loading()`/Suspense boundary.
|
|
197
199
|
*/
|
|
198
200
|
export type LoaderOptions = {
|
|
199
|
-
|
|
201
|
+
ssr?: boolean;
|
|
200
202
|
};
|
|
201
203
|
|
|
202
204
|
/**
|
|
@@ -347,13 +347,14 @@ export type PathHelpers<TEnv> = {
|
|
|
347
347
|
/**
|
|
348
348
|
* Attach a data loader to the current route/layout.
|
|
349
349
|
*
|
|
350
|
-
* Pass `{
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* holes) — the pre-flush
|
|
350
|
+
* Pass `{ ssr: false }` — the same knob as loading(fallback, { ssr:
|
|
351
|
+
* false }) — to await this loader before first flush on DOCUMENT requests
|
|
352
|
+
* (see {@link LoaderOptions}): the opt-in for loaders whose data, handle
|
|
353
|
+
* pushes, or thrown notFound()/redirect() must be in the SSR'd HTML.
|
|
354
|
+
* Per-loader: a streaming sibling keeps streaming. Under a `ppr` route the
|
|
355
|
+
* flag BAKES: the loader executes at shell capture and its settled return
|
|
356
|
+
* freezes into the shell (nested promises stay live holes) — the pre-flush
|
|
357
|
+
* promise applied to the prelude.
|
|
357
358
|
*/
|
|
358
359
|
loader: <TData>(
|
|
359
360
|
loaderDef: LoaderDefinition<TData>,
|