@rangojs/router 0.0.0-experimental.57 → 0.0.0-experimental.57005a2b
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 +2 -1
- package/dist/vite/index.js +507 -192
- package/dist/vite/index.js.bak +5448 -0
- package/package.json +3 -3
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/middleware/SKILL.md +32 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +764 -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/src/__internal.ts +1 -1
- package/src/browser/navigation-bridge.ts +21 -2
- package/src/browser/navigation-client.ts +34 -6
- package/src/browser/partial-update.ts +14 -2
- package/src/browser/prefetch/cache.ts +16 -6
- package/src/browser/prefetch/fetch.ts +60 -4
- package/src/browser/react/Link.tsx +25 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/scroll-restoration.ts +10 -8
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/build/generate-manifest.ts +3 -6
- package/src/build/route-trie.ts +50 -24
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/client.tsx +84 -230
- 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/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 +46 -6
- package/src/router/lazy-includes.ts +5 -5
- package/src/router/loader-resolution.ts +147 -19
- package/src/router/manifest.ts +12 -7
- package/src/router/match-api.ts +124 -189
- package/src/router/match-middleware/cache-lookup.ts +24 -7
- package/src/router/match-middleware/segment-resolution.ts +53 -0
- package/src/router/match-result.ts +82 -4
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/prerender-match.ts +108 -8
- 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 +11 -0
- package/src/router/segment-resolution/fresh.ts +59 -2
- package/src/router/segment-resolution/revalidation.ts +79 -6
- package/src/router.ts +13 -1
- package/src/rsc/handler.ts +468 -377
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/progressive-enhancement.ts +10 -2
- package/src/rsc/rsc-rendering.ts +5 -1
- package/src/rsc/server-action.ts +6 -0
- package/src/rsc/ssr-setup.ts +1 -1
- package/src/rsc/types.ts +1 -0
- 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 +40 -4
- package/src/server/handle-store.ts +19 -0
- package/src/server/request-context.ts +125 -3
- package/src/static-handler.ts +18 -6
- package/src/types/handler-context.ts +12 -2
- package/src/types/loader-types.ts +32 -4
- 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/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/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/refresh-cmd.ts +88 -26
- package/src/vite/rango.ts +2 -1
- package/src/vite/router-discovery.ts +178 -37
- package/src/vite/utils/prerender-utils.ts +37 -5
package/src/rsc/handler.ts
CHANGED
|
@@ -15,6 +15,7 @@ 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";
|
|
@@ -82,6 +83,11 @@ import {
|
|
|
82
83
|
mayNeedSSR,
|
|
83
84
|
SSR_SETUP_VAR,
|
|
84
85
|
} from "./ssr-setup.js";
|
|
86
|
+
import {
|
|
87
|
+
classifyRequest,
|
|
88
|
+
type RequestPlan,
|
|
89
|
+
type ExecutableRequestPlan,
|
|
90
|
+
} from "../router/request-classification.js";
|
|
85
91
|
|
|
86
92
|
/**
|
|
87
93
|
* Create an RSC request handler.
|
|
@@ -161,10 +167,13 @@ export function createRSCHandler<
|
|
|
161
167
|
phase: ErrorPhase,
|
|
162
168
|
context: Parameters<typeof invokeOnError<TEnv>>[3],
|
|
163
169
|
): void {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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);
|
|
168
177
|
}
|
|
169
178
|
invokeOnError(router.onError, error, phase, context, "RSC");
|
|
170
179
|
}
|
|
@@ -530,7 +539,9 @@ export function createRSCHandler<
|
|
|
530
539
|
});
|
|
531
540
|
};
|
|
532
541
|
|
|
533
|
-
// 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.
|
|
534
545
|
async function coreRequestHandler(
|
|
535
546
|
request: Request,
|
|
536
547
|
env: TEnv,
|
|
@@ -538,71 +549,112 @@ export function createRSCHandler<
|
|
|
538
549
|
variables: Record<string, any>,
|
|
539
550
|
nonce: string | undefined,
|
|
540
551
|
): Promise<Response> {
|
|
541
|
-
const previewStart = performance.now();
|
|
542
|
-
const preview = await router.previewMatch(request, { env });
|
|
543
|
-
const previewDur = performance.now() - previewStart;
|
|
544
552
|
const handlerTiming: string[] = variables.__handlerTiming || [];
|
|
545
|
-
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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,
|
|
556
576
|
),
|
|
557
|
-
|
|
558
|
-
|
|
577
|
+
{
|
|
578
|
+
headers: { "Content-Type": "application/json" },
|
|
579
|
+
},
|
|
559
580
|
);
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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);
|
|
569
631
|
}
|
|
570
|
-
|
|
632
|
+
// Full requests: let the pipeline handle the redirect via match()
|
|
633
|
+
// which returns { redirect: url }. Fall through to full-render.
|
|
571
634
|
}
|
|
572
635
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
if (mayNeedSSR(request, url)) {
|
|
577
|
-
variables[SSR_SETUP_VAR] = startSSRSetup(
|
|
578
|
-
handlerCtx,
|
|
579
|
-
request,
|
|
580
|
-
env,
|
|
581
|
-
url,
|
|
582
|
-
router.debugPerformance
|
|
583
|
-
? () => requireRequestContext()._metricsStore
|
|
584
|
-
: undefined,
|
|
636
|
+
if (plan.mode === "version-mismatch") {
|
|
637
|
+
console.log(
|
|
638
|
+
`[RSC] Version mismatch: client=${url.searchParams.get("_rsc_v")}, server=${version}. Forcing reload.`,
|
|
585
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
|
+
});
|
|
586
647
|
}
|
|
587
648
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
// PE form submissions before any execution. Regular page navigations
|
|
598
|
-
// (GET without _rsc_loader/_rsc_action) are not affected.
|
|
599
|
-
const originPhase: OriginCheckPhase | null = isAction
|
|
600
|
-
? "action"
|
|
601
|
-
: isLoaderFetch
|
|
602
|
-
? "loader"
|
|
603
|
-
: request.method === "POST"
|
|
604
|
-
? "pe-form"
|
|
605
|
-
: 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;
|
|
606
658
|
if (originPhase) {
|
|
607
659
|
const originResult = await checkRequestOrigin(
|
|
608
660
|
request,
|
|
@@ -652,13 +704,33 @@ export function createRSCHandler<
|
|
|
652
704
|
}
|
|
653
705
|
}
|
|
654
706
|
|
|
655
|
-
//
|
|
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
|
|
656
731
|
const handleStore = requireRequestContext()._handleStore;
|
|
657
732
|
|
|
658
733
|
// Wire up error reporting for late streaming-handle failures
|
|
659
|
-
// (LateHandlePushError: handle pushed after stream completion).
|
|
660
|
-
// Without this, these errors are only caught by React's error boundary
|
|
661
|
-
// and never reach the router's onError callback or telemetry.
|
|
662
734
|
handleStore.onError = (error: Error) => {
|
|
663
735
|
const reqCtx = requireRequestContext();
|
|
664
736
|
callOnError(error, "handler", {
|
|
@@ -688,37 +760,106 @@ export function createRSCHandler<
|
|
|
688
760
|
};
|
|
689
761
|
|
|
690
762
|
// Set route params early so all execution paths can access ctx.params.
|
|
691
|
-
|
|
692
|
-
|
|
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;
|
|
693
768
|
}
|
|
694
769
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
//
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
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,
|
|
708
835
|
variables,
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
);
|
|
712
|
-
if (progressiveResult) {
|
|
713
|
-
return progressiveResult;
|
|
836
|
+
plan.route.params,
|
|
837
|
+
);
|
|
714
838
|
}
|
|
715
839
|
|
|
716
|
-
//
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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;
|
|
722
863
|
try {
|
|
723
864
|
const actionOutcome = await withTimeout(
|
|
724
865
|
executeServerAction(
|
|
@@ -726,7 +867,7 @@ export function createRSCHandler<
|
|
|
726
867
|
request,
|
|
727
868
|
env,
|
|
728
869
|
url,
|
|
729
|
-
actionId,
|
|
870
|
+
plan.actionId,
|
|
730
871
|
handleStore,
|
|
731
872
|
),
|
|
732
873
|
router.timeouts.actionMs,
|
|
@@ -739,8 +880,8 @@ export function createRSCHandler<
|
|
|
739
880
|
url,
|
|
740
881
|
"action",
|
|
741
882
|
actionOutcome.durationMs,
|
|
742
|
-
|
|
743
|
-
actionId,
|
|
883
|
+
plan.route.routeKey,
|
|
884
|
+
plan.actionId,
|
|
744
885
|
);
|
|
745
886
|
}
|
|
746
887
|
const result = actionOutcome.result;
|
|
@@ -752,347 +893,297 @@ export function createRSCHandler<
|
|
|
752
893
|
request,
|
|
753
894
|
url,
|
|
754
895
|
env,
|
|
755
|
-
actionId,
|
|
896
|
+
actionId: plan.actionId,
|
|
756
897
|
handledByBoundary: false,
|
|
757
898
|
});
|
|
758
899
|
console.error(`[RSC] Action error:`, error);
|
|
759
900
|
throw error;
|
|
760
901
|
}
|
|
761
|
-
}
|
|
762
902
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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,
|
|
767
913
|
request,
|
|
768
914
|
env,
|
|
769
915
|
url,
|
|
770
916
|
variables,
|
|
771
917
|
nonce,
|
|
772
|
-
preview?.params,
|
|
773
|
-
preview?.routeKey,
|
|
774
918
|
handleStore,
|
|
919
|
+
isPartialAction,
|
|
775
920
|
actionContinuation,
|
|
776
921
|
);
|
|
777
|
-
|
|
778
|
-
response.headers.append("Vary", "Accept");
|
|
779
|
-
}
|
|
780
|
-
return response;
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
// Wrap the render path (with or without route middleware) in a
|
|
784
|
-
// renderStartMs timeout so slow renders are caught before output.
|
|
785
|
-
const executeRender = async (): Promise<Response> => {
|
|
786
|
-
if (preview?.routeMiddleware && preview.routeMiddleware.length > 0) {
|
|
787
|
-
const mwResponse = await executeMiddleware(
|
|
788
|
-
buildRouteMiddlewareEntries<TEnv>(preview.routeMiddleware),
|
|
789
|
-
request,
|
|
790
|
-
env,
|
|
791
|
-
variables,
|
|
792
|
-
renderHandler,
|
|
793
|
-
routeReverse,
|
|
794
|
-
);
|
|
795
|
-
|
|
796
|
-
if (
|
|
797
|
-
url.searchParams.has("_rsc_partial") ||
|
|
798
|
-
url.searchParams.has("_rsc_action")
|
|
799
|
-
) {
|
|
800
|
-
const intercepted = interceptRedirectForPartial(
|
|
801
|
-
mwResponse,
|
|
802
|
-
createRedirectFlightResponse,
|
|
803
|
-
);
|
|
804
|
-
if (intercepted) return intercepted;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
return finalizeResponse(mwResponse);
|
|
808
|
-
}
|
|
922
|
+
}
|
|
809
923
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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
|
+
}
|
|
813
941
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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,
|
|
821
950
|
request,
|
|
822
951
|
env,
|
|
823
952
|
url,
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
953
|
+
variables,
|
|
954
|
+
nonce,
|
|
955
|
+
handleStore,
|
|
956
|
+
false,
|
|
827
957
|
);
|
|
828
958
|
}
|
|
829
|
-
|
|
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
|
+
);
|
|
830
975
|
}
|
|
831
976
|
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
//
|
|
835
|
-
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>,
|
|
836
985
|
request: Request,
|
|
837
986
|
env: TEnv,
|
|
838
987
|
url: URL,
|
|
839
988
|
variables: Record<string, any>,
|
|
840
989
|
nonce: string | undefined,
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
handleStore?: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
990
|
+
handleStore: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
991
|
+
isPartial: boolean,
|
|
844
992
|
actionContinuation?: ActionContinuation,
|
|
845
993
|
): Promise<Response> {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
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
|
+
}
|
|
861
1040
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (referer) {
|
|
869
|
-
try {
|
|
870
|
-
const refererUrl = new URL(referer);
|
|
871
|
-
if (refererUrl.origin === url.origin) {
|
|
872
|
-
reloadUrl = referer;
|
|
873
|
-
}
|
|
874
|
-
} catch {
|
|
875
|
-
// Malformed referer, fall back to cleanUrl
|
|
1041
|
+
if (isPartial) {
|
|
1042
|
+
const intercepted = interceptRedirectForPartial(
|
|
1043
|
+
error,
|
|
1044
|
+
createRedirectFlightResponse,
|
|
1045
|
+
);
|
|
1046
|
+
if (intercepted) return intercepted;
|
|
876
1047
|
}
|
|
1048
|
+
|
|
1049
|
+
return error;
|
|
877
1050
|
}
|
|
878
|
-
}
|
|
879
1051
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
const isDev = process.env.NODE_ENV !== "production";
|
|
892
|
-
if (
|
|
893
|
-
url.searchParams.has("__debug_manifest") &&
|
|
894
|
-
(isDev || router.allowDebugManifest)
|
|
895
|
-
) {
|
|
896
|
-
const trie = getRouterTrie(router.id) ?? getRouteTrie();
|
|
897
|
-
const routeManifest = getRequiredRouteMap();
|
|
898
|
-
const { extractAncestryFromTrie } =
|
|
899
|
-
await import("../build/route-trie.js");
|
|
900
|
-
return new Response(
|
|
901
|
-
JSON.stringify(
|
|
902
|
-
{
|
|
903
|
-
routerId: router.id,
|
|
904
|
-
routeManifest,
|
|
905
|
-
routeAncestry: trie ? extractAncestryFromTrie(trie) : {},
|
|
906
|
-
routeTrie: trie,
|
|
907
|
-
precomputedEntries: getPrecomputedEntries(),
|
|
908
|
-
},
|
|
909
|
-
null,
|
|
910
|
-
2,
|
|
911
|
-
),
|
|
912
|
-
{
|
|
913
|
-
headers: { "Content-Type": "application/json" },
|
|
914
|
-
},
|
|
915
|
-
);
|
|
916
|
-
}
|
|
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
|
+
});
|
|
917
1063
|
|
|
918
|
-
|
|
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
|
+
};
|
|
919
1096
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
1097
|
+
const rscStream = renderToReadableStream(payload, {
|
|
1098
|
+
onError: (error: unknown) => {
|
|
1099
|
+
callOnError(error, "rendering", { request, url, env });
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
926
1102
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
handlerCtx,
|
|
933
|
-
request,
|
|
934
|
-
env,
|
|
935
|
-
url,
|
|
936
|
-
store,
|
|
937
|
-
actionContinuation,
|
|
938
|
-
);
|
|
939
|
-
}
|
|
1103
|
+
const isRscRequest =
|
|
1104
|
+
isPartial ||
|
|
1105
|
+
(!request.headers.get("accept")?.includes("text/html") &&
|
|
1106
|
+
!url.searchParams.has("__html")) ||
|
|
1107
|
+
url.searchParams.has("__rsc");
|
|
940
1108
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
handlerCtx,
|
|
948
|
-
request,
|
|
949
|
-
env,
|
|
950
|
-
url,
|
|
951
|
-
variables,
|
|
952
|
-
routeParams,
|
|
953
|
-
);
|
|
954
|
-
}
|
|
1109
|
+
if (isRscRequest) {
|
|
1110
|
+
return createResponseWithMergedHeaders(rscStream, {
|
|
1111
|
+
status: 404,
|
|
1112
|
+
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
955
1115
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
request,
|
|
963
|
-
env,
|
|
964
|
-
url,
|
|
965
|
-
isPartial,
|
|
966
|
-
store,
|
|
967
|
-
nonce,
|
|
968
|
-
);
|
|
969
|
-
} catch (error) {
|
|
970
|
-
// Check if middleware/handler returned Response
|
|
971
|
-
if (error instanceof Response) {
|
|
972
|
-
// During partial (client-side navigation), a 200 Response from a handler
|
|
973
|
-
// means the route serves raw content (JSON, text, etc.), not JSX.
|
|
974
|
-
// Signal the browser to hard-navigate so it renders the raw response.
|
|
975
|
-
// Only for 200 — redirects (3xx) work already because the browser follows
|
|
976
|
-
// them automatically to a URL that serves Flight data.
|
|
977
|
-
if (isPartial && error.status === 200) {
|
|
978
|
-
console.warn(
|
|
979
|
-
`[RSC] Route handler at ${url.pathname} returned a Response during client-side navigation. ` +
|
|
980
|
-
`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,
|
|
981
1122
|
);
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
"X-RSC-Reload": stripInternalParams(url).toString(),
|
|
986
|
-
"content-type": "text/x-component;charset=utf-8",
|
|
987
|
-
},
|
|
1123
|
+
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
1124
|
+
nonce,
|
|
1125
|
+
streamMode,
|
|
988
1126
|
});
|
|
989
|
-
}
|
|
990
1127
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
);
|
|
996
|
-
if (intercepted) return intercepted;
|
|
1128
|
+
return createResponseWithMergedHeaders(htmlStream, {
|
|
1129
|
+
status: 404,
|
|
1130
|
+
headers: { "content-type": "text/html;charset=utf-8" },
|
|
1131
|
+
});
|
|
997
1132
|
}
|
|
998
1133
|
|
|
999
|
-
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// Render 404 page for unmatched routes
|
|
1003
|
-
// Check both instanceof and error.name for cross-bundle compatibility
|
|
1004
|
-
const isRouteNotFound =
|
|
1005
|
-
error instanceof RouteNotFoundError ||
|
|
1006
|
-
(error instanceof Error && error.name === "RouteNotFoundError");
|
|
1007
|
-
if (isRouteNotFound) {
|
|
1134
|
+
// Report unhandled errors
|
|
1008
1135
|
callOnError(error, "routing", {
|
|
1009
1136
|
request,
|
|
1010
1137
|
url,
|
|
1011
1138
|
env,
|
|
1012
|
-
handledByBoundary:
|
|
1139
|
+
handledByBoundary: false,
|
|
1013
1140
|
});
|
|
1141
|
+
console.error(`[RSC] Error:`, error);
|
|
1142
|
+
throw error;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1014
1145
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
1021
|
-
|
|
1022
|
-
// Create a simple segment for the 404 page
|
|
1023
|
-
const notFoundSegment = {
|
|
1024
|
-
id: "notFound",
|
|
1025
|
-
namespace: "notFound",
|
|
1026
|
-
type: "route" as const,
|
|
1027
|
-
index: 0,
|
|
1028
|
-
component: notFoundComponent,
|
|
1029
|
-
params: {},
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
const payload: RscPayload = {
|
|
1033
|
-
metadata: {
|
|
1034
|
-
pathname: url.pathname,
|
|
1035
|
-
routerId: router.id,
|
|
1036
|
-
basename: router.basename,
|
|
1037
|
-
segments: [notFoundSegment],
|
|
1038
|
-
matched: [],
|
|
1039
|
-
diff: [],
|
|
1040
|
-
isPartial: false,
|
|
1041
|
-
rootLayout: router.rootLayout,
|
|
1042
|
-
handles: store.stream(),
|
|
1043
|
-
version,
|
|
1044
|
-
themeConfig: router.themeConfig,
|
|
1045
|
-
warmupEnabled: router.warmupEnabled,
|
|
1046
|
-
initialTheme: requireRequestContext().theme,
|
|
1047
|
-
// No routeName for not-found routes
|
|
1048
|
-
},
|
|
1049
|
-
};
|
|
1050
|
-
|
|
1051
|
-
const rscStream = renderToReadableStream(payload);
|
|
1052
|
-
|
|
1053
|
-
// Determine if this is an RSC request or HTML request.
|
|
1054
|
-
// Partial requests are always RSC (see main isRscRequest comment).
|
|
1055
|
-
const isRscRequest =
|
|
1056
|
-
isPartial ||
|
|
1057
|
-
(!request.headers.get("accept")?.includes("text/html") &&
|
|
1058
|
-
!url.searchParams.has("__html")) ||
|
|
1059
|
-
url.searchParams.has("__rsc");
|
|
1060
|
-
|
|
1061
|
-
if (isRscRequest) {
|
|
1062
|
-
return createResponseWithMergedHeaders(rscStream, {
|
|
1063
|
-
status: 404,
|
|
1064
|
-
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// Delegate to SSR for HTML response (reuse early setup if available)
|
|
1069
|
-
const [ssrModule, streamMode] = await getSSRSetup(
|
|
1070
|
-
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),
|
|
1071
1151
|
request,
|
|
1072
1152
|
env,
|
|
1073
|
-
|
|
1074
|
-
|
|
1153
|
+
variables,
|
|
1154
|
+
renderHandler,
|
|
1155
|
+
routeReverse,
|
|
1075
1156
|
);
|
|
1076
|
-
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
1077
|
-
nonce,
|
|
1078
|
-
streamMode,
|
|
1079
|
-
});
|
|
1080
1157
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1158
|
+
if (isPartial || actionContinuation) {
|
|
1159
|
+
const intercepted = interceptRedirectForPartial(
|
|
1160
|
+
mwResponse,
|
|
1161
|
+
createRedirectFlightResponse,
|
|
1162
|
+
);
|
|
1163
|
+
if (intercepted) return intercepted;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
return finalizeResponse(mwResponse);
|
|
1085
1167
|
}
|
|
1086
1168
|
|
|
1087
|
-
|
|
1088
|
-
|
|
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(
|
|
1089
1179
|
request,
|
|
1090
|
-
url,
|
|
1091
1180
|
env,
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1181
|
+
url,
|
|
1182
|
+
"render-start",
|
|
1183
|
+
renderOutcome.durationMs,
|
|
1184
|
+
routeKey,
|
|
1185
|
+
);
|
|
1096
1186
|
}
|
|
1187
|
+
return renderOutcome.result;
|
|
1097
1188
|
}
|
|
1098
1189
|
}
|