@rangojs/router 0.0.0-experimental.8123bb7e → 0.0.0-experimental.82
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 +829 -380
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +4 -4
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +24 -18
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +3 -1
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/parallel/SKILL.md +59 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/rango/SKILL.md +24 -22
- package/skills/route/SKILL.md +24 -0
- 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 +37 -5
- package/src/browser/navigation-client.ts +128 -77
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/partial-update.ts +41 -7
- package/src/browser/prefetch/cache.ts +113 -21
- package/src/browser/prefetch/fetch.ts +156 -18
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/react/Link.tsx +72 -8
- package/src/browser/react/NavigationProvider.tsx +14 -3
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +11 -1
- 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/segment-reconciler.ts +36 -14
- 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-trie.ts +50 -24
- 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 +84 -230
- package/src/deps/browser.ts +0 -1
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +3 -1
- package/src/index.ts +46 -6
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +25 -1
- package/src/route-definition/dsl-helpers.ts +194 -32
- package/src/route-definition/helpers-types.ts +61 -14
- 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 +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/handler-context.ts +51 -15
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/lazy-includes.ts +5 -5
- package/src/router/loader-resolution.ts +150 -21
- package/src/router/manifest.ts +22 -13
- 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 -397
- package/src/rsc/loader-fetch.ts +18 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +14 -3
- package/src/rsc/rsc-rendering.ts +15 -2
- package/src/rsc/server-action.ts +10 -2
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +6 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +11 -61
- package/src/server/context.ts +65 -5
- 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 +12 -1
- package/src/types/segments.ts +1 -1
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +39 -6
- 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/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- 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 -211
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/rango.ts +17 -11
- package/src/vite/router-discovery.ts +237 -37
- package/src/vite/utils/prerender-utils.ts +37 -5
- 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,20 +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
|
-
// Client navigations send the debugId as a header.
|
|
432
|
-
// SSR requests have no client — generate one and create the channel directly.
|
|
433
|
-
const clientDebugId = request.headers.get(DEBUG_ID_HEADER);
|
|
434
|
-
const debugId = clientDebugId || crypto.randomUUID();
|
|
435
|
-
const channel = clientDebugId
|
|
436
|
-
? createServerDebugChannel(debugId)
|
|
437
|
-
: createServerDebugChannel(debugId);
|
|
438
|
-
if (channel) {
|
|
439
|
-
requestContext._debugChannel = channel;
|
|
440
|
-
console.log("[perf-tracks] debug channel attached for", debugId);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
430
|
// Wire background error reporting so "use cache" and other subsystems
|
|
444
431
|
// can surface non-fatal errors through the router's onError callback.
|
|
445
432
|
requestContext._reportBackgroundError = (
|
|
@@ -474,6 +461,9 @@ export function createRSCHandler<
|
|
|
474
461
|
// - Server components during rendering
|
|
475
462
|
// - Error boundaries
|
|
476
463
|
// - Streaming
|
|
464
|
+
// Store basename on request context (scoped per-request via existing ALS)
|
|
465
|
+
requestContext._basename = router.basename;
|
|
466
|
+
|
|
477
467
|
return runWithRequestContext(requestContext, async () => {
|
|
478
468
|
// Core handler logic (wrapped by middleware)
|
|
479
469
|
const coreHandler = async (): Promise<Response> => {
|
|
@@ -549,7 +539,9 @@ export function createRSCHandler<
|
|
|
549
539
|
});
|
|
550
540
|
};
|
|
551
541
|
|
|
552
|
-
// 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.
|
|
553
545
|
async function coreRequestHandler(
|
|
554
546
|
request: Request,
|
|
555
547
|
env: TEnv,
|
|
@@ -557,71 +549,112 @@ export function createRSCHandler<
|
|
|
557
549
|
variables: Record<string, any>,
|
|
558
550
|
nonce: string | undefined,
|
|
559
551
|
): Promise<Response> {
|
|
560
|
-
const previewStart = performance.now();
|
|
561
|
-
const preview = await router.previewMatch(request, { env });
|
|
562
|
-
const previewDur = performance.now() - previewStart;
|
|
563
552
|
const handlerTiming: string[] = variables.__handlerTiming || [];
|
|
564
|
-
|
|
565
|
-
//
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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,
|
|
575
576
|
),
|
|
576
|
-
|
|
577
|
-
|
|
577
|
+
{
|
|
578
|
+
headers: { "Content-Type": "application/json" },
|
|
579
|
+
},
|
|
578
580
|
);
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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;
|
|
620
|
+
}
|
|
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);
|
|
588
631
|
}
|
|
589
|
-
|
|
632
|
+
// Full requests: let the pipeline handle the redirect via match()
|
|
633
|
+
// which returns { redirect: url }. Fall through to full-render.
|
|
590
634
|
}
|
|
591
635
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (mayNeedSSR(request, url)) {
|
|
596
|
-
variables[SSR_SETUP_VAR] = startSSRSetup(
|
|
597
|
-
handlerCtx,
|
|
598
|
-
request,
|
|
599
|
-
env,
|
|
600
|
-
url,
|
|
601
|
-
router.debugPerformance
|
|
602
|
-
? () => requireRequestContext()._metricsStore
|
|
603
|
-
: undefined,
|
|
636
|
+
if (plan.mode === "version-mismatch") {
|
|
637
|
+
console.log(
|
|
638
|
+
`[RSC] Version mismatch: client=${url.searchParams.get("_rsc_v")}, server=${version}. Forcing reload.`,
|
|
604
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
|
+
});
|
|
605
647
|
}
|
|
606
648
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
// PE form submissions before any execution. Regular page navigations
|
|
617
|
-
// (GET without _rsc_loader/_rsc_action) are not affected.
|
|
618
|
-
const originPhase: OriginCheckPhase | null = isAction
|
|
619
|
-
? "action"
|
|
620
|
-
: isLoaderFetch
|
|
621
|
-
? "loader"
|
|
622
|
-
: request.method === "POST"
|
|
623
|
-
? "pe-form"
|
|
624
|
-
: 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;
|
|
625
658
|
if (originPhase) {
|
|
626
659
|
const originResult = await checkRequestOrigin(
|
|
627
660
|
request,
|
|
@@ -671,13 +704,33 @@ export function createRSCHandler<
|
|
|
671
704
|
}
|
|
672
705
|
}
|
|
673
706
|
|
|
674
|
-
//
|
|
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
|
|
675
731
|
const handleStore = requireRequestContext()._handleStore;
|
|
676
732
|
|
|
677
733
|
// Wire up error reporting for late streaming-handle failures
|
|
678
|
-
// (LateHandlePushError: handle pushed after stream completion).
|
|
679
|
-
// Without this, these errors are only caught by React's error boundary
|
|
680
|
-
// and never reach the router's onError callback or telemetry.
|
|
681
734
|
handleStore.onError = (error: Error) => {
|
|
682
735
|
const reqCtx = requireRequestContext();
|
|
683
736
|
callOnError(error, "handler", {
|
|
@@ -707,37 +760,106 @@ export function createRSCHandler<
|
|
|
707
760
|
};
|
|
708
761
|
|
|
709
762
|
// Set route params early so all execution paths can access ctx.params.
|
|
710
|
-
|
|
711
|
-
|
|
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;
|
|
712
768
|
}
|
|
713
769
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
//
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
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,
|
|
727
835
|
variables,
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
);
|
|
731
|
-
if (progressiveResult) {
|
|
732
|
-
return progressiveResult;
|
|
836
|
+
plan.route.params,
|
|
837
|
+
);
|
|
733
838
|
}
|
|
734
839
|
|
|
735
|
-
//
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// ---- Action: execute action, then revalidate wrapped in route middleware ----
|
|
861
|
+
if (plan.mode === "action") {
|
|
862
|
+
let actionContinuation: ActionContinuation | undefined;
|
|
741
863
|
try {
|
|
742
864
|
const actionOutcome = await withTimeout(
|
|
743
865
|
executeServerAction(
|
|
@@ -745,7 +867,7 @@ export function createRSCHandler<
|
|
|
745
867
|
request,
|
|
746
868
|
env,
|
|
747
869
|
url,
|
|
748
|
-
actionId,
|
|
870
|
+
plan.actionId,
|
|
749
871
|
handleStore,
|
|
750
872
|
),
|
|
751
873
|
router.timeouts.actionMs,
|
|
@@ -758,8 +880,8 @@ export function createRSCHandler<
|
|
|
758
880
|
url,
|
|
759
881
|
"action",
|
|
760
882
|
actionOutcome.durationMs,
|
|
761
|
-
|
|
762
|
-
actionId,
|
|
883
|
+
plan.route.routeKey,
|
|
884
|
+
plan.actionId,
|
|
763
885
|
);
|
|
764
886
|
}
|
|
765
887
|
const result = actionOutcome.result;
|
|
@@ -771,344 +893,297 @@ export function createRSCHandler<
|
|
|
771
893
|
request,
|
|
772
894
|
url,
|
|
773
895
|
env,
|
|
774
|
-
actionId,
|
|
896
|
+
actionId: plan.actionId,
|
|
775
897
|
handledByBoundary: false,
|
|
776
898
|
});
|
|
777
899
|
console.error(`[RSC] Action error:`, error);
|
|
778
900
|
throw error;
|
|
779
901
|
}
|
|
780
|
-
}
|
|
781
902
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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,
|
|
786
913
|
request,
|
|
787
914
|
env,
|
|
788
915
|
url,
|
|
789
916
|
variables,
|
|
790
917
|
nonce,
|
|
791
|
-
preview?.params,
|
|
792
|
-
preview?.routeKey,
|
|
793
918
|
handleStore,
|
|
919
|
+
isPartialAction,
|
|
794
920
|
actionContinuation,
|
|
795
921
|
);
|
|
796
|
-
|
|
797
|
-
response.headers.append("Vary", "Accept");
|
|
798
|
-
}
|
|
799
|
-
return response;
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
// Wrap the render path (with or without route middleware) in a
|
|
803
|
-
// renderStartMs timeout so slow renders are caught before output.
|
|
804
|
-
const executeRender = async (): Promise<Response> => {
|
|
805
|
-
if (preview?.routeMiddleware && preview.routeMiddleware.length > 0) {
|
|
806
|
-
const mwResponse = await executeMiddleware(
|
|
807
|
-
buildRouteMiddlewareEntries<TEnv>(preview.routeMiddleware),
|
|
808
|
-
request,
|
|
809
|
-
env,
|
|
810
|
-
variables,
|
|
811
|
-
renderHandler,
|
|
812
|
-
routeReverse,
|
|
813
|
-
);
|
|
814
|
-
|
|
815
|
-
if (
|
|
816
|
-
url.searchParams.has("_rsc_partial") ||
|
|
817
|
-
url.searchParams.has("_rsc_action")
|
|
818
|
-
) {
|
|
819
|
-
const intercepted = interceptRedirectForPartial(
|
|
820
|
-
mwResponse,
|
|
821
|
-
createRedirectFlightResponse,
|
|
822
|
-
);
|
|
823
|
-
if (intercepted) return intercepted;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
return finalizeResponse(mwResponse);
|
|
827
|
-
}
|
|
922
|
+
}
|
|
828
923
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
+
}
|
|
832
941
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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,
|
|
840
950
|
request,
|
|
841
951
|
env,
|
|
842
952
|
url,
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
953
|
+
variables,
|
|
954
|
+
nonce,
|
|
955
|
+
handleStore,
|
|
956
|
+
false,
|
|
846
957
|
);
|
|
847
958
|
}
|
|
848
|
-
|
|
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
|
+
);
|
|
849
975
|
}
|
|
850
976
|
|
|
851
|
-
//
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
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>,
|
|
855
985
|
request: Request,
|
|
856
986
|
env: TEnv,
|
|
857
987
|
url: URL,
|
|
858
988
|
variables: Record<string, any>,
|
|
859
989
|
nonce: string | undefined,
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
handleStore?: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
990
|
+
handleStore: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
991
|
+
isPartial: boolean,
|
|
863
992
|
actionContinuation?: ActionContinuation,
|
|
864
993
|
): Promise<Response> {
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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
|
+
}
|
|
876
1040
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
if (referer) {
|
|
884
|
-
try {
|
|
885
|
-
const refererUrl = new URL(referer);
|
|
886
|
-
if (refererUrl.origin === url.origin) {
|
|
887
|
-
reloadUrl = referer;
|
|
888
|
-
}
|
|
889
|
-
} catch {
|
|
890
|
-
// Malformed referer, fall back to cleanUrl
|
|
1041
|
+
if (isPartial) {
|
|
1042
|
+
const intercepted = interceptRedirectForPartial(
|
|
1043
|
+
error,
|
|
1044
|
+
createRedirectFlightResponse,
|
|
1045
|
+
);
|
|
1046
|
+
if (intercepted) return intercepted;
|
|
891
1047
|
}
|
|
1048
|
+
|
|
1049
|
+
return error;
|
|
892
1050
|
}
|
|
893
|
-
}
|
|
894
1051
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
const isDev = process.env.NODE_ENV !== "production";
|
|
907
|
-
if (
|
|
908
|
-
url.searchParams.has("__debug_manifest") &&
|
|
909
|
-
(isDev || router.allowDebugManifest)
|
|
910
|
-
) {
|
|
911
|
-
const trie = getRouterTrie(router.id) ?? getRouteTrie();
|
|
912
|
-
const routeManifest = getRequiredRouteMap();
|
|
913
|
-
const { extractAncestryFromTrie } =
|
|
914
|
-
await import("../build/route-trie.js");
|
|
915
|
-
return new Response(
|
|
916
|
-
JSON.stringify(
|
|
917
|
-
{
|
|
918
|
-
routerId: router.id,
|
|
919
|
-
routeManifest,
|
|
920
|
-
routeAncestry: trie ? extractAncestryFromTrie(trie) : {},
|
|
921
|
-
routeTrie: trie,
|
|
922
|
-
precomputedEntries: getPrecomputedEntries(),
|
|
923
|
-
},
|
|
924
|
-
null,
|
|
925
|
-
2,
|
|
926
|
-
),
|
|
927
|
-
{
|
|
928
|
-
headers: { "Content-Type": "application/json" },
|
|
929
|
-
},
|
|
930
|
-
);
|
|
931
|
-
}
|
|
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
|
+
});
|
|
932
1063
|
|
|
933
|
-
|
|
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
|
+
};
|
|
934
1096
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
}
|
|
1097
|
+
const rscStream = renderToReadableStream(payload, {
|
|
1098
|
+
onError: (error: unknown) => {
|
|
1099
|
+
callOnError(error, "rendering", { request, url, env });
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
941
1102
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
handlerCtx,
|
|
948
|
-
request,
|
|
949
|
-
env,
|
|
950
|
-
url,
|
|
951
|
-
store,
|
|
952
|
-
actionContinuation,
|
|
953
|
-
);
|
|
954
|
-
}
|
|
1103
|
+
const isRscRequest =
|
|
1104
|
+
isPartial ||
|
|
1105
|
+
(!request.headers.get("accept")?.includes("text/html") &&
|
|
1106
|
+
!url.searchParams.has("__html")) ||
|
|
1107
|
+
url.searchParams.has("__rsc");
|
|
955
1108
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
handlerCtx,
|
|
963
|
-
request,
|
|
964
|
-
env,
|
|
965
|
-
url,
|
|
966
|
-
variables,
|
|
967
|
-
routeParams,
|
|
968
|
-
);
|
|
969
|
-
}
|
|
1109
|
+
if (isRscRequest) {
|
|
1110
|
+
return createResponseWithMergedHeaders(rscStream, {
|
|
1111
|
+
status: 404,
|
|
1112
|
+
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
970
1115
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
request,
|
|
978
|
-
env,
|
|
979
|
-
url,
|
|
980
|
-
isPartial,
|
|
981
|
-
store,
|
|
982
|
-
nonce,
|
|
983
|
-
);
|
|
984
|
-
} catch (error) {
|
|
985
|
-
// Check if middleware/handler returned Response
|
|
986
|
-
if (error instanceof Response) {
|
|
987
|
-
// During partial (client-side navigation), a 200 Response from a handler
|
|
988
|
-
// means the route serves raw content (JSON, text, etc.), not JSX.
|
|
989
|
-
// Signal the browser to hard-navigate so it renders the raw response.
|
|
990
|
-
// Only for 200 — redirects (3xx) work already because the browser follows
|
|
991
|
-
// them automatically to a URL that serves Flight data.
|
|
992
|
-
if (isPartial && error.status === 200) {
|
|
993
|
-
console.warn(
|
|
994
|
-
`[RSC] Route handler at ${url.pathname} returned a Response during client-side navigation. ` +
|
|
995
|
-
`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,
|
|
996
1122
|
);
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
"X-RSC-Reload": stripInternalParams(url).toString(),
|
|
1001
|
-
"content-type": "text/x-component;charset=utf-8",
|
|
1002
|
-
},
|
|
1123
|
+
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
1124
|
+
nonce,
|
|
1125
|
+
streamMode,
|
|
1003
1126
|
});
|
|
1004
|
-
}
|
|
1005
1127
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
);
|
|
1011
|
-
if (intercepted) return intercepted;
|
|
1128
|
+
return createResponseWithMergedHeaders(htmlStream, {
|
|
1129
|
+
status: 404,
|
|
1130
|
+
headers: { "content-type": "text/html;charset=utf-8" },
|
|
1131
|
+
});
|
|
1012
1132
|
}
|
|
1013
1133
|
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// Render 404 page for unmatched routes
|
|
1018
|
-
// Check both instanceof and error.name for cross-bundle compatibility
|
|
1019
|
-
const isRouteNotFound =
|
|
1020
|
-
error instanceof RouteNotFoundError ||
|
|
1021
|
-
(error instanceof Error && error.name === "RouteNotFoundError");
|
|
1022
|
-
if (isRouteNotFound) {
|
|
1134
|
+
// Report unhandled errors
|
|
1023
1135
|
callOnError(error, "routing", {
|
|
1024
1136
|
request,
|
|
1025
1137
|
url,
|
|
1026
1138
|
env,
|
|
1027
|
-
handledByBoundary:
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1030
|
-
// Get notFound component from router options or use default
|
|
1031
|
-
const notFoundOption = router.notFound;
|
|
1032
|
-
const notFoundComponent =
|
|
1033
|
-
typeof notFoundOption === "function"
|
|
1034
|
-
? notFoundOption({ pathname: url.pathname })
|
|
1035
|
-
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
1036
|
-
|
|
1037
|
-
// Create a simple segment for the 404 page
|
|
1038
|
-
const notFoundSegment = {
|
|
1039
|
-
id: "notFound",
|
|
1040
|
-
namespace: "notFound",
|
|
1041
|
-
type: "route" as const,
|
|
1042
|
-
index: 0,
|
|
1043
|
-
component: notFoundComponent,
|
|
1044
|
-
params: {},
|
|
1045
|
-
};
|
|
1046
|
-
|
|
1047
|
-
const payload: RscPayload = {
|
|
1048
|
-
metadata: {
|
|
1049
|
-
pathname: url.pathname,
|
|
1050
|
-
segments: [notFoundSegment],
|
|
1051
|
-
matched: [],
|
|
1052
|
-
diff: [],
|
|
1053
|
-
isPartial: false,
|
|
1054
|
-
rootLayout: router.rootLayout,
|
|
1055
|
-
handles: store.stream(),
|
|
1056
|
-
version,
|
|
1057
|
-
themeConfig: router.themeConfig,
|
|
1058
|
-
warmupEnabled: router.warmupEnabled,
|
|
1059
|
-
initialTheme: requireRequestContext().theme,
|
|
1060
|
-
// No routeName for not-found routes
|
|
1061
|
-
},
|
|
1062
|
-
};
|
|
1063
|
-
|
|
1064
|
-
const debugChannel = requireRequestContext()._debugChannel;
|
|
1065
|
-
const rscStream = renderToReadableStream(payload, {
|
|
1066
|
-
...(debugChannel && { debugChannel }),
|
|
1139
|
+
handledByBoundary: false,
|
|
1067
1140
|
});
|
|
1141
|
+
console.error(`[RSC] Error:`, error);
|
|
1142
|
+
throw error;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1068
1145
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
(
|
|
1074
|
-
!url.searchParams.has("__html")) ||
|
|
1075
|
-
url.searchParams.has("__rsc");
|
|
1076
|
-
|
|
1077
|
-
if (isRscRequest) {
|
|
1078
|
-
return createResponseWithMergedHeaders(rscStream, {
|
|
1079
|
-
status: 404,
|
|
1080
|
-
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
// Delegate to SSR for HTML response (reuse early setup if available)
|
|
1085
|
-
const [ssrModule, streamMode] = await getSSRSetup(
|
|
1086
|
-
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),
|
|
1087
1151
|
request,
|
|
1088
1152
|
env,
|
|
1089
|
-
|
|
1090
|
-
|
|
1153
|
+
variables,
|
|
1154
|
+
renderHandler,
|
|
1155
|
+
routeReverse,
|
|
1091
1156
|
);
|
|
1092
|
-
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
1093
|
-
nonce,
|
|
1094
|
-
streamMode,
|
|
1095
|
-
});
|
|
1096
1157
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1158
|
+
if (isPartial || actionContinuation) {
|
|
1159
|
+
const intercepted = interceptRedirectForPartial(
|
|
1160
|
+
mwResponse,
|
|
1161
|
+
createRedirectFlightResponse,
|
|
1162
|
+
);
|
|
1163
|
+
if (intercepted) return intercepted;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
return finalizeResponse(mwResponse);
|
|
1101
1167
|
}
|
|
1102
1168
|
|
|
1103
|
-
|
|
1104
|
-
|
|
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(
|
|
1105
1179
|
request,
|
|
1106
|
-
url,
|
|
1107
1180
|
env,
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1181
|
+
url,
|
|
1182
|
+
"render-start",
|
|
1183
|
+
renderOutcome.durationMs,
|
|
1184
|
+
routeKey,
|
|
1185
|
+
);
|
|
1112
1186
|
}
|
|
1187
|
+
return renderOutcome.result;
|
|
1113
1188
|
}
|
|
1114
1189
|
}
|