@rangojs/router 0.0.0-experimental.a769fbe7 → 0.0.0-experimental.b02a2fec
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 +76 -18
- package/dist/bin/rango.js +130 -47
- package/dist/vite/index.js +689 -366
- package/dist/vite/index.js.bak +5448 -0
- package/package.json +2 -2
- package/skills/links/SKILL.md +3 -1
- package/skills/middleware/SKILL.md +2 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/router-setup/SKILL.md +35 -0
- package/src/__internal.ts +1 -1
- package/src/browser/app-version.ts +14 -0
- package/src/browser/navigation-bridge.ts +19 -4
- package/src/browser/navigation-client.ts +64 -64
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/partial-update.ts +27 -5
- package/src/browser/prefetch/fetch.ts +8 -2
- package/src/browser/react/Link.tsx +44 -8
- package/src/browser/react/NavigationProvider.tsx +8 -1
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-router.ts +21 -8
- package/src/browser/rsc-router.tsx +26 -3
- package/src/browser/scroll-restoration.ts +10 -8
- package/src/browser/server-action-bridge.ts +8 -18
- package/src/browser/types.ts +20 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +211 -72
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/client.tsx +2 -56
- package/src/deps/browser.ts +0 -1
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +3 -1
- package/src/index.ts +12 -0
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +22 -1
- package/src/route-definition/dsl-helpers.ts +42 -19
- package/src/route-definition/helpers-types.ts +4 -1
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +9 -1
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-types.ts +11 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/handler-context.ts +48 -15
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/loader-resolution.ts +150 -21
- package/src/router/match-api.ts +124 -189
- package/src/router/match-middleware/cache-lookup.ts +28 -8
- package/src/router/match-middleware/segment-resolution.ts +53 -0
- package/src/router/match-result.ts +82 -4
- package/src/router/middleware-types.ts +0 -6
- package/src/router/middleware.ts +0 -3
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +70 -5
- package/src/router/segment-resolution/revalidation.ts +87 -9
- package/src/router.ts +53 -5
- package/src/rsc/handler.ts +472 -398
- package/src/rsc/loader-fetch.ts +18 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +12 -3
- package/src/rsc/rsc-rendering.ts +8 -2
- package/src/rsc/server-action.ts +8 -2
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +6 -4
- package/src/server/context.ts +39 -2
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +132 -13
- package/src/ssr/index.tsx +3 -0
- package/src/static-handler.ts +18 -6
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +17 -11
- package/src/types/loader-types.ts +32 -5
- package/src/types/route-entry.ts +1 -1
- package/src/types/segments.ts +1 -0
- package/src/urls/path-helper-types.ts +9 -2
- package/src/urls/path-helper.ts +47 -12
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +16 -6
- package/src/use-loader.tsx +77 -5
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +5 -1
- package/src/vite/discovery/prerender-collection.ts +128 -74
- package/src/vite/discovery/state.ts +13 -4
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +60 -5
- package/src/vite/plugins/expose-id-utils.ts +12 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-internal-ids.ts +257 -40
- package/src/vite/plugins/performance-tracks.ts +64 -207
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/rango.ts +18 -5
- package/src/vite/router-discovery.ts +178 -37
- package/src/vite/utils/prerender-utils.ts +18 -0
- package/src/vite/utils/shared-utils.ts +3 -2
- package/src/browser/debug-channel.ts +0 -93
package/src/rsc/handler.ts
CHANGED
|
@@ -15,14 +15,10 @@ import {
|
|
|
15
15
|
setRequestContextParams,
|
|
16
16
|
requireRequestContext,
|
|
17
17
|
getRequestContext,
|
|
18
|
+
_getRequestContext,
|
|
18
19
|
createRequestContext,
|
|
19
20
|
} from "../server/request-context.js";
|
|
20
21
|
import * as rscDeps from "@vitejs/plugin-rsc/rsc";
|
|
21
|
-
import {
|
|
22
|
-
DEBUG_ID_HEADER,
|
|
23
|
-
createServerDebugChannel,
|
|
24
|
-
} from "../vite/plugins/performance-tracks.js";
|
|
25
|
-
|
|
26
22
|
import type {
|
|
27
23
|
RscPayload,
|
|
28
24
|
CreateRSCHandlerOptions,
|
|
@@ -87,6 +83,11 @@ import {
|
|
|
87
83
|
mayNeedSSR,
|
|
88
84
|
SSR_SETUP_VAR,
|
|
89
85
|
} from "./ssr-setup.js";
|
|
86
|
+
import {
|
|
87
|
+
classifyRequest,
|
|
88
|
+
type RequestPlan,
|
|
89
|
+
type ExecutableRequestPlan,
|
|
90
|
+
} from "../router/request-classification.js";
|
|
90
91
|
|
|
91
92
|
/**
|
|
92
93
|
* Create an RSC request handler.
|
|
@@ -166,10 +167,13 @@ export function createRSCHandler<
|
|
|
166
167
|
phase: ErrorPhase,
|
|
167
168
|
context: Parameters<typeof invokeOnError<TEnv>>[3],
|
|
168
169
|
): void {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
// Guard: abort signal handlers fire asynchronously outside the ALS
|
|
171
|
+
// request scope, so the context may be gone. Skip dedup in that
|
|
172
|
+
// case — the error is from a cancelled stream, not a real failure.
|
|
173
|
+
const reqCtx = _getRequestContext();
|
|
174
|
+
if (error != null && typeof error === "object" && reqCtx) {
|
|
175
|
+
if (reqCtx._reportedErrors.has(error)) return;
|
|
176
|
+
reqCtx._reportedErrors.add(error);
|
|
173
177
|
}
|
|
174
178
|
invokeOnError(router.onError, error, phase, context, "RSC");
|
|
175
179
|
}
|
|
@@ -267,10 +271,7 @@ export function createRSCHandler<
|
|
|
267
271
|
...(locationState && { locationState }),
|
|
268
272
|
},
|
|
269
273
|
};
|
|
270
|
-
const
|
|
271
|
-
const rscStream = renderToReadableStream<RscPayload>(redirectPayload, {
|
|
272
|
-
...(debugChannel && { debugChannel }),
|
|
273
|
-
});
|
|
274
|
+
const rscStream = renderToReadableStream<RscPayload>(redirectPayload);
|
|
274
275
|
return createResponseWithMergedHeaders(rscStream, {
|
|
275
276
|
status: 200,
|
|
276
277
|
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
@@ -426,21 +427,6 @@ export function createRSCHandler<
|
|
|
426
427
|
requestContext._debugPerformance = true;
|
|
427
428
|
requestContext._metricsStore = earlyMetricsStore;
|
|
428
429
|
}
|
|
429
|
-
// Dev-only: wire debug channel for React Performance Tracks
|
|
430
|
-
if (process.env.NODE_ENV !== "production") {
|
|
431
|
-
const debugId = request.headers.get(DEBUG_ID_HEADER);
|
|
432
|
-
console.log("[perf-tracks] handler: debugId header =", debugId);
|
|
433
|
-
if (debugId) {
|
|
434
|
-
const channel = createServerDebugChannel(debugId);
|
|
435
|
-
console.log(
|
|
436
|
-
"[perf-tracks] handler: channel =",
|
|
437
|
-
channel ? "created" : "NOT FOUND",
|
|
438
|
-
);
|
|
439
|
-
if (channel) {
|
|
440
|
-
requestContext._debugChannel = channel;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
430
|
// Wire background error reporting so "use cache" and other subsystems
|
|
445
431
|
// can surface non-fatal errors through the router's onError callback.
|
|
446
432
|
requestContext._reportBackgroundError = (
|
|
@@ -475,6 +461,9 @@ export function createRSCHandler<
|
|
|
475
461
|
// - Server components during rendering
|
|
476
462
|
// - Error boundaries
|
|
477
463
|
// - Streaming
|
|
464
|
+
// Store basename on request context (scoped per-request via existing ALS)
|
|
465
|
+
requestContext._basename = router.basename;
|
|
466
|
+
|
|
478
467
|
return runWithRequestContext(requestContext, async () => {
|
|
479
468
|
// Core handler logic (wrapped by middleware)
|
|
480
469
|
const coreHandler = async (): Promise<Response> => {
|
|
@@ -550,7 +539,9 @@ export function createRSCHandler<
|
|
|
550
539
|
});
|
|
551
540
|
};
|
|
552
541
|
|
|
553
|
-
// Core request handling logic (separated for middleware wrapping)
|
|
542
|
+
// Core request handling logic (separated for middleware wrapping).
|
|
543
|
+
// Uses the classify → execute model: classifyRequest produces a RequestPlan,
|
|
544
|
+
// then execution dispatches on the plan mode.
|
|
554
545
|
async function coreRequestHandler(
|
|
555
546
|
request: Request,
|
|
556
547
|
env: TEnv,
|
|
@@ -558,71 +549,112 @@ export function createRSCHandler<
|
|
|
558
549
|
variables: Record<string, any>,
|
|
559
550
|
nonce: string | undefined,
|
|
560
551
|
): Promise<Response> {
|
|
561
|
-
const previewStart = performance.now();
|
|
562
|
-
const preview = await router.previewMatch(request, { env });
|
|
563
|
-
const previewDur = performance.now() - previewStart;
|
|
564
552
|
const handlerTiming: string[] = variables.__handlerTiming || [];
|
|
565
|
-
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
553
|
+
|
|
554
|
+
// Debug manifest endpoint: handled before classification since it
|
|
555
|
+
// doesn't need a route match and needs trie access from the closure.
|
|
556
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
557
|
+
if (
|
|
558
|
+
url.searchParams.has("__debug_manifest") &&
|
|
559
|
+
(isDev || router.allowDebugManifest)
|
|
560
|
+
) {
|
|
561
|
+
const trie = getRouterTrie(router.id) ?? getRouteTrie();
|
|
562
|
+
const routeManifest = getRequiredRouteMap();
|
|
563
|
+
const { extractAncestryFromTrie } =
|
|
564
|
+
await import("../build/route-trie.js");
|
|
565
|
+
return new Response(
|
|
566
|
+
JSON.stringify(
|
|
567
|
+
{
|
|
568
|
+
routerId: router.id,
|
|
569
|
+
routeManifest,
|
|
570
|
+
routeAncestry: trie ? extractAncestryFromTrie(trie) : {},
|
|
571
|
+
routeTrie: trie,
|
|
572
|
+
precomputedEntries: getPrecomputedEntries(),
|
|
573
|
+
},
|
|
574
|
+
null,
|
|
575
|
+
2,
|
|
576
576
|
),
|
|
577
|
-
|
|
578
|
-
|
|
577
|
+
{
|
|
578
|
+
headers: { "Content-Type": "application/json" },
|
|
579
|
+
},
|
|
579
580
|
);
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ---- 1. Classify ----
|
|
584
|
+
// classifyRequest may throw RouteNotFoundError for unknown routes.
|
|
585
|
+
// In that case, fall through to a full-render plan so the pipeline
|
|
586
|
+
// can render the 404 page via the existing error handling path.
|
|
587
|
+
const classifyStart = performance.now();
|
|
588
|
+
let plan: RequestPlan<TEnv>;
|
|
589
|
+
try {
|
|
590
|
+
plan = await classifyRequest<TEnv>(request, url, {
|
|
591
|
+
findMatch: router.findMatch,
|
|
592
|
+
routerVersion: version,
|
|
593
|
+
routerId: router.id,
|
|
594
|
+
});
|
|
595
|
+
} catch (error) {
|
|
596
|
+
if (
|
|
597
|
+
error instanceof RouteNotFoundError ||
|
|
598
|
+
(error instanceof Error && error.name === "RouteNotFoundError")
|
|
599
|
+
) {
|
|
600
|
+
// Let the render path handle 404 — match()/matchPartial() will
|
|
601
|
+
// re-throw RouteNotFoundError and the catch block in
|
|
602
|
+
// executeRenderWithMiddleware renders the not-found page.
|
|
603
|
+
plan = {
|
|
604
|
+
mode: "full-render",
|
|
605
|
+
route: {
|
|
606
|
+
matched: null as any,
|
|
607
|
+
manifestEntry: null as any,
|
|
608
|
+
entries: [],
|
|
609
|
+
routeKey: "",
|
|
610
|
+
localRouteName: "",
|
|
611
|
+
params: {},
|
|
612
|
+
routeMiddleware: [],
|
|
613
|
+
cacheScope: null,
|
|
614
|
+
isPassthrough: false,
|
|
615
|
+
},
|
|
616
|
+
negotiated: false,
|
|
617
|
+
};
|
|
618
|
+
} else {
|
|
619
|
+
throw error;
|
|
589
620
|
}
|
|
590
|
-
|
|
621
|
+
}
|
|
622
|
+
const classifyDur = performance.now() - classifyStart;
|
|
623
|
+
handlerTiming.push(`handler-classify;dur=${classifyDur.toFixed(2)}`);
|
|
624
|
+
|
|
625
|
+
// ---- 2. Terminal plans (no execution needed) ----
|
|
626
|
+
if (plan.mode === "redirect") {
|
|
627
|
+
// Redirects are handled by the pipeline (match/matchPartial),
|
|
628
|
+
// but for partial requests we short-circuit with a Flight redirect.
|
|
629
|
+
if (url.searchParams.has("_rsc_partial")) {
|
|
630
|
+
return createRedirectFlightResponse(plan.redirectUrl);
|
|
631
|
+
}
|
|
632
|
+
// Full requests: let the pipeline handle the redirect via match()
|
|
633
|
+
// which returns { redirect: url }. Fall through to full-render.
|
|
591
634
|
}
|
|
592
635
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (mayNeedSSR(request, url)) {
|
|
597
|
-
variables[SSR_SETUP_VAR] = startSSRSetup(
|
|
598
|
-
handlerCtx,
|
|
599
|
-
request,
|
|
600
|
-
env,
|
|
601
|
-
url,
|
|
602
|
-
router.debugPerformance
|
|
603
|
-
? () => requireRequestContext()._metricsStore
|
|
604
|
-
: undefined,
|
|
636
|
+
if (plan.mode === "version-mismatch") {
|
|
637
|
+
console.log(
|
|
638
|
+
`[RSC] Version mismatch: client=${url.searchParams.get("_rsc_v")}, server=${version}. Forcing reload.`,
|
|
605
639
|
);
|
|
640
|
+
return createResponseWithMergedHeaders(null, {
|
|
641
|
+
status: 200,
|
|
642
|
+
headers: {
|
|
643
|
+
"X-RSC-Reload": plan.reloadUrl,
|
|
644
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
645
|
+
},
|
|
646
|
+
});
|
|
606
647
|
}
|
|
607
648
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// PE form submissions before any execution. Regular page navigations
|
|
618
|
-
// (GET without _rsc_loader/_rsc_action) are not affected.
|
|
619
|
-
const originPhase: OriginCheckPhase | null = isAction
|
|
620
|
-
? "action"
|
|
621
|
-
: isLoaderFetch
|
|
622
|
-
? "loader"
|
|
623
|
-
: request.method === "POST"
|
|
624
|
-
? "pe-form"
|
|
625
|
-
: null;
|
|
649
|
+
// ---- 3. Origin guard (gate for action/loader/PE modes) ----
|
|
650
|
+
const originPhase: OriginCheckPhase | null =
|
|
651
|
+
plan.mode === "action"
|
|
652
|
+
? "action"
|
|
653
|
+
: plan.mode === "loader"
|
|
654
|
+
? "loader"
|
|
655
|
+
: plan.mode === "pe-render"
|
|
656
|
+
? "pe-form"
|
|
657
|
+
: null;
|
|
626
658
|
if (originPhase) {
|
|
627
659
|
const originResult = await checkRequestOrigin(
|
|
628
660
|
request,
|
|
@@ -672,13 +704,33 @@ export function createRSCHandler<
|
|
|
672
704
|
}
|
|
673
705
|
}
|
|
674
706
|
|
|
675
|
-
//
|
|
707
|
+
// ---- 4. Execute ----
|
|
708
|
+
return executeRequest(
|
|
709
|
+
plan as ExecutableRequestPlan<TEnv>,
|
|
710
|
+
request,
|
|
711
|
+
env,
|
|
712
|
+
url,
|
|
713
|
+
variables,
|
|
714
|
+
nonce,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Execute a classified request plan. Dispatches to the appropriate handler
|
|
719
|
+
// based on plan.mode. Lives in the createRSCHandler closure for access to
|
|
720
|
+
// handlerCtx, router, callOnError, etc.
|
|
721
|
+
// Only receives executable plans (version-mismatch is handled above).
|
|
722
|
+
async function executeRequest(
|
|
723
|
+
plan: ExecutableRequestPlan<TEnv>,
|
|
724
|
+
request: Request,
|
|
725
|
+
env: TEnv,
|
|
726
|
+
url: URL,
|
|
727
|
+
variables: Record<string, any>,
|
|
728
|
+
nonce: string | undefined,
|
|
729
|
+
): Promise<Response> {
|
|
730
|
+
// Common setup
|
|
676
731
|
const handleStore = requireRequestContext()._handleStore;
|
|
677
732
|
|
|
678
733
|
// Wire up error reporting for late streaming-handle failures
|
|
679
|
-
// (LateHandlePushError: handle pushed after stream completion).
|
|
680
|
-
// Without this, these errors are only caught by React's error boundary
|
|
681
|
-
// and never reach the router's onError callback or telemetry.
|
|
682
734
|
handleStore.onError = (error: Error) => {
|
|
683
735
|
const reqCtx = requireRequestContext();
|
|
684
736
|
callOnError(error, "handler", {
|
|
@@ -708,37 +760,106 @@ export function createRSCHandler<
|
|
|
708
760
|
};
|
|
709
761
|
|
|
710
762
|
// Set route params early so all execution paths can access ctx.params.
|
|
711
|
-
|
|
712
|
-
|
|
763
|
+
// Also store the classified snapshot so match/matchPartial can reuse it
|
|
764
|
+
// instead of calling resolveRoute again.
|
|
765
|
+
if (plan.mode !== "redirect") {
|
|
766
|
+
setRequestContextParams(plan.route.params, plan.route.routeKey);
|
|
767
|
+
requireRequestContext()._classifiedRoute = plan.route;
|
|
713
768
|
}
|
|
714
769
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
//
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
routeMiddleware:
|
|
770
|
+
const routeReverse = createReverseFunction(getRequiredRouteMap());
|
|
771
|
+
|
|
772
|
+
// ---- Response route: skip entire RSC pipeline ----
|
|
773
|
+
if (plan.mode === "response") {
|
|
774
|
+
// Build ResponseRouteMatch from plan fields. handleResponseRoute
|
|
775
|
+
// expects a flat object with params at the top level.
|
|
776
|
+
const responseMatch: ResponseRouteMatch = {
|
|
777
|
+
responseType: plan.responseType,
|
|
778
|
+
handler: plan.handler,
|
|
779
|
+
params: plan.route.params,
|
|
780
|
+
negotiated: plan.negotiated,
|
|
781
|
+
manifestEntry: plan.manifestEntry,
|
|
782
|
+
routeMiddleware: plan.routeMiddleware,
|
|
783
|
+
};
|
|
784
|
+
const responseOutcome = await withTimeout(
|
|
785
|
+
handleResponseRoute(
|
|
786
|
+
handlerCtx,
|
|
787
|
+
responseMatch,
|
|
788
|
+
request,
|
|
789
|
+
env,
|
|
790
|
+
url,
|
|
791
|
+
variables,
|
|
792
|
+
),
|
|
793
|
+
router.timeouts.renderStartMs,
|
|
794
|
+
"render-start",
|
|
795
|
+
);
|
|
796
|
+
if (responseOutcome.timedOut) {
|
|
797
|
+
return handleTimeoutResponse(
|
|
798
|
+
request,
|
|
799
|
+
env,
|
|
800
|
+
url,
|
|
801
|
+
"render-start",
|
|
802
|
+
responseOutcome.durationMs,
|
|
803
|
+
plan.route.routeKey,
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
const response = responseOutcome.result;
|
|
807
|
+
if (plan.negotiated) {
|
|
808
|
+
response.headers.append("Vary", "Accept");
|
|
809
|
+
}
|
|
810
|
+
return response;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// SSR setup: kick off in parallel for modes that need HTML rendering.
|
|
814
|
+
// Placed after response-route short-circuit so response/mime routes
|
|
815
|
+
// never pay for SSR work.
|
|
816
|
+
if (plan.mode !== "loader" && mayNeedSSR(request, url)) {
|
|
817
|
+
variables[SSR_SETUP_VAR] = startSSRSetup(
|
|
818
|
+
handlerCtx,
|
|
819
|
+
request,
|
|
820
|
+
env,
|
|
821
|
+
url,
|
|
822
|
+
router.debugPerformance
|
|
823
|
+
? () => requireRequestContext()._metricsStore
|
|
824
|
+
: undefined,
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// ---- Loader fetch ----
|
|
829
|
+
if (plan.mode === "loader") {
|
|
830
|
+
return handleLoaderFetch(
|
|
831
|
+
handlerCtx,
|
|
832
|
+
request,
|
|
833
|
+
env,
|
|
834
|
+
url,
|
|
728
835
|
variables,
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
836
|
+
plan.route.params,
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// ---- Progressive enhancement ----
|
|
841
|
+
if (plan.mode === "pe-render") {
|
|
842
|
+
const peResult = await handleProgressiveEnhancement(
|
|
843
|
+
handlerCtx,
|
|
844
|
+
request,
|
|
845
|
+
env,
|
|
846
|
+
url,
|
|
847
|
+
false, // isAction = false for PE
|
|
848
|
+
handleStore,
|
|
849
|
+
nonce,
|
|
850
|
+
{
|
|
851
|
+
routeMiddleware: plan.route.routeMiddleware,
|
|
852
|
+
variables,
|
|
853
|
+
routeReverse,
|
|
854
|
+
},
|
|
855
|
+
);
|
|
856
|
+
if (peResult) return peResult;
|
|
857
|
+
// PE handler returned null (not a PE form) — fall through to render
|
|
734
858
|
}
|
|
735
859
|
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
// the revalidation pass (identical to a normal render).
|
|
740
|
-
let actionContinuation: ActionContinuation | undefined;
|
|
741
|
-
if (isAction && actionId) {
|
|
860
|
+
// ---- Action: execute action, then revalidate wrapped in route middleware ----
|
|
861
|
+
if (plan.mode === "action") {
|
|
862
|
+
let actionContinuation: ActionContinuation | undefined;
|
|
742
863
|
try {
|
|
743
864
|
const actionOutcome = await withTimeout(
|
|
744
865
|
executeServerAction(
|
|
@@ -746,7 +867,7 @@ export function createRSCHandler<
|
|
|
746
867
|
request,
|
|
747
868
|
env,
|
|
748
869
|
url,
|
|
749
|
-
actionId,
|
|
870
|
+
plan.actionId,
|
|
750
871
|
handleStore,
|
|
751
872
|
),
|
|
752
873
|
router.timeouts.actionMs,
|
|
@@ -759,8 +880,8 @@ export function createRSCHandler<
|
|
|
759
880
|
url,
|
|
760
881
|
"action",
|
|
761
882
|
actionOutcome.durationMs,
|
|
762
|
-
|
|
763
|
-
actionId,
|
|
883
|
+
plan.route.routeKey,
|
|
884
|
+
plan.actionId,
|
|
764
885
|
);
|
|
765
886
|
}
|
|
766
887
|
const result = actionOutcome.result;
|
|
@@ -772,344 +893,297 @@ export function createRSCHandler<
|
|
|
772
893
|
request,
|
|
773
894
|
url,
|
|
774
895
|
env,
|
|
775
|
-
actionId,
|
|
896
|
+
actionId: plan.actionId,
|
|
776
897
|
handledByBoundary: false,
|
|
777
898
|
});
|
|
778
899
|
console.error(`[RSC] Action error:`, error);
|
|
779
900
|
throw error;
|
|
780
901
|
}
|
|
781
|
-
}
|
|
782
902
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
903
|
+
// Revalidation render wrapped in route middleware.
|
|
904
|
+
// Actions from client-side navigation include _rsc_partial — preserve
|
|
905
|
+
// the partial flag so the revalidation returns a Flight stream, not HTML.
|
|
906
|
+
// App-switch is already excluded by classifyRequest (would be full-render).
|
|
907
|
+
const isPartialAction = url.searchParams.has("_rsc_partial");
|
|
908
|
+
return executeRenderWithMiddleware(
|
|
909
|
+
plan.route.routeMiddleware,
|
|
910
|
+
plan.negotiated,
|
|
911
|
+
plan.route.routeKey,
|
|
912
|
+
routeReverse,
|
|
787
913
|
request,
|
|
788
914
|
env,
|
|
789
915
|
url,
|
|
790
916
|
variables,
|
|
791
917
|
nonce,
|
|
792
|
-
preview?.params,
|
|
793
|
-
preview?.routeKey,
|
|
794
918
|
handleStore,
|
|
919
|
+
isPartialAction,
|
|
795
920
|
actionContinuation,
|
|
796
921
|
);
|
|
797
|
-
|
|
798
|
-
response.headers.append("Vary", "Accept");
|
|
799
|
-
}
|
|
800
|
-
return response;
|
|
801
|
-
};
|
|
802
|
-
|
|
803
|
-
// Wrap the render path (with or without route middleware) in a
|
|
804
|
-
// renderStartMs timeout so slow renders are caught before output.
|
|
805
|
-
const executeRender = async (): Promise<Response> => {
|
|
806
|
-
if (preview?.routeMiddleware && preview.routeMiddleware.length > 0) {
|
|
807
|
-
const mwResponse = await executeMiddleware(
|
|
808
|
-
buildRouteMiddlewareEntries<TEnv>(preview.routeMiddleware),
|
|
809
|
-
request,
|
|
810
|
-
env,
|
|
811
|
-
variables,
|
|
812
|
-
renderHandler,
|
|
813
|
-
routeReverse,
|
|
814
|
-
);
|
|
815
|
-
|
|
816
|
-
if (
|
|
817
|
-
url.searchParams.has("_rsc_partial") ||
|
|
818
|
-
url.searchParams.has("_rsc_action")
|
|
819
|
-
) {
|
|
820
|
-
const intercepted = interceptRedirectForPartial(
|
|
821
|
-
mwResponse,
|
|
822
|
-
createRedirectFlightResponse,
|
|
823
|
-
);
|
|
824
|
-
if (intercepted) return intercepted;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
return finalizeResponse(mwResponse);
|
|
828
|
-
}
|
|
922
|
+
}
|
|
829
923
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
924
|
+
// ---- Full render / Partial render (or PE that fell through) ----
|
|
925
|
+
if (plan.mode === "full-render" || plan.mode === "partial-render") {
|
|
926
|
+
const isPartial = plan.mode === "partial-render";
|
|
927
|
+
return executeRenderWithMiddleware(
|
|
928
|
+
plan.route.routeMiddleware,
|
|
929
|
+
plan.negotiated,
|
|
930
|
+
plan.route.routeKey,
|
|
931
|
+
routeReverse,
|
|
932
|
+
request,
|
|
933
|
+
env,
|
|
934
|
+
url,
|
|
935
|
+
variables,
|
|
936
|
+
nonce,
|
|
937
|
+
handleStore,
|
|
938
|
+
isPartial,
|
|
939
|
+
);
|
|
940
|
+
}
|
|
833
941
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
942
|
+
// PE that fell through (handleProgressiveEnhancement returned null)
|
|
943
|
+
// falls back to full render
|
|
944
|
+
if (plan.mode === "pe-render") {
|
|
945
|
+
return executeRenderWithMiddleware(
|
|
946
|
+
plan.route.routeMiddleware,
|
|
947
|
+
false,
|
|
948
|
+
plan.route.routeKey,
|
|
949
|
+
routeReverse,
|
|
841
950
|
request,
|
|
842
951
|
env,
|
|
843
952
|
url,
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
953
|
+
variables,
|
|
954
|
+
nonce,
|
|
955
|
+
handleStore,
|
|
956
|
+
false,
|
|
847
957
|
);
|
|
848
958
|
}
|
|
849
|
-
|
|
959
|
+
|
|
960
|
+
// Redirect plan that wasn't handled above (full-page redirect — let
|
|
961
|
+
// the pipeline handle it via match() which returns { redirect: url })
|
|
962
|
+
return executeRenderWithMiddleware(
|
|
963
|
+
plan.route.routeMiddleware,
|
|
964
|
+
false,
|
|
965
|
+
plan.route.routeKey,
|
|
966
|
+
routeReverse,
|
|
967
|
+
request,
|
|
968
|
+
env,
|
|
969
|
+
url,
|
|
970
|
+
variables,
|
|
971
|
+
nonce,
|
|
972
|
+
handleStore,
|
|
973
|
+
false,
|
|
974
|
+
);
|
|
850
975
|
}
|
|
851
976
|
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
async function
|
|
977
|
+
// Shared render execution: wraps handleRscRendering (or revalidateAfterAction)
|
|
978
|
+
// in route middleware and timeout handling. Consolidates the pattern used by
|
|
979
|
+
// action-revalidate, full-render, and partial-render modes.
|
|
980
|
+
async function executeRenderWithMiddleware(
|
|
981
|
+
routeMiddleware: import("../router/middleware-types.js").CollectedMiddleware[],
|
|
982
|
+
negotiated: boolean,
|
|
983
|
+
routeKey: string,
|
|
984
|
+
routeReverse: ReturnType<typeof createReverseFunction>,
|
|
856
985
|
request: Request,
|
|
857
986
|
env: TEnv,
|
|
858
987
|
url: URL,
|
|
859
988
|
variables: Record<string, any>,
|
|
860
989
|
nonce: string | undefined,
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
handleStore?: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
990
|
+
handleStore: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
991
|
+
isPartial: boolean,
|
|
864
992
|
actionContinuation?: ActionContinuation,
|
|
865
993
|
): Promise<Response> {
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
994
|
+
const renderHandler = async (): Promise<Response> => {
|
|
995
|
+
try {
|
|
996
|
+
let response: Response;
|
|
997
|
+
if (actionContinuation) {
|
|
998
|
+
response = await revalidateAfterAction(
|
|
999
|
+
handlerCtx,
|
|
1000
|
+
request,
|
|
1001
|
+
env,
|
|
1002
|
+
url,
|
|
1003
|
+
handleStore,
|
|
1004
|
+
actionContinuation,
|
|
1005
|
+
);
|
|
1006
|
+
} else {
|
|
1007
|
+
response = await handleRscRendering(
|
|
1008
|
+
handlerCtx,
|
|
1009
|
+
request,
|
|
1010
|
+
env,
|
|
1011
|
+
url,
|
|
1012
|
+
isPartial,
|
|
1013
|
+
handleStore,
|
|
1014
|
+
nonce,
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
if (negotiated) {
|
|
1018
|
+
response.headers.append("Vary", "Accept");
|
|
1019
|
+
}
|
|
1020
|
+
return response;
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
// Check if middleware/handler returned Response
|
|
1023
|
+
if (error instanceof Response) {
|
|
1024
|
+
// During partial (client-side navigation), a 200 Response from a handler
|
|
1025
|
+
// means the route serves raw content (JSON, text, etc.), not JSX.
|
|
1026
|
+
// Signal the browser to hard-navigate so it renders the raw response.
|
|
1027
|
+
if (isPartial && error.status === 200) {
|
|
1028
|
+
console.warn(
|
|
1029
|
+
`[RSC] Route handler at ${url.pathname} returned a Response during client-side navigation. ` +
|
|
1030
|
+
`Falling back to hard navigation. Use data-external on the <Link> to avoid the extra round-trip.`,
|
|
1031
|
+
);
|
|
1032
|
+
return createResponseWithMergedHeaders(null, {
|
|
1033
|
+
status: 200,
|
|
1034
|
+
headers: {
|
|
1035
|
+
"X-RSC-Reload": stripInternalParams(url).toString(),
|
|
1036
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
1037
|
+
},
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
877
1040
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
if (referer) {
|
|
885
|
-
try {
|
|
886
|
-
const refererUrl = new URL(referer);
|
|
887
|
-
if (refererUrl.origin === url.origin) {
|
|
888
|
-
reloadUrl = referer;
|
|
889
|
-
}
|
|
890
|
-
} catch {
|
|
891
|
-
// Malformed referer, fall back to cleanUrl
|
|
1041
|
+
if (isPartial) {
|
|
1042
|
+
const intercepted = interceptRedirectForPartial(
|
|
1043
|
+
error,
|
|
1044
|
+
createRedirectFlightResponse,
|
|
1045
|
+
);
|
|
1046
|
+
if (intercepted) return intercepted;
|
|
892
1047
|
}
|
|
1048
|
+
|
|
1049
|
+
return error;
|
|
893
1050
|
}
|
|
894
|
-
}
|
|
895
1051
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
const isDev = process.env.NODE_ENV !== "production";
|
|
908
|
-
if (
|
|
909
|
-
url.searchParams.has("__debug_manifest") &&
|
|
910
|
-
(isDev || router.allowDebugManifest)
|
|
911
|
-
) {
|
|
912
|
-
const trie = getRouterTrie(router.id) ?? getRouteTrie();
|
|
913
|
-
const routeManifest = getRequiredRouteMap();
|
|
914
|
-
const { extractAncestryFromTrie } =
|
|
915
|
-
await import("../build/route-trie.js");
|
|
916
|
-
return new Response(
|
|
917
|
-
JSON.stringify(
|
|
918
|
-
{
|
|
919
|
-
routerId: router.id,
|
|
920
|
-
routeManifest,
|
|
921
|
-
routeAncestry: trie ? extractAncestryFromTrie(trie) : {},
|
|
922
|
-
routeTrie: trie,
|
|
923
|
-
precomputedEntries: getPrecomputedEntries(),
|
|
924
|
-
},
|
|
925
|
-
null,
|
|
926
|
-
2,
|
|
927
|
-
),
|
|
928
|
-
{
|
|
929
|
-
headers: { "Content-Type": "application/json" },
|
|
930
|
-
},
|
|
931
|
-
);
|
|
932
|
-
}
|
|
1052
|
+
// Render 404 page for unmatched routes
|
|
1053
|
+
const isRouteNotFound =
|
|
1054
|
+
error instanceof RouteNotFoundError ||
|
|
1055
|
+
(error instanceof Error && error.name === "RouteNotFoundError");
|
|
1056
|
+
if (isRouteNotFound) {
|
|
1057
|
+
callOnError(error, "routing", {
|
|
1058
|
+
request,
|
|
1059
|
+
url,
|
|
1060
|
+
env,
|
|
1061
|
+
handledByBoundary: true,
|
|
1062
|
+
});
|
|
933
1063
|
|
|
934
|
-
|
|
1064
|
+
const notFoundOption = router.notFound;
|
|
1065
|
+
const notFoundComponent =
|
|
1066
|
+
typeof notFoundOption === "function"
|
|
1067
|
+
? notFoundOption({ pathname: url.pathname })
|
|
1068
|
+
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
1069
|
+
|
|
1070
|
+
const notFoundSegment = {
|
|
1071
|
+
id: "notFound",
|
|
1072
|
+
namespace: "notFound",
|
|
1073
|
+
type: "route" as const,
|
|
1074
|
+
index: 0,
|
|
1075
|
+
component: notFoundComponent,
|
|
1076
|
+
params: {},
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
const payload: RscPayload = {
|
|
1080
|
+
metadata: {
|
|
1081
|
+
pathname: url.pathname,
|
|
1082
|
+
routerId: router.id,
|
|
1083
|
+
basename: router.basename,
|
|
1084
|
+
segments: [notFoundSegment],
|
|
1085
|
+
matched: [],
|
|
1086
|
+
diff: [],
|
|
1087
|
+
isPartial: false,
|
|
1088
|
+
rootLayout: router.rootLayout,
|
|
1089
|
+
handles: handleStore.stream(),
|
|
1090
|
+
version,
|
|
1091
|
+
themeConfig: router.themeConfig,
|
|
1092
|
+
warmupEnabled: router.warmupEnabled,
|
|
1093
|
+
initialTheme: requireRequestContext().theme,
|
|
1094
|
+
},
|
|
1095
|
+
};
|
|
935
1096
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
}
|
|
1097
|
+
const rscStream = renderToReadableStream(payload, {
|
|
1098
|
+
onError: (error: unknown) => {
|
|
1099
|
+
callOnError(error, "rendering", { request, url, env });
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
942
1102
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
handlerCtx,
|
|
949
|
-
request,
|
|
950
|
-
env,
|
|
951
|
-
url,
|
|
952
|
-
store,
|
|
953
|
-
actionContinuation,
|
|
954
|
-
);
|
|
955
|
-
}
|
|
1103
|
+
const isRscRequest =
|
|
1104
|
+
isPartial ||
|
|
1105
|
+
(!request.headers.get("accept")?.includes("text/html") &&
|
|
1106
|
+
!url.searchParams.has("__html")) ||
|
|
1107
|
+
url.searchParams.has("__rsc");
|
|
956
1108
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
handlerCtx,
|
|
964
|
-
request,
|
|
965
|
-
env,
|
|
966
|
-
url,
|
|
967
|
-
variables,
|
|
968
|
-
routeParams,
|
|
969
|
-
);
|
|
970
|
-
}
|
|
1109
|
+
if (isRscRequest) {
|
|
1110
|
+
return createResponseWithMergedHeaders(rscStream, {
|
|
1111
|
+
status: 404,
|
|
1112
|
+
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
971
1115
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
request,
|
|
979
|
-
env,
|
|
980
|
-
url,
|
|
981
|
-
isPartial,
|
|
982
|
-
store,
|
|
983
|
-
nonce,
|
|
984
|
-
);
|
|
985
|
-
} catch (error) {
|
|
986
|
-
// Check if middleware/handler returned Response
|
|
987
|
-
if (error instanceof Response) {
|
|
988
|
-
// During partial (client-side navigation), a 200 Response from a handler
|
|
989
|
-
// means the route serves raw content (JSON, text, etc.), not JSX.
|
|
990
|
-
// Signal the browser to hard-navigate so it renders the raw response.
|
|
991
|
-
// Only for 200 — redirects (3xx) work already because the browser follows
|
|
992
|
-
// them automatically to a URL that serves Flight data.
|
|
993
|
-
if (isPartial && error.status === 200) {
|
|
994
|
-
console.warn(
|
|
995
|
-
`[RSC] Route handler at ${url.pathname} returned a Response during client-side navigation. ` +
|
|
996
|
-
`Falling back to hard navigation. Use data-external on the <Link> to avoid the extra round-trip.`,
|
|
1116
|
+
const [ssrModule, streamMode] = await getSSRSetup(
|
|
1117
|
+
handlerCtx,
|
|
1118
|
+
request,
|
|
1119
|
+
env,
|
|
1120
|
+
url,
|
|
1121
|
+
requireRequestContext()._metricsStore,
|
|
997
1122
|
);
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
"X-RSC-Reload": stripInternalParams(url).toString(),
|
|
1002
|
-
"content-type": "text/x-component;charset=utf-8",
|
|
1003
|
-
},
|
|
1123
|
+
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
1124
|
+
nonce,
|
|
1125
|
+
streamMode,
|
|
1004
1126
|
});
|
|
1005
|
-
}
|
|
1006
1127
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
);
|
|
1012
|
-
if (intercepted) return intercepted;
|
|
1128
|
+
return createResponseWithMergedHeaders(htmlStream, {
|
|
1129
|
+
status: 404,
|
|
1130
|
+
headers: { "content-type": "text/html;charset=utf-8" },
|
|
1131
|
+
});
|
|
1013
1132
|
}
|
|
1014
1133
|
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// Render 404 page for unmatched routes
|
|
1019
|
-
// Check both instanceof and error.name for cross-bundle compatibility
|
|
1020
|
-
const isRouteNotFound =
|
|
1021
|
-
error instanceof RouteNotFoundError ||
|
|
1022
|
-
(error instanceof Error && error.name === "RouteNotFoundError");
|
|
1023
|
-
if (isRouteNotFound) {
|
|
1134
|
+
// Report unhandled errors
|
|
1024
1135
|
callOnError(error, "routing", {
|
|
1025
1136
|
request,
|
|
1026
1137
|
url,
|
|
1027
1138
|
env,
|
|
1028
|
-
handledByBoundary:
|
|
1029
|
-
});
|
|
1030
|
-
|
|
1031
|
-
// Get notFound component from router options or use default
|
|
1032
|
-
const notFoundOption = router.notFound;
|
|
1033
|
-
const notFoundComponent =
|
|
1034
|
-
typeof notFoundOption === "function"
|
|
1035
|
-
? notFoundOption({ pathname: url.pathname })
|
|
1036
|
-
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
1037
|
-
|
|
1038
|
-
// Create a simple segment for the 404 page
|
|
1039
|
-
const notFoundSegment = {
|
|
1040
|
-
id: "notFound",
|
|
1041
|
-
namespace: "notFound",
|
|
1042
|
-
type: "route" as const,
|
|
1043
|
-
index: 0,
|
|
1044
|
-
component: notFoundComponent,
|
|
1045
|
-
params: {},
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
const payload: RscPayload = {
|
|
1049
|
-
metadata: {
|
|
1050
|
-
pathname: url.pathname,
|
|
1051
|
-
segments: [notFoundSegment],
|
|
1052
|
-
matched: [],
|
|
1053
|
-
diff: [],
|
|
1054
|
-
isPartial: false,
|
|
1055
|
-
rootLayout: router.rootLayout,
|
|
1056
|
-
handles: store.stream(),
|
|
1057
|
-
version,
|
|
1058
|
-
themeConfig: router.themeConfig,
|
|
1059
|
-
warmupEnabled: router.warmupEnabled,
|
|
1060
|
-
initialTheme: requireRequestContext().theme,
|
|
1061
|
-
// No routeName for not-found routes
|
|
1062
|
-
},
|
|
1063
|
-
};
|
|
1064
|
-
|
|
1065
|
-
const debugChannel = requireRequestContext()._debugChannel;
|
|
1066
|
-
const rscStream = renderToReadableStream(payload, {
|
|
1067
|
-
...(debugChannel && { debugChannel }),
|
|
1139
|
+
handledByBoundary: false,
|
|
1068
1140
|
});
|
|
1141
|
+
console.error(`[RSC] Error:`, error);
|
|
1142
|
+
throw error;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1069
1145
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
(
|
|
1075
|
-
!url.searchParams.has("__html")) ||
|
|
1076
|
-
url.searchParams.has("__rsc");
|
|
1077
|
-
|
|
1078
|
-
if (isRscRequest) {
|
|
1079
|
-
return createResponseWithMergedHeaders(rscStream, {
|
|
1080
|
-
status: 404,
|
|
1081
|
-
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// Delegate to SSR for HTML response (reuse early setup if available)
|
|
1086
|
-
const [ssrModule, streamMode] = await getSSRSetup(
|
|
1087
|
-
handlerCtx,
|
|
1146
|
+
// Wrap the render path in a renderStartMs timeout
|
|
1147
|
+
const executeRender = async (): Promise<Response> => {
|
|
1148
|
+
if (routeMiddleware.length > 0) {
|
|
1149
|
+
const mwResponse = await executeMiddleware(
|
|
1150
|
+
buildRouteMiddlewareEntries<TEnv>(routeMiddleware),
|
|
1088
1151
|
request,
|
|
1089
1152
|
env,
|
|
1090
|
-
|
|
1091
|
-
|
|
1153
|
+
variables,
|
|
1154
|
+
renderHandler,
|
|
1155
|
+
routeReverse,
|
|
1092
1156
|
);
|
|
1093
|
-
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
1094
|
-
nonce,
|
|
1095
|
-
streamMode,
|
|
1096
|
-
});
|
|
1097
1157
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1158
|
+
if (isPartial || actionContinuation) {
|
|
1159
|
+
const intercepted = interceptRedirectForPartial(
|
|
1160
|
+
mwResponse,
|
|
1161
|
+
createRedirectFlightResponse,
|
|
1162
|
+
);
|
|
1163
|
+
if (intercepted) return intercepted;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
return finalizeResponse(mwResponse);
|
|
1102
1167
|
}
|
|
1103
1168
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1169
|
+
return renderHandler();
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
const renderOutcome = await withTimeout(
|
|
1173
|
+
executeRender(),
|
|
1174
|
+
router.timeouts.renderStartMs,
|
|
1175
|
+
"render-start",
|
|
1176
|
+
);
|
|
1177
|
+
if (renderOutcome.timedOut) {
|
|
1178
|
+
return handleTimeoutResponse(
|
|
1106
1179
|
request,
|
|
1107
|
-
url,
|
|
1108
1180
|
env,
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1181
|
+
url,
|
|
1182
|
+
"render-start",
|
|
1183
|
+
renderOutcome.durationMs,
|
|
1184
|
+
routeKey,
|
|
1185
|
+
);
|
|
1113
1186
|
}
|
|
1187
|
+
return renderOutcome.result;
|
|
1114
1188
|
}
|
|
1115
1189
|
}
|