@rangojs/router 0.16.0 → 0.17.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/README.md +254 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-scope.d.ts +34 -5
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +53 -13
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +15 -3
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +3 -2
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
- package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
- package/dist/types/rsc/shell-capture.d.ts +5 -4
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +10 -0
- package/dist/types/server/context.d.ts +33 -3
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +24 -21
- package/dist/types/ssr/index.d.ts +15 -0
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/vite/index.js +1381 -1346
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +65 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +207 -97
- package/skills/caching/SKILL.md +295 -93
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +53 -31
- package/skills/cloudflare/SKILL.md +72 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +39 -33
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +2 -1
- package/skills/deployment-caching/SKILL.md +6 -1
- package/skills/document-cache/SKILL.md +92 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +53 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +31 -16
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +37 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +242 -106
- package/skills/middleware/SKILL.md +119 -23
- package/skills/migrate-nextjs/SKILL.md +100 -45
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +49 -38
- package/skills/migrate-react-router/component-migration.md +25 -6
- package/skills/migrate-react-router/data-and-actions.md +18 -5
- package/skills/migrate-react-router/route-mapping.md +12 -6
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +28 -14
- package/skills/parallel/SKILL.md +75 -39
- package/skills/ppr/SKILL.md +345 -150
- package/skills/prerender/SKILL.md +189 -100
- package/skills/rango/SKILL.md +65 -34
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +165 -60
- package/skills/router-setup/SKILL.md +418 -240
- package/skills/scripts/SKILL.md +31 -20
- package/skills/server-actions/SKILL.md +220 -59
- package/skills/shell-manifest/SKILL.md +12 -7
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +68 -32
- package/skills/testing/cache-prerender.md +55 -29
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +8 -5
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +106 -29
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +126 -47
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +200 -82
- package/skills/vercel/SKILL.md +21 -11
- package/skills/view-transitions/SKILL.md +41 -20
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +1 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +1 -6
- package/src/cache/cache-runtime.ts +230 -61
- package/src/cache/cache-scope.ts +76 -12
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +132 -28
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/tag-invalidation.ts +4 -3
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +16 -3
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +117 -30
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +34 -25
- package/src/router/intercept-resolution.ts +125 -39
- package/src/router/loader-resolution.ts +56 -11
- package/src/router/match-api.ts +12 -17
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +93 -10
- package/src/router/match-middleware/cache-store.ts +17 -90
- package/src/router/middleware-types.ts +3 -5
- package/src/router/middleware.ts +52 -9
- package/src/router/prerender-match.ts +53 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +44 -12
- package/src/router/segment-resolution/loader-cache.ts +205 -45
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +6 -6
- package/src/router/segment-resolution/mask-nested.ts +7 -7
- package/src/router/segment-resolution/revalidation.ts +34 -6
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +8 -45
- package/src/rsc/shell-capture-constants.ts +13 -0
- package/src/rsc/shell-capture.ts +111 -90
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +11 -0
- package/src/server/context.ts +88 -11
- package/src/server/cookie-store.ts +5 -4
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +35 -30
- package/src/ssr/index.tsx +40 -7
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +8 -6
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
package/src/rsc/rsc-rendering.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* RSC Rendering Handler (Navigation)
|
|
3
3
|
*
|
|
4
4
|
* Handles RSC rendering for both partial (client-side navigation) and full
|
|
5
|
-
* (initial page load) requests.
|
|
6
|
-
* static generation.
|
|
5
|
+
* (initial page load) requests.
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
import {
|
|
@@ -394,8 +393,8 @@ function* requestRenderPlan<TEnv>(
|
|
|
394
393
|
const shell = yield* scope("shell-serve", shellServePlan(input));
|
|
395
394
|
if (shell.kind === "serve") return shell.response;
|
|
396
395
|
|
|
397
|
-
// Match + payload. A control response (redirect
|
|
398
|
-
//
|
|
396
|
+
// Match + payload. A control response (redirect) ends the plan before any
|
|
397
|
+
// rendering.
|
|
399
398
|
const prepared = yield* scope(
|
|
400
399
|
isPartial ? "prepare:partial" : "prepare:full",
|
|
401
400
|
preparePayloadPlan(input),
|
|
@@ -470,7 +469,6 @@ function* shellServePlan<TEnv>(
|
|
|
470
469
|
if (
|
|
471
470
|
isPartial ||
|
|
472
471
|
request.method !== "GET" ||
|
|
473
|
-
url.searchParams.has("__prerender_collect") ||
|
|
474
472
|
isRscRequest(request, url, false) ||
|
|
475
473
|
reqCtx._dynamic
|
|
476
474
|
) {
|
|
@@ -639,7 +637,7 @@ function* shellServePlan<TEnv>(
|
|
|
639
637
|
|
|
640
638
|
/**
|
|
641
639
|
* Match the request and assemble the RSC payload, or produce a control
|
|
642
|
-
* response (redirect
|
|
640
|
+
* response (redirect) that ends the request plan.
|
|
643
641
|
*/
|
|
644
642
|
function* preparePayloadPlan<TEnv>(
|
|
645
643
|
input: RequestRenderInput<TEnv>,
|
|
@@ -750,13 +748,6 @@ function* preparePayloadPlan<TEnv>(
|
|
|
750
748
|
// Caching is now handled in router.match() via cache provider in request context
|
|
751
749
|
// match.segments already contains cached or fresh segments as appropriate
|
|
752
750
|
|
|
753
|
-
if (url.searchParams.has("__prerender_collect")) {
|
|
754
|
-
const response = yield* step("prerender-collect", () =>
|
|
755
|
-
collectPrerenderArtifacts(match, handleStore),
|
|
756
|
-
);
|
|
757
|
-
return { kind: "control", response };
|
|
758
|
-
}
|
|
759
|
-
|
|
760
751
|
return {
|
|
761
752
|
kind: "payload",
|
|
762
753
|
payload: buildFullPayload(match, ctx, url, reqCtx, handleStore),
|
|
@@ -869,38 +860,6 @@ function mirrorPprServerTimingsForDev(
|
|
|
869
860
|
}
|
|
870
861
|
}
|
|
871
862
|
|
|
872
|
-
/**
|
|
873
|
-
* Build-time prerender collection: serialize segments and handle data
|
|
874
|
-
* to JSON for storage as build artifacts. At runtime the worker
|
|
875
|
-
* deserializes these and feeds them through the normal segment pipeline.
|
|
876
|
-
*/
|
|
877
|
-
async function collectPrerenderArtifacts<TEnv>(
|
|
878
|
-
match: Awaited<ReturnType<HandlerContext<TEnv>["router"]["match"]>>,
|
|
879
|
-
handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
|
|
880
|
-
): Promise<Response> {
|
|
881
|
-
const nonLoaderSegments = match.segments.filter((s) => s.type !== "loader");
|
|
882
|
-
handleStore.seal();
|
|
883
|
-
await handleStore.settled;
|
|
884
|
-
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
885
|
-
const serializedSegments = await serializeSegments(nonLoaderSegments);
|
|
886
|
-
const handles: Record<string, Record<string, unknown[]>> = {};
|
|
887
|
-
for (const seg of nonLoaderSegments) {
|
|
888
|
-
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
889
|
-
if (Object.keys(segHandles).length > 0) {
|
|
890
|
-
handles[seg.id] = segHandles;
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
return new Response(
|
|
894
|
-
JSON.stringify({
|
|
895
|
-
segments: serializedSegments,
|
|
896
|
-
handles,
|
|
897
|
-
routeName: match.routeName,
|
|
898
|
-
params: match.params,
|
|
899
|
-
}),
|
|
900
|
-
{ headers: { "Content-Type": "application/json" } },
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
|
|
904
863
|
/** Assemble headers + tracking and run the foreground stage-driver render. */
|
|
905
864
|
function renderPreparedRscResponse<TEnv>(
|
|
906
865
|
input: RequestRenderInput<TEnv>,
|
|
@@ -1534,6 +1493,10 @@ function serveShellHit(
|
|
|
1534
1493
|
search: shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
1535
1494
|
// The HIT request's origin — same host as the capture's (key-scoped).
|
|
1536
1495
|
origin: url.origin,
|
|
1496
|
+
// The document cache reads the list before storing this composite.
|
|
1497
|
+
onError: (error) => {
|
|
1498
|
+
reqCtx._renderErrors?.push(error);
|
|
1499
|
+
},
|
|
1537
1500
|
}),
|
|
1538
1501
|
);
|
|
1539
1502
|
};
|
|
@@ -43,3 +43,16 @@ export const SHELL_CAPTURE_MAX_WAIT_MS = 15_000;
|
|
|
43
43
|
* check on the stampede guard, keyed off the same constant.
|
|
44
44
|
*/
|
|
45
45
|
export const SHELL_CAPTURE_TASK_HARD_CAP_MS = 25_000;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Shared PPR loader-lane hint, appended to every capture warning or error
|
|
49
|
+
* whose fix depends on the lane (shell-capture.ts warnings, the cookie-store.ts
|
|
50
|
+
* capture guard). Lives in this import-free leaf so cookie-store.ts can import
|
|
51
|
+
* it without pulling the capture orchestration module. Source anchor for the
|
|
52
|
+
* rule: resolveLoaderData (router/segment-resolution/loader-cache.ts).
|
|
53
|
+
*/
|
|
54
|
+
export const PPR_LANE_HINT: string =
|
|
55
|
+
"Lane rule: only loader(Def, { ssr: false }) executes at capture and bakes its " +
|
|
56
|
+
"non-promise data; every other loader is live (masked at capture, fresh per " +
|
|
57
|
+
"request) and needs loading() or an inline <Suspense> above its reader. See the " +
|
|
58
|
+
'/ppr skill, "The loader lane rule" (node_modules/@rangojs/router/skills/ppr/SKILL.md).';
|
package/src/rsc/shell-capture.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
enqueueSerializedCapture,
|
|
29
29
|
} from "./capture-queue.js";
|
|
30
30
|
import {
|
|
31
|
+
PPR_LANE_HINT,
|
|
31
32
|
SHELL_CAPTURE_MAX_WAIT_MS,
|
|
32
33
|
SHELL_CAPTURE_TASK_HARD_CAP_MS,
|
|
33
34
|
} from "./shell-capture-constants.js";
|
|
@@ -257,10 +258,11 @@ function releaseCaptureGuard(key: string, token: CaptureGuardToken): void {
|
|
|
257
258
|
* mode's ceiling (60s in production, {@link REFUSED_CAPTURE_DEV_MAX_MS} in dev).
|
|
258
259
|
*
|
|
259
260
|
* Why exponential and not a flat 60s: a flat long window conflates two very
|
|
260
|
-
* different failures. A STRUCTURALLY ineligible route (
|
|
261
|
-
* reader) fails forever and wants the long 60s cap. But a
|
|
262
|
-
* can also fail the in-place retry under a truly cold
|
|
263
|
-
* or a cold worker under parallel load) — and it
|
|
261
|
+
* different failures. A STRUCTURALLY ineligible route (a boundary-less live
|
|
262
|
+
* loader read, a cookie reader) fails forever and wants the long 60s cap. But a
|
|
263
|
+
* cold-but-ELIGIBLE route can also fail the in-place retry under a truly cold
|
|
264
|
+
* graph (dev module transform, or a cold worker under parallel load) — and it
|
|
265
|
+
* must recover FAST, on the next
|
|
264
266
|
* request or two, not be frozen for 60s (that would re-break the very cold-start DX
|
|
265
267
|
* the retry fixes; it bit the cloudflare dev e2e). Escalating from 1s means the
|
|
266
268
|
* eligible route re-probes almost immediately (warm now → HIT and clear), while the
|
|
@@ -366,9 +368,8 @@ const warnedNullCaptures = new Set<string>();
|
|
|
366
368
|
* usually healed, so a firing warning leans toward the structural cause — but we
|
|
367
369
|
* still name both so a cold-start straggler is not misdiagnosed.
|
|
368
370
|
*
|
|
369
|
-
* The pointer is
|
|
370
|
-
*
|
|
371
|
-
* rather than a relative path that dead-ends for consumers.
|
|
371
|
+
* The pointer is the shared PPR_LANE_HINT: the /ppr skill ships in the npm
|
|
372
|
+
* tarball, so the path resolves for consumers (a05c8251 convention).
|
|
372
373
|
*/
|
|
373
374
|
function warnNullCaptureOnce(key: string): void {
|
|
374
375
|
if (warnedNullCaptures.has(key)) return;
|
|
@@ -379,18 +380,13 @@ function warnNullCaptureOnce(key: string): void {
|
|
|
379
380
|
"by whether the route ever flips to HIT:\n" +
|
|
380
381
|
" 1. Cold-start warmup (dev module transform, or a cold worker): the capture raced " +
|
|
381
382
|
"an unfinished shell render. This SELF-HEALS — the route flips to HIT once a later " +
|
|
382
|
-
"request warms the modules
|
|
383
|
+
"request warms the modules.\n" +
|
|
383
384
|
" 2. Something suspends above <body> with no Suspense boundary and never settles " +
|
|
384
|
-
"within the capture window: a
|
|
385
|
-
"(
|
|
386
|
-
"
|
|
387
|
-
"
|
|
388
|
-
|
|
389
|
-
"loader (a child route's loading() does not unpin a parent layout's loaders), or " +
|
|
390
|
-
"a <Suspense> above the consuming component.\n" +
|
|
391
|
-
'See the /ppr skill (node_modules/@rangojs/router/skills/ppr/SKILL.md), "The hole ' +
|
|
392
|
-
'doctrine" and "The layout-with-loaders playbook", or the design docs: ' +
|
|
393
|
-
"https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/design/ppr-shell-resume.md",
|
|
385
|
+
"within the capture window: a live loader (no ssr: false) read without loading() or " +
|
|
386
|
+
"an inline <Suspense>; a loader(Def, { ssr: false }) or top-level push (e.g. Meta) " +
|
|
387
|
+
"slower than ppr.captureTimeout; or a pending promise with no Suspense above it. The " +
|
|
388
|
+
"boundary belongs to the entry or component that owns the data.\n" +
|
|
389
|
+
PPR_LANE_HINT,
|
|
394
390
|
);
|
|
395
391
|
}
|
|
396
392
|
|
|
@@ -399,19 +395,23 @@ const warnedRefusedCaptures = new Set<string>();
|
|
|
399
395
|
|
|
400
396
|
/**
|
|
401
397
|
* Warn once per key that the capture was REFUSED for a deterministic reason
|
|
402
|
-
* (identity-guard trip
|
|
403
|
-
* warnNullCaptureOnce: these are not cold-start
|
|
404
|
-
* and the message carries the concrete cause
|
|
398
|
+
* (identity-guard trip, a rejected or signal-settled bake-lane loader, a store
|
|
399
|
+
* refusal). Distinct from warnNullCaptureOnce: these are not cold-start
|
|
400
|
+
* shapes, the retry is skipped, and the message carries the concrete cause
|
|
401
|
+
* instead of a differential. Lane-dependent causes pass PPR_LANE_HINT as the
|
|
402
|
+
* trailing pointer; the rest keep the plain skill pointer.
|
|
405
403
|
*/
|
|
406
|
-
function warnCaptureRefusedOnce(
|
|
404
|
+
function warnCaptureRefusedOnce(
|
|
405
|
+
key: string,
|
|
406
|
+
reason: string,
|
|
407
|
+
pointer: string = "See the /ppr skill (node_modules/@rangojs/router/skills/ppr/SKILL.md).",
|
|
408
|
+
): void {
|
|
407
409
|
if (warnedRefusedCaptures.has(key)) return;
|
|
408
410
|
warnedRefusedCaptures.add(key);
|
|
409
411
|
console.warn(
|
|
410
412
|
`[rango] Shell capture for "${key}" was refused: ${reason}\n` +
|
|
411
413
|
"The route stays on MISS (axis 1) — the page keeps working, only the shell " +
|
|
412
|
-
|
|
413
|
-
"(node_modules/@rangojs/router/skills/ppr/SKILL.md) and " +
|
|
414
|
-
"docs/design/loader-container-bake.md.",
|
|
414
|
+
`cache is off. ${pointer}`,
|
|
415
415
|
);
|
|
416
416
|
}
|
|
417
417
|
|
|
@@ -489,13 +489,11 @@ function warnBakeCostOnce(key: string, source: string, ms: number): void {
|
|
|
489
489
|
`[rango] Shell capture for "${key}" waited ${ms}ms for ${source} to ` +
|
|
490
490
|
"settle before the shell could freeze. This cost recurs on every capture " +
|
|
491
491
|
"of the route and occupies the per-isolate capture queue; the served " +
|
|
492
|
-
"response never shows it.
|
|
493
|
-
"
|
|
494
|
-
"
|
|
495
|
-
"
|
|
496
|
-
|
|
497
|
-
"loading() boundary to its entry (live lane, masked at capture). See the " +
|
|
498
|
-
"/ppr skill (node_modules/@rangojs/router/skills/ppr/SKILL.md).",
|
|
492
|
+
"response never shows it. To make the value per-request, nest it " +
|
|
493
|
+
"({ data: promise }) and read it with use() under Suspense; to keep it " +
|
|
494
|
+
"baked but cheap, wrap the work in cache(); for a loader(Def, { ssr: false }), " +
|
|
495
|
+
"drop ssr: false to move it to the live lane. " +
|
|
496
|
+
PPR_LANE_HINT,
|
|
499
497
|
);
|
|
500
498
|
}
|
|
501
499
|
|
|
@@ -1328,8 +1326,9 @@ async function runShellCapture(
|
|
|
1328
1326
|
if (second !== "no-shell") return second;
|
|
1329
1327
|
|
|
1330
1328
|
// Both attempts came back with no usable shell. Cold-start would have healed by
|
|
1331
|
-
// now, so the eternal-MISS structural shape (a loader
|
|
1332
|
-
//
|
|
1329
|
+
// now, so the eternal-MISS structural shape (a boundary-less live-loader read;
|
|
1330
|
+
// lane rule: see resolveLoaderData, loader-cache.ts) is the likely cause —
|
|
1331
|
+
// warn once per key. Ordering matters: because the retry
|
|
1333
1332
|
// absorbs cold-start, cold-start routes almost never reach this warning. The
|
|
1334
1333
|
// caller (scheduleShellCapture) reads this `no-shell` return to back the key off.
|
|
1335
1334
|
log(
|
|
@@ -1507,29 +1506,34 @@ export function deriveShellCaptureContext(
|
|
|
1507
1506
|
// re-run can fill, so the entry must not serve handler-free
|
|
1508
1507
|
// (ShellCacheEntry.handlerLiveHoles). Still-pending top-level handler
|
|
1509
1508
|
// pushes at the putShell barrier count too — their liveness is unknowable.
|
|
1510
|
-
// -
|
|
1511
|
-
//
|
|
1509
|
+
// - loader-push tag: DSL-loader pushes re-run fresh on every HIT, so their
|
|
1510
|
+
// captured values must NOT enter a segment record's handle snapshot
|
|
1512
1511
|
// (replay would duplicate the fresh push, and their masked nested
|
|
1513
|
-
// promises would stall the Flight handle encode to its timeout). The
|
|
1514
|
-
//
|
|
1515
|
-
//
|
|
1516
|
-
// getDataForSegment consumer (the
|
|
1517
|
-
// sees every push.
|
|
1512
|
+
// promises would stall the Flight handle encode to its timeout). The
|
|
1513
|
+
// store tags them by array position (HandleStore.push loaderPush), which
|
|
1514
|
+
// covers primitives, and only the captureHandles cache-write call site
|
|
1515
|
+
// excludes tagged pushes — every other getDataForSegment consumer (the
|
|
1516
|
+
// render-barrier snapshot, prerender) sees every push. Unflagged loaders
|
|
1517
|
+
// never execute at capture; the tagged pushes come from loaders an
|
|
1518
|
+
// ssr:false loader awaits via ctx.use and from loader-cache replays
|
|
1519
|
+
// (loader-cache.ts replayLoaderHandles), both re-run on a HIT.
|
|
1518
1520
|
// EXCEPTION — bake-lane { ssr: false } loaders: they execute at capture
|
|
1519
|
-
//
|
|
1520
|
-
//
|
|
1521
|
-
//
|
|
1522
|
-
//
|
|
1523
|
-
//
|
|
1524
|
-
//
|
|
1525
|
-
//
|
|
1526
|
-
//
|
|
1521
|
+
// (fresh.ts awaits them), their pushes are in the captured HTML (<head>
|
|
1522
|
+
// title/meta, useHandle echoes), and the stored snapshot carries the same
|
|
1523
|
+
// values so a HIT's handle data is guaranteed to match its own prelude.
|
|
1524
|
+
// They still re-run on every HIT (resolveLoaderData, loader-cache.ts), so
|
|
1525
|
+
// each kept push carries its loader as `owner` (HandleStore.push): the
|
|
1526
|
+
// record stores it (CachedEntryData.handleOwners), the HIT restores it
|
|
1527
|
+
// through pushReplayed, and the re-run's live pushes replace the
|
|
1528
|
+
// restored copies in place. Only settled, thenable-free pushes
|
|
1529
|
+
// qualify — a deferred (thenable) push or one with masked nested promises
|
|
1530
|
+
// keeps the exclusion so the handle encode cannot stall on a
|
|
1531
|
+
// never-resolving mask.
|
|
1527
1532
|
const handleLiveness = {
|
|
1528
1533
|
holes: false,
|
|
1529
1534
|
pendingPushes: 0,
|
|
1530
1535
|
handlerInvokedLoader: false,
|
|
1531
1536
|
};
|
|
1532
|
-
const loaderScopedPushValues = new WeakSet<object>();
|
|
1533
1537
|
const rawCapturePush = freshHandleStore.push.bind(freshHandleStore);
|
|
1534
1538
|
freshHandleStore.push = (
|
|
1535
1539
|
handleName: string,
|
|
@@ -1550,42 +1554,36 @@ export function deriveShellCaptureContext(
|
|
|
1550
1554
|
return masked;
|
|
1551
1555
|
};
|
|
1552
1556
|
let masked: unknown;
|
|
1557
|
+
let loaderPush = pushedInLoaderScope;
|
|
1558
|
+
let owner: string | undefined;
|
|
1553
1559
|
if (isThenable(value)) {
|
|
1554
1560
|
if (!pushedInLoaderScope) {
|
|
1555
1561
|
handleLiveness.pendingPushes++;
|
|
1556
1562
|
const settle = () => handleLiveness.pendingPushes--;
|
|
1557
1563
|
value.then(settle, settle);
|
|
1558
1564
|
}
|
|
1559
|
-
|
|
1560
|
-
// Deferred (thenable) loader pushes always keep the exclusion — the
|
|
1565
|
+
// Deferred (thenable) loader pushes always keep the tag — the
|
|
1561
1566
|
// bake-lane carve-out below is for settled values only.
|
|
1562
|
-
|
|
1563
|
-
loaderScopedPushValues.add(masked as object);
|
|
1564
|
-
}
|
|
1567
|
+
masked = value.then(maskWithLiveness);
|
|
1565
1568
|
} else {
|
|
1566
1569
|
masked = maskWithLiveness(value);
|
|
1567
|
-
if (
|
|
1568
|
-
pushedInLoaderScope &&
|
|
1569
|
-
typeof masked === "object" &&
|
|
1570
|
-
masked !== null
|
|
1571
|
-
) {
|
|
1570
|
+
if (loaderPush && !maskedNestedThenable) {
|
|
1572
1571
|
// Bake-lane { ssr: false } loader pushes are shell material (see the
|
|
1573
|
-
// funnel comment above):
|
|
1574
|
-
// settled and thenable-free; everything else stays
|
|
1572
|
+
// funnel comment above): untagged, the stored snapshot keeps them
|
|
1573
|
+
// when settled and thenable-free; everything else stays tagged.
|
|
1575
1574
|
const bodyLoaderId = getCurrentLoaderBodyId();
|
|
1576
|
-
|
|
1577
|
-
bodyLoaderId
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
if (!baked) {
|
|
1581
|
-
loaderScopedPushValues.add(masked);
|
|
1582
|
-
}
|
|
1575
|
+
loaderPush =
|
|
1576
|
+
bodyLoaderId === undefined ||
|
|
1577
|
+
derivedCtx._awaitBeforeFlushLoaderIds?.has(bodyLoaderId) !== true;
|
|
1578
|
+
if (!loaderPush) owner = bodyLoaderId;
|
|
1583
1579
|
}
|
|
1584
1580
|
}
|
|
1585
|
-
rawCapturePush(handleName, segmentId, masked);
|
|
1581
|
+
rawCapturePush(handleName, segmentId, masked, loaderPush, owner);
|
|
1586
1582
|
};
|
|
1587
1583
|
|
|
1588
|
-
const derivedCtx: RequestContext = Object.create(reqCtx)
|
|
1584
|
+
const derivedCtx: RequestContext = Object.assign(Object.create(reqCtx), {
|
|
1585
|
+
_handleStore: freshHandleStore,
|
|
1586
|
+
});
|
|
1589
1587
|
if (identity) {
|
|
1590
1588
|
derivedCtx.request = identity.request;
|
|
1591
1589
|
derivedCtx.url = identity.url;
|
|
@@ -1593,7 +1591,6 @@ export function deriveShellCaptureContext(
|
|
|
1593
1591
|
derivedCtx.pathname = identity.url.pathname;
|
|
1594
1592
|
derivedCtx.searchParams = identity.url.searchParams;
|
|
1595
1593
|
}
|
|
1596
|
-
derivedCtx._handleStore = freshHandleStore;
|
|
1597
1594
|
// Own render barrier, closure-bound to the derived ctx and the fresh store
|
|
1598
1595
|
// (issue #684, plan 009). Without this every _renderBarrier* read fell
|
|
1599
1596
|
// through the prototype to the foreground's ALREADY-RESOLVED barrier: a
|
|
@@ -1603,7 +1600,6 @@ export function deriveShellCaptureContext(
|
|
|
1603
1600
|
// also resets _treeHasStreaming (recomputed for the capture's tree) and the
|
|
1604
1601
|
// deadlock-guard fields as own properties.
|
|
1605
1602
|
wireRenderBarrier(derivedCtx, freshHandleStore);
|
|
1606
|
-
derivedCtx._shellCaptureLoaderHandleValues = loaderScopedPushValues;
|
|
1607
1603
|
derivedCtx._requestTags = new Set<string>();
|
|
1608
1604
|
// Own explicit-store registry: cache-store resolutions during the capture
|
|
1609
1605
|
// (the implicit scope's SnapshotOnlySegmentStore, any per-capture explicit
|
|
@@ -1614,6 +1610,9 @@ export function deriveShellCaptureContext(
|
|
|
1614
1610
|
// context; module-singleton stores stay registered by normal renders.
|
|
1615
1611
|
derivedCtx._explicitTaggedStores = new Set();
|
|
1616
1612
|
derivedCtx._transitionWhen = [];
|
|
1613
|
+
// Own list: the capture's render errors refuse the capture only, and the
|
|
1614
|
+
// foreground's never reach it.
|
|
1615
|
+
derivedCtx._renderErrors = [];
|
|
1617
1616
|
derivedCtx._shellCaptureRun = true;
|
|
1618
1617
|
// Own-property reset: an inherited flag would make serializeSegments store
|
|
1619
1618
|
// a double-encoded fragment. No current arming site reaches a capture
|
|
@@ -1714,8 +1713,9 @@ async function captureAndStoreShell(
|
|
|
1714
1713
|
// generator has not yielded, SsrRoot suspends at the root (consumeAsyncGenerator
|
|
1715
1714
|
// sits above every boundary), the prelude comes back trivial, and
|
|
1716
1715
|
// captureShellHTML's sanity gate returns null: the designed fail-safe no-op, not
|
|
1717
|
-
// an error. This mirrors the
|
|
1718
|
-
// excludes loaders. See
|
|
1716
|
+
// an error. This mirrors the matchForPrerender (router/prerender-match.ts)
|
|
1717
|
+
// seal+settled regime, which also excludes loaders. See
|
|
1718
|
+
// docs/design/ppr-shell-resume.md ("Loaders and handles").
|
|
1719
1719
|
handleStore.seal();
|
|
1720
1720
|
|
|
1721
1721
|
// Handles contract, shell half ("nesting = liveness"): TOP-LEVEL pushed handle
|
|
@@ -1794,13 +1794,12 @@ async function captureAndStoreShell(
|
|
|
1794
1794
|
: "handler/render code (no loader body was executing)";
|
|
1795
1795
|
warnCaptureRefusedOnce(
|
|
1796
1796
|
capture.key,
|
|
1797
|
-
`${origin} called ${fnName}() during capture
|
|
1798
|
-
"
|
|
1799
|
-
"(
|
|
1800
|
-
"
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
"as a capture-time copy, mirroring cache() semantics (the consumption-lane rule).",
|
|
1797
|
+
`${origin} called ${fnName}() during capture; identity must not bake into the shared shell. ` +
|
|
1798
|
+
"Segment loader: drop ssr: false and read it under loading() or an inline <Suspense> " +
|
|
1799
|
+
"(a nested promise does not help: its body still runs at capture). Handler/render " +
|
|
1800
|
+
"code: read it in a live loader and consume it with useLoader. Exception: " +
|
|
1801
|
+
"await ctx.use(loader) in a HANDLER bakes as a capture-time copy.",
|
|
1802
|
+
PPR_LANE_HINT,
|
|
1804
1803
|
);
|
|
1805
1804
|
return "refused";
|
|
1806
1805
|
};
|
|
@@ -1819,6 +1818,9 @@ async function captureAndStoreShell(
|
|
|
1819
1818
|
// static-part search reads bake what the key names.
|
|
1820
1819
|
search: capture.searchSeed,
|
|
1821
1820
|
origin: capture.originSeed,
|
|
1821
|
+
onError: (error) => {
|
|
1822
|
+
reqCtx._renderErrors?.push(error);
|
|
1823
|
+
},
|
|
1822
1824
|
}),
|
|
1823
1825
|
);
|
|
1824
1826
|
} catch (error) {
|
|
@@ -1835,9 +1837,9 @@ async function captureAndStoreShell(
|
|
|
1835
1837
|
// null = sanity gate refused (trivial/empty prelude, no <body>). Store nothing
|
|
1836
1838
|
// and report `no-shell` so the caller (runShellCapture) can retry once and, if
|
|
1837
1839
|
// that also fails, warn once per key. On a cold render this is the shell not
|
|
1838
|
-
// yet finished;
|
|
1839
|
-
// the structural eternal-MISS shape
|
|
1840
|
-
//
|
|
1840
|
+
// yet finished; a boundary-less live-loader read (lane rule: see
|
|
1841
|
+
// resolveLoaderData, loader-cache.ts) is the structural eternal-MISS shape.
|
|
1842
|
+
// The caller's warning names both.
|
|
1841
1843
|
// Guard check first — BEFORE the trivial-prelude retry path. A guard trip
|
|
1842
1844
|
// is deterministic (retrying re-trips it), and when the tripping loader's
|
|
1843
1845
|
// error UI still completed a shell, storing it would bake the failure into
|
|
@@ -1938,7 +1940,8 @@ async function captureAndStoreShell(
|
|
|
1938
1940
|
warnCaptureRefusedOnce(
|
|
1939
1941
|
capture.key,
|
|
1940
1942
|
`the loader for segment "${segmentKey}" rejected during capture; its error UI must not bake into the shared shell. ` +
|
|
1941
|
-
"Fix the loader, or
|
|
1943
|
+
"Fix the loader, or drop its ssr: false to move it to the live lane.",
|
|
1944
|
+
PPR_LANE_HINT,
|
|
1942
1945
|
);
|
|
1943
1946
|
return "refused";
|
|
1944
1947
|
}
|
|
@@ -1954,7 +1957,8 @@ async function captureAndStoreShell(
|
|
|
1954
1957
|
warnCaptureRefusedOnce(
|
|
1955
1958
|
capture.key,
|
|
1956
1959
|
`the loader for segment "${segmentKey}" settled with redirect()/notFound() during capture; a request-specific signal must not bake into the shared shell. ` +
|
|
1957
|
-
"
|
|
1960
|
+
"Drop its ssr: false to move it to the live lane, or move the decision into middleware.",
|
|
1961
|
+
PPR_LANE_HINT,
|
|
1958
1962
|
);
|
|
1959
1963
|
return "refused";
|
|
1960
1964
|
}
|
|
@@ -1966,7 +1970,14 @@ async function captureAndStoreShell(
|
|
|
1966
1970
|
// must round-trip; serializeResult preserves it through Flight.
|
|
1967
1971
|
serializeContainer ??= (await import("../cache/segment-codec.js"))
|
|
1968
1972
|
.serializeResult;
|
|
1969
|
-
|
|
1973
|
+
// A value Flight cannot encode (an async component that throws, a
|
|
1974
|
+
// rejected promise the elide walk does not reach, a function, a
|
|
1975
|
+
// class instance) completes with an error row instead of rejecting
|
|
1976
|
+
// (issue #927). Collected with the render's errors, so the check
|
|
1977
|
+
// after this drain refuses the capture.
|
|
1978
|
+
const serialized = await serializeContainer(elided.value, (error) => {
|
|
1979
|
+
reqCtx._renderErrors?.push(error);
|
|
1980
|
+
});
|
|
1970
1981
|
if (serialized !== null) {
|
|
1971
1982
|
(snapshot ??= []).push({
|
|
1972
1983
|
family: "loader",
|
|
@@ -1979,12 +1990,22 @@ async function captureAndStoreShell(
|
|
|
1979
1990
|
});
|
|
1980
1991
|
}
|
|
1981
1992
|
} catch {
|
|
1982
|
-
//
|
|
1983
|
-
//
|
|
1993
|
+
// Codec import failed: leave it unpinned (it drifts on a HIT, the
|
|
1994
|
+
// pre-snapshot behavior) rather than failing the capture.
|
|
1984
1995
|
}
|
|
1985
1996
|
}
|
|
1986
1997
|
}
|
|
1987
1998
|
|
|
1999
|
+
// A shell component that threw inside a Suspense boundary does not reject
|
|
2000
|
+
// the capture: Flight and Fizz report it through onError and the prelude
|
|
2001
|
+
// carries the errored boundary, which every HIT would serve (issue #915).
|
|
2002
|
+
// The drain's container encode adds its Flight errors here too (#927).
|
|
2003
|
+
// After the loader drain so a rejected bake-lane loader keeps its specific
|
|
2004
|
+
// refusal. Thrown like a fatal shell error: no retry, reportCacheError and
|
|
2005
|
+
// backoff in scheduleShellCapture.
|
|
2006
|
+
const renderErrors = reqCtx._renderErrors;
|
|
2007
|
+
if (renderErrors && renderErrors.length > 0) throw renderErrors[0];
|
|
2008
|
+
|
|
1988
2009
|
// Snapshot size guard (issue #651): the snapshot duplicates every pinned
|
|
1989
2010
|
// cache value inside the shell entry, so a page over a large cache()
|
|
1990
2011
|
// segment can push the stored envelope toward store value limits (KV caps
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -82,11 +82,11 @@ export interface SsrHtmlStageOptions<TEnv> {
|
|
|
82
82
|
/** Metrics store for ssr:module-load / ssr:stream-mode timing (per call site). */
|
|
83
83
|
metricsStore: MetricsStore | undefined;
|
|
84
84
|
/**
|
|
85
|
-
* renderHTML options minus streamMode (the stage
|
|
86
|
-
* the per-site nonce and, for the PE action re-render, formState.
|
|
87
|
-
* verbatim so each call site's exact key set is preserved.
|
|
85
|
+
* renderHTML options minus streamMode and onError (the stage owns both).
|
|
86
|
+
* Carries the per-site nonce and, for the PE action re-render, formState.
|
|
87
|
+
* Spread verbatim so each call site's exact key set is preserved.
|
|
88
88
|
*/
|
|
89
|
-
render: Omit<SSRRenderOptions, "streamMode">;
|
|
89
|
+
render: Omit<SSRRenderOptions, "streamMode" | "onError">;
|
|
90
90
|
/** ResponseInit merged into the prepared render (e.g. content-type). */
|
|
91
91
|
init?: ResponseInit;
|
|
92
92
|
}
|
|
@@ -109,6 +109,7 @@ export function createSsrHtmlStage<TEnv>(
|
|
|
109
109
|
options.url,
|
|
110
110
|
options.metricsStore,
|
|
111
111
|
);
|
|
112
|
+
const renderErrors = _getRequestContext()?._renderErrors;
|
|
112
113
|
return {
|
|
113
114
|
render: () =>
|
|
114
115
|
// search: the LIVE request's query string, threaded out-of-band into
|
|
@@ -124,6 +125,12 @@ export function createSsrHtmlStage<TEnv>(
|
|
|
124
125
|
// so origin-dependent markup (Link's data-external) agrees with the
|
|
125
126
|
// browser's window.location across hydration.
|
|
126
127
|
origin: options.url.origin,
|
|
128
|
+
// A component that throws inside a Suspense boundary leaves an
|
|
129
|
+
// errored boundary in a completed document; the document cache
|
|
130
|
+
// reads the list before storing it.
|
|
131
|
+
onError: (error) => {
|
|
132
|
+
renderErrors?.push(error);
|
|
133
|
+
},
|
|
127
134
|
}),
|
|
128
135
|
...(options.init && { init: options.init }),
|
|
129
136
|
};
|
|
@@ -185,8 +192,8 @@ function acceptsFlightExplicitly(request: Request, url: URL): boolean {
|
|
|
185
192
|
* Classify whether a request may require SSR (HTML rendering).
|
|
186
193
|
*
|
|
187
194
|
* Returns false for requests that are definitively RSC-only: transport
|
|
188
|
-
* params (partial/action/loader/__rsc)
|
|
189
|
-
*
|
|
195
|
+
* params (partial/action/loader/__rsc) or an explicit Accept:
|
|
196
|
+
* text/x-component. Must never return false for a request whose
|
|
190
197
|
* render-time decision (isRscRequest) will be HTML — the two share
|
|
191
198
|
* acceptsFlightExplicitly so the Accept rule cannot drift. document-cache.ts
|
|
192
199
|
* keys its HTML/RSC response slots off this function, so any divergence from
|
|
@@ -201,8 +208,7 @@ export function mayNeedSSR(request: Request, url: URL): boolean {
|
|
|
201
208
|
url.searchParams.has("_rsc_action") ||
|
|
202
209
|
request.headers.has("rsc-action") ||
|
|
203
210
|
url.searchParams.has("_rsc_loader") ||
|
|
204
|
-
url.searchParams.has("__rsc")
|
|
205
|
-
url.searchParams.has("__prerender_collect")
|
|
211
|
+
url.searchParams.has("__rsc")
|
|
206
212
|
) {
|
|
207
213
|
return false;
|
|
208
214
|
}
|
package/src/rsc/types.ts
CHANGED
|
@@ -193,6 +193,13 @@ export interface SSRRenderOptions {
|
|
|
193
193
|
* the browser across hydration. Absent on host-agnostic build captures.
|
|
194
194
|
*/
|
|
195
195
|
origin?: string;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Called for each error Fizz reports through its onError: a component that
|
|
199
|
+
* threw inside a Suspense boundary, which leaves the boundary errored in an
|
|
200
|
+
* otherwise completed document. React's default console.error is kept.
|
|
201
|
+
*/
|
|
202
|
+
onError?: (error: unknown) => void;
|
|
196
203
|
}
|
|
197
204
|
|
|
198
205
|
/**
|
|
@@ -219,6 +226,8 @@ export interface SSRModule {
|
|
|
219
226
|
maxWaitMs?: number;
|
|
220
227
|
search?: string;
|
|
221
228
|
origin?: string;
|
|
229
|
+
/** Each component error the prerender reports (never the capture's own abort). */
|
|
230
|
+
onError?: (error: unknown) => void;
|
|
222
231
|
},
|
|
223
232
|
) => Promise<{ prelude: Uint8Array; postponed: string | null } | null>;
|
|
224
233
|
|
|
@@ -237,6 +246,8 @@ export interface SSRModule {
|
|
|
237
246
|
nonce?: string;
|
|
238
247
|
search?: string;
|
|
239
248
|
origin?: string;
|
|
249
|
+
/** Each component error the resumed holes report. */
|
|
250
|
+
onError?: (error: unknown) => void;
|
|
240
251
|
},
|
|
241
252
|
) => Promise<ReadableStream<Uint8Array>>;
|
|
242
253
|
}
|