@rangojs/router 0.0.0-experimental.a769fbe7 → 0.0.0-experimental.ac99d918
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 +211 -43
- package/dist/bin/rango.js +279 -102
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3313 -1160
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +62 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +63 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +124 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +223 -9
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +77 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +66 -5
- package/skills/router-setup/SKILL.md +38 -3
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +319 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +81 -147
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +66 -14
- package/src/browser/navigation-client.ts +172 -109
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +79 -93
- package/src/browser/prefetch/cache.ts +180 -62
- package/src/browser/prefetch/fetch.ts +252 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +72 -10
- package/src/browser/react/NavigationProvider.tsx +83 -31
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +111 -24
- package/src/browser/scroll-restoration.ts +29 -19
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -62
- package/src/browser/types.ts +60 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +6 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +333 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2256 -241
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/browser.ts +0 -1
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +69 -10
- package/src/index.ts +112 -14
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +413 -275
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +107 -32
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +50 -8
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +46 -30
- package/src/router/intercept-resolution.ts +23 -23
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +207 -30
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +161 -262
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +169 -140
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +119 -65
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +329 -305
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +128 -42
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -409
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +18 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +28 -4
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +53 -56
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -69
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +22 -9
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +208 -201
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +211 -52
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +289 -124
- package/src/ssr/index.tsx +22 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +124 -70
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +40 -11
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +36 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +236 -6
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- 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 +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +65 -207
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +46 -17
- package/src/vite/rango.ts +207 -125
- package/src/vite/router-discovery.ts +931 -133
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +97 -44
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/debug-channel.ts +0 -93
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
package/src/rsc/handler.ts
CHANGED
|
@@ -8,21 +8,17 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { createElement } from "react";
|
|
11
|
-
import {
|
|
11
|
+
import { isRouteNotFoundError } from "../errors.js";
|
|
12
12
|
import { matchMiddleware, executeMiddleware } from "../router/middleware.js";
|
|
13
13
|
import {
|
|
14
14
|
runWithRequestContext,
|
|
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,
|
|
@@ -35,6 +31,8 @@ import {
|
|
|
35
31
|
interceptRedirectForPartial,
|
|
36
32
|
buildRouteMiddlewareEntries,
|
|
37
33
|
} from "./helpers.js";
|
|
34
|
+
import { guardOutgoingRedirect } from "./redirect-guard.js";
|
|
35
|
+
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
38
36
|
import {
|
|
39
37
|
handleResponseRoute,
|
|
40
38
|
type ResponseRouteMatch,
|
|
@@ -60,6 +58,8 @@ import {
|
|
|
60
58
|
getRouterTrie,
|
|
61
59
|
} from "../route-map-builder.js";
|
|
62
60
|
import type { HandlerContext } from "./handler-context.js";
|
|
61
|
+
import type { CacheErrorCategory } from "../cache/cache-error.js";
|
|
62
|
+
import type { SegmentCacheStore } from "../cache/types.js";
|
|
63
63
|
import { buildRouterTrieFromUrlpatterns } from "./manifest-init.js";
|
|
64
64
|
import { handleProgressiveEnhancement } from "./progressive-enhancement.js";
|
|
65
65
|
import {
|
|
@@ -68,7 +68,10 @@ import {
|
|
|
68
68
|
type ActionContinuation,
|
|
69
69
|
} from "./server-action.js";
|
|
70
70
|
import { handleLoaderFetch } from "./loader-fetch.js";
|
|
71
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
checkRequestOrigin,
|
|
73
|
+
ORIGIN_CHECK_PHASE_BY_MODE,
|
|
74
|
+
} from "./origin-guard.js";
|
|
72
75
|
import { handleRscRendering } from "./rsc-rendering.js";
|
|
73
76
|
import {
|
|
74
77
|
withTimeout,
|
|
@@ -85,8 +88,14 @@ import {
|
|
|
85
88
|
startSSRSetup,
|
|
86
89
|
getSSRSetup,
|
|
87
90
|
mayNeedSSR,
|
|
91
|
+
isRscRequest,
|
|
88
92
|
SSR_SETUP_VAR,
|
|
89
93
|
} from "./ssr-setup.js";
|
|
94
|
+
import {
|
|
95
|
+
classifyRequest,
|
|
96
|
+
type RequestPlan,
|
|
97
|
+
type ExecutableRequestPlan,
|
|
98
|
+
} from "../router/request-classification.js";
|
|
90
99
|
|
|
91
100
|
/**
|
|
92
101
|
* Create an RSC request handler.
|
|
@@ -121,12 +130,35 @@ import {
|
|
|
121
130
|
* });
|
|
122
131
|
* ```
|
|
123
132
|
*/
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Response that tells the client to do a full document navigation. Shared by
|
|
136
|
+
* the terminal reload plans (version-mismatch and app-switch): an empty 200
|
|
137
|
+
* carrying X-RSC-Reload, which the client turns into window.location.href.
|
|
138
|
+
*/
|
|
139
|
+
function createReloadResponse(reloadUrl: string) {
|
|
140
|
+
return createResponseWithMergedHeaders(null, {
|
|
141
|
+
status: 200,
|
|
142
|
+
headers: {
|
|
143
|
+
"X-RSC-Reload": reloadUrl,
|
|
144
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
124
149
|
export function createRSCHandler<
|
|
125
150
|
TEnv = unknown,
|
|
126
151
|
TRoutes extends Record<string, string> = Record<string, string>,
|
|
127
152
|
>(options: CreateRSCHandlerOptions<TEnv, TRoutes>) {
|
|
128
153
|
const { router, version = VERSION, nonce: nonceProvider } = options;
|
|
129
154
|
|
|
155
|
+
// Handler-owned registry of explicit per-scope stores from cache({ store }).
|
|
156
|
+
// Lives in the closure so it is scoped per handler (multi-router deployments
|
|
157
|
+
// get separate registries) and accumulates every explicit store this handler
|
|
158
|
+
// resolves across requests. updateTag()/revalidateTag() iterate it to reach
|
|
159
|
+
// stores not covered by the app-level ctx._cacheStore.
|
|
160
|
+
const explicitTaggedStores = new Set<SegmentCacheStore>();
|
|
161
|
+
|
|
130
162
|
// Use provided deps or default to @vitejs/plugin-rsc/rsc exports
|
|
131
163
|
const deps = options.deps ?? rscDeps;
|
|
132
164
|
const {
|
|
@@ -166,10 +198,13 @@ export function createRSCHandler<
|
|
|
166
198
|
phase: ErrorPhase,
|
|
167
199
|
context: Parameters<typeof invokeOnError<TEnv>>[3],
|
|
168
200
|
): void {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
201
|
+
// Guard: abort signal handlers fire asynchronously outside the ALS
|
|
202
|
+
// request scope, so the context may be gone. Skip dedup in that
|
|
203
|
+
// case — the error is from a cancelled stream, not a real failure.
|
|
204
|
+
const reqCtx = _getRequestContext();
|
|
205
|
+
if (error != null && typeof error === "object" && reqCtx) {
|
|
206
|
+
if (reqCtx._reportedErrors.has(error)) return;
|
|
207
|
+
reqCtx._reportedErrors.add(error);
|
|
173
208
|
}
|
|
174
209
|
invokeOnError(router.onError, error, phase, context, "RSC");
|
|
175
210
|
}
|
|
@@ -258,19 +293,17 @@ export function createRSCHandler<
|
|
|
258
293
|
function createRedirectFlightResponse(
|
|
259
294
|
redirectUrl: string,
|
|
260
295
|
locationState?: Record<string, unknown>,
|
|
296
|
+
external?: boolean,
|
|
261
297
|
): Response {
|
|
262
298
|
const redirectPayload: RscPayload = {
|
|
263
299
|
metadata: {
|
|
264
300
|
pathname: redirectUrl,
|
|
265
301
|
segments: [],
|
|
266
|
-
redirect: { url: redirectUrl },
|
|
302
|
+
redirect: { url: redirectUrl, ...(external && { external: true }) },
|
|
267
303
|
...(locationState && { locationState }),
|
|
268
304
|
},
|
|
269
305
|
};
|
|
270
|
-
const
|
|
271
|
-
const rscStream = renderToReadableStream<RscPayload>(redirectPayload, {
|
|
272
|
-
...(debugChannel && { debugChannel }),
|
|
273
|
-
});
|
|
306
|
+
const rscStream = renderToReadableStream<RscPayload>(redirectPayload);
|
|
274
307
|
return createResponseWithMergedHeaders(rscStream, {
|
|
275
308
|
status: 200,
|
|
276
309
|
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
@@ -351,7 +384,7 @@ export function createRSCHandler<
|
|
|
351
384
|
// Resolve cache store configuration
|
|
352
385
|
// Priority: options.cache (handler override) > router.cache (router default)
|
|
353
386
|
// Store is enabled only if: config provided, enabled, and no ?__no_cache query param
|
|
354
|
-
let cacheStore
|
|
387
|
+
let cacheStore: SegmentCacheStore | undefined;
|
|
355
388
|
const cacheOption = options.cache ?? router.cache;
|
|
356
389
|
if (cacheOption && !url.searchParams.has("__no_cache")) {
|
|
357
390
|
const cacheConfig =
|
|
@@ -418,34 +451,22 @@ export function createRSCHandler<
|
|
|
418
451
|
url,
|
|
419
452
|
variables,
|
|
420
453
|
cacheStore,
|
|
454
|
+
explicitTaggedStores,
|
|
421
455
|
cacheProfiles: router.cacheProfiles,
|
|
422
456
|
executionContext: executionCtx,
|
|
423
457
|
themeConfig: router.themeConfig,
|
|
458
|
+
stateCookieName: router.resolvedStateCookieName,
|
|
459
|
+
version,
|
|
424
460
|
});
|
|
425
461
|
if (earlyMetricsStore) {
|
|
426
462
|
requestContext._debugPerformance = true;
|
|
427
463
|
requestContext._metricsStore = earlyMetricsStore;
|
|
428
464
|
}
|
|
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
465
|
// Wire background error reporting so "use cache" and other subsystems
|
|
445
466
|
// can surface non-fatal errors through the router's onError callback.
|
|
446
467
|
requestContext._reportBackgroundError = (
|
|
447
468
|
error: unknown,
|
|
448
|
-
category:
|
|
469
|
+
category: CacheErrorCategory,
|
|
449
470
|
) => {
|
|
450
471
|
callOnError(error, "cache", {
|
|
451
472
|
request,
|
|
@@ -475,6 +496,9 @@ export function createRSCHandler<
|
|
|
475
496
|
// - Server components during rendering
|
|
476
497
|
// - Error boundaries
|
|
477
498
|
// - Streaming
|
|
499
|
+
// Store basename on request context (scoped per-request via existing ALS)
|
|
500
|
+
requestContext._basename = router.basename;
|
|
501
|
+
|
|
478
502
|
return runWithRequestContext(requestContext, async () => {
|
|
479
503
|
// Core handler logic (wrapped by middleware)
|
|
480
504
|
const coreHandler = async (): Promise<Response> => {
|
|
@@ -544,13 +568,22 @@ export function createRSCHandler<
|
|
|
544
568
|
}
|
|
545
569
|
|
|
546
570
|
const fullTiming = timingParts.join(", ");
|
|
547
|
-
if (fullTiming
|
|
571
|
+
if (fullTiming && !isWebSocketUpgradeResponse(response)) {
|
|
572
|
+
response.headers.set("Server-Timing", fullTiming);
|
|
573
|
+
}
|
|
548
574
|
|
|
549
|
-
|
|
575
|
+
// Single open-redirect chokepoint: every response (PE, full-page,
|
|
576
|
+
// middleware short-circuit, response-route) funnels through here, so
|
|
577
|
+
// guarding browser-followed (3xx) redirects once covers them all and any
|
|
578
|
+
// future redirect exit. Soft SPA/Flight redirects are 200/204 and pass
|
|
579
|
+
// through untouched (validated client-side instead).
|
|
580
|
+
return guardOutgoingRedirect(response, url.origin, router.basename);
|
|
550
581
|
});
|
|
551
582
|
};
|
|
552
583
|
|
|
553
|
-
// Core request handling logic (separated for middleware wrapping)
|
|
584
|
+
// Core request handling logic (separated for middleware wrapping).
|
|
585
|
+
// Uses the classify → execute model: classifyRequest produces a RequestPlan,
|
|
586
|
+
// then execution dispatches on the plan mode.
|
|
554
587
|
async function coreRequestHandler(
|
|
555
588
|
request: Request,
|
|
556
589
|
env: TEnv,
|
|
@@ -558,71 +591,103 @@ export function createRSCHandler<
|
|
|
558
591
|
variables: Record<string, any>,
|
|
559
592
|
nonce: string | undefined,
|
|
560
593
|
): Promise<Response> {
|
|
561
|
-
const previewStart = performance.now();
|
|
562
|
-
const preview = await router.previewMatch(request, { env });
|
|
563
|
-
const previewDur = performance.now() - previewStart;
|
|
564
594
|
const handlerTiming: string[] = variables.__handlerTiming || [];
|
|
565
|
-
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
595
|
+
|
|
596
|
+
// Debug manifest endpoint: handled before classification since it
|
|
597
|
+
// doesn't need a route match and needs trie access from the closure.
|
|
598
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
599
|
+
if (
|
|
600
|
+
url.searchParams.has("__debug_manifest") &&
|
|
601
|
+
(isDev || router.allowDebugManifest)
|
|
602
|
+
) {
|
|
603
|
+
const trie = getRouterTrie(router.id) ?? getRouteTrie();
|
|
604
|
+
const routeManifest = getRequiredRouteMap();
|
|
605
|
+
const { extractAncestryFromTrie } =
|
|
606
|
+
await import("../build/route-trie.js");
|
|
607
|
+
return new Response(
|
|
608
|
+
JSON.stringify(
|
|
609
|
+
{
|
|
610
|
+
routerId: router.id,
|
|
611
|
+
routeManifest,
|
|
612
|
+
routeAncestry: trie ? extractAncestryFromTrie(trie) : {},
|
|
613
|
+
routeTrie: trie,
|
|
614
|
+
precomputedEntries: getPrecomputedEntries(),
|
|
615
|
+
},
|
|
616
|
+
null,
|
|
617
|
+
2,
|
|
576
618
|
),
|
|
577
|
-
|
|
578
|
-
|
|
619
|
+
{
|
|
620
|
+
headers: { "Content-Type": "application/json" },
|
|
621
|
+
},
|
|
579
622
|
);
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// ---- 1. Classify ----
|
|
626
|
+
// classifyRequest may throw RouteNotFoundError for unknown routes.
|
|
627
|
+
// In that case, fall through to a full-render plan so the pipeline
|
|
628
|
+
// can render the 404 page via the existing error handling path.
|
|
629
|
+
const classifyStart = performance.now();
|
|
630
|
+
let plan: RequestPlan<TEnv>;
|
|
631
|
+
try {
|
|
632
|
+
plan = await classifyRequest<TEnv>(request, url, {
|
|
633
|
+
findMatch: router.findMatch,
|
|
634
|
+
routerVersion: version,
|
|
635
|
+
routerId: router.id,
|
|
636
|
+
});
|
|
637
|
+
} catch (error) {
|
|
638
|
+
if (isRouteNotFoundError(error)) {
|
|
639
|
+
// Let the render path handle 404 — match()/matchPartial() will
|
|
640
|
+
// re-throw RouteNotFoundError and the catch block in
|
|
641
|
+
// executeRenderWithMiddleware renders the not-found page.
|
|
642
|
+
plan = {
|
|
643
|
+
mode: "full-render",
|
|
644
|
+
route: {
|
|
645
|
+
matched: null as any,
|
|
646
|
+
manifestEntry: null as any,
|
|
647
|
+
entries: [],
|
|
648
|
+
routeKey: "",
|
|
649
|
+
localRouteName: "",
|
|
650
|
+
params: {},
|
|
651
|
+
routeMiddleware: [],
|
|
652
|
+
cacheScope: null,
|
|
653
|
+
isPassthrough: false,
|
|
654
|
+
},
|
|
655
|
+
negotiated: false,
|
|
656
|
+
};
|
|
657
|
+
} else {
|
|
658
|
+
throw error;
|
|
589
659
|
}
|
|
590
|
-
|
|
660
|
+
}
|
|
661
|
+
const classifyDur = performance.now() - classifyStart;
|
|
662
|
+
handlerTiming.push(`handler-classify;dur=${classifyDur.toFixed(2)}`);
|
|
663
|
+
|
|
664
|
+
// ---- 2. Terminal plans (no execution needed) ----
|
|
665
|
+
if (plan.mode === "redirect") {
|
|
666
|
+
// Redirects are handled by the pipeline (match/matchPartial),
|
|
667
|
+
// but for partial requests we short-circuit with a Flight redirect.
|
|
668
|
+
if (url.searchParams.has("_rsc_partial")) {
|
|
669
|
+
return createRedirectFlightResponse(plan.redirectUrl);
|
|
670
|
+
}
|
|
671
|
+
// Full requests: let the pipeline handle the redirect via match()
|
|
672
|
+
// which returns { redirect: url }. Fall through to full-render.
|
|
591
673
|
}
|
|
592
674
|
|
|
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,
|
|
675
|
+
if (plan.mode === "version-mismatch") {
|
|
676
|
+
console.log(
|
|
677
|
+
`[RSC] Version mismatch: client=${url.searchParams.get("_rsc_v")}, server=${version}. Forcing reload.`,
|
|
605
678
|
);
|
|
679
|
+
return createReloadResponse(plan.reloadUrl);
|
|
606
680
|
}
|
|
607
681
|
|
|
608
|
-
|
|
682
|
+
if (plan.mode === "app-switch") {
|
|
683
|
+
// Cross-app SPA navigation crossed a host-router app boundary. Force a
|
|
684
|
+
// real document navigation so the target app's document is re-established
|
|
685
|
+
// (stylesheets, theme, warmup, prefetch-TTL). See request-classification.
|
|
686
|
+
return createReloadResponse(plan.reloadUrl);
|
|
687
|
+
}
|
|
609
688
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
const isLoaderFetch = url.searchParams.has("_rsc_loader");
|
|
613
|
-
const actionId =
|
|
614
|
-
request.headers.get("rsc-action") || url.searchParams.get("_rsc_action");
|
|
615
|
-
|
|
616
|
-
// Origin guard: reject cross-origin actions, loader fetches, and
|
|
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;
|
|
689
|
+
// ---- 3. Origin guard (gate for action/loader/PE modes) ----
|
|
690
|
+
const originPhase = ORIGIN_CHECK_PHASE_BY_MODE[plan.mode];
|
|
626
691
|
if (originPhase) {
|
|
627
692
|
const originResult = await checkRequestOrigin(
|
|
628
693
|
request,
|
|
@@ -672,13 +737,33 @@ export function createRSCHandler<
|
|
|
672
737
|
}
|
|
673
738
|
}
|
|
674
739
|
|
|
675
|
-
//
|
|
740
|
+
// ---- 4. Execute ----
|
|
741
|
+
return executeRequest(
|
|
742
|
+
plan as ExecutableRequestPlan<TEnv>,
|
|
743
|
+
request,
|
|
744
|
+
env,
|
|
745
|
+
url,
|
|
746
|
+
variables,
|
|
747
|
+
nonce,
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Execute a classified request plan. Dispatches to the appropriate handler
|
|
752
|
+
// based on plan.mode. Lives in the createRSCHandler closure for access to
|
|
753
|
+
// handlerCtx, router, callOnError, etc.
|
|
754
|
+
// Only receives executable plans (version-mismatch is handled above).
|
|
755
|
+
async function executeRequest(
|
|
756
|
+
plan: ExecutableRequestPlan<TEnv>,
|
|
757
|
+
request: Request,
|
|
758
|
+
env: TEnv,
|
|
759
|
+
url: URL,
|
|
760
|
+
variables: Record<string, any>,
|
|
761
|
+
nonce: string | undefined,
|
|
762
|
+
): Promise<Response> {
|
|
763
|
+
// Common setup
|
|
676
764
|
const handleStore = requireRequestContext()._handleStore;
|
|
677
765
|
|
|
678
766
|
// 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
767
|
handleStore.onError = (error: Error) => {
|
|
683
768
|
const reqCtx = requireRequestContext();
|
|
684
769
|
callOnError(error, "handler", {
|
|
@@ -708,37 +793,106 @@ export function createRSCHandler<
|
|
|
708
793
|
};
|
|
709
794
|
|
|
710
795
|
// Set route params early so all execution paths can access ctx.params.
|
|
711
|
-
|
|
712
|
-
|
|
796
|
+
// Also store the classified snapshot so match/matchPartial can reuse it
|
|
797
|
+
// instead of calling resolveRoute again.
|
|
798
|
+
if (plan.mode !== "redirect") {
|
|
799
|
+
setRequestContextParams(plan.route.params, plan.route.routeKey);
|
|
800
|
+
requireRequestContext()._classifiedRoute = plan.route;
|
|
713
801
|
}
|
|
714
802
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
//
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
routeMiddleware:
|
|
803
|
+
const routeReverse = createReverseFunction(getRequiredRouteMap());
|
|
804
|
+
|
|
805
|
+
// ---- Response route: skip entire RSC pipeline ----
|
|
806
|
+
if (plan.mode === "response") {
|
|
807
|
+
// Build ResponseRouteMatch from plan fields. handleResponseRoute
|
|
808
|
+
// expects a flat object with params at the top level.
|
|
809
|
+
const responseMatch: ResponseRouteMatch = {
|
|
810
|
+
responseType: plan.responseType,
|
|
811
|
+
handler: plan.handler,
|
|
812
|
+
params: plan.route.params,
|
|
813
|
+
negotiated: plan.negotiated,
|
|
814
|
+
manifestEntry: plan.manifestEntry,
|
|
815
|
+
routeMiddleware: plan.routeMiddleware,
|
|
816
|
+
};
|
|
817
|
+
const responseOutcome = await withTimeout(
|
|
818
|
+
handleResponseRoute(
|
|
819
|
+
handlerCtx,
|
|
820
|
+
responseMatch,
|
|
821
|
+
request,
|
|
822
|
+
env,
|
|
823
|
+
url,
|
|
824
|
+
variables,
|
|
825
|
+
),
|
|
826
|
+
router.timeouts.renderStartMs,
|
|
827
|
+
"render-start",
|
|
828
|
+
);
|
|
829
|
+
if (responseOutcome.timedOut) {
|
|
830
|
+
return handleTimeoutResponse(
|
|
831
|
+
request,
|
|
832
|
+
env,
|
|
833
|
+
url,
|
|
834
|
+
"render-start",
|
|
835
|
+
responseOutcome.durationMs,
|
|
836
|
+
plan.route.routeKey,
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const response = responseOutcome.result;
|
|
840
|
+
if (plan.negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
841
|
+
response.headers.append("Vary", "Accept");
|
|
842
|
+
}
|
|
843
|
+
return response;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// SSR setup: kick off in parallel for modes that need HTML rendering.
|
|
847
|
+
// Placed after response-route short-circuit so response/mime routes
|
|
848
|
+
// never pay for SSR work.
|
|
849
|
+
if (plan.mode !== "loader" && mayNeedSSR(request, url)) {
|
|
850
|
+
variables[SSR_SETUP_VAR] = startSSRSetup(
|
|
851
|
+
handlerCtx,
|
|
852
|
+
request,
|
|
853
|
+
env,
|
|
854
|
+
url,
|
|
855
|
+
router.debugPerformance
|
|
856
|
+
? () => requireRequestContext()._metricsStore
|
|
857
|
+
: undefined,
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// ---- Loader fetch ----
|
|
862
|
+
if (plan.mode === "loader") {
|
|
863
|
+
return handleLoaderFetch(
|
|
864
|
+
handlerCtx,
|
|
865
|
+
request,
|
|
866
|
+
env,
|
|
867
|
+
url,
|
|
728
868
|
variables,
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
869
|
+
plan.route.params,
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// ---- Progressive enhancement ----
|
|
874
|
+
if (plan.mode === "pe-render") {
|
|
875
|
+
const peResult = await handleProgressiveEnhancement(
|
|
876
|
+
handlerCtx,
|
|
877
|
+
request,
|
|
878
|
+
env,
|
|
879
|
+
url,
|
|
880
|
+
false, // isAction = false for PE
|
|
881
|
+
handleStore,
|
|
882
|
+
nonce,
|
|
883
|
+
{
|
|
884
|
+
routeMiddleware: plan.route.routeMiddleware,
|
|
885
|
+
variables,
|
|
886
|
+
routeReverse,
|
|
887
|
+
},
|
|
888
|
+
);
|
|
889
|
+
if (peResult) return peResult;
|
|
890
|
+
// PE handler returned null (not a PE form) — fall through to render
|
|
734
891
|
}
|
|
735
892
|
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
// the revalidation pass (identical to a normal render).
|
|
740
|
-
let actionContinuation: ActionContinuation | undefined;
|
|
741
|
-
if (isAction && actionId) {
|
|
893
|
+
// ---- Action: execute action, then revalidate wrapped in route middleware ----
|
|
894
|
+
if (plan.mode === "action") {
|
|
895
|
+
let actionContinuation: ActionContinuation | undefined;
|
|
742
896
|
try {
|
|
743
897
|
const actionOutcome = await withTimeout(
|
|
744
898
|
executeServerAction(
|
|
@@ -746,7 +900,7 @@ export function createRSCHandler<
|
|
|
746
900
|
request,
|
|
747
901
|
env,
|
|
748
902
|
url,
|
|
749
|
-
actionId,
|
|
903
|
+
plan.actionId,
|
|
750
904
|
handleStore,
|
|
751
905
|
),
|
|
752
906
|
router.timeouts.actionMs,
|
|
@@ -759,8 +913,8 @@ export function createRSCHandler<
|
|
|
759
913
|
url,
|
|
760
914
|
"action",
|
|
761
915
|
actionOutcome.durationMs,
|
|
762
|
-
|
|
763
|
-
actionId,
|
|
916
|
+
plan.route.routeKey,
|
|
917
|
+
plan.actionId,
|
|
764
918
|
);
|
|
765
919
|
}
|
|
766
920
|
const result = actionOutcome.result;
|
|
@@ -772,40 +926,237 @@ export function createRSCHandler<
|
|
|
772
926
|
request,
|
|
773
927
|
url,
|
|
774
928
|
env,
|
|
775
|
-
actionId,
|
|
929
|
+
actionId: plan.actionId,
|
|
776
930
|
handledByBoundary: false,
|
|
777
931
|
});
|
|
778
932
|
console.error(`[RSC] Action error:`, error);
|
|
779
933
|
throw error;
|
|
780
934
|
}
|
|
781
|
-
}
|
|
782
935
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
936
|
+
// Revalidation render wrapped in route middleware.
|
|
937
|
+
// Actions from client-side navigation include _rsc_partial — preserve
|
|
938
|
+
// the partial flag so the revalidation returns a Flight stream, not HTML.
|
|
939
|
+
// App-switch is already excluded by classifyRequest (would be full-render).
|
|
940
|
+
const isPartialAction = url.searchParams.has("_rsc_partial");
|
|
941
|
+
return executeRenderWithMiddleware(
|
|
942
|
+
plan.route.routeMiddleware,
|
|
943
|
+
plan.negotiated,
|
|
944
|
+
plan.route.routeKey,
|
|
945
|
+
routeReverse,
|
|
787
946
|
request,
|
|
788
947
|
env,
|
|
789
948
|
url,
|
|
790
949
|
variables,
|
|
791
950
|
nonce,
|
|
792
|
-
preview?.params,
|
|
793
|
-
preview?.routeKey,
|
|
794
951
|
handleStore,
|
|
952
|
+
isPartialAction,
|
|
795
953
|
actionContinuation,
|
|
796
954
|
);
|
|
797
|
-
|
|
798
|
-
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Full render, partial render, fallen-through PE, and full-page redirect all
|
|
958
|
+
// render through the same middleware-wrapped path. Only full/partial-render
|
|
959
|
+
// carry negotiation + the partial flag; pe/redirect render plainly.
|
|
960
|
+
const isPartial = plan.mode === "partial-render";
|
|
961
|
+
const negotiated =
|
|
962
|
+
plan.mode === "full-render" || plan.mode === "partial-render"
|
|
963
|
+
? plan.negotiated
|
|
964
|
+
: false;
|
|
965
|
+
return executeRenderWithMiddleware(
|
|
966
|
+
plan.route.routeMiddleware,
|
|
967
|
+
negotiated,
|
|
968
|
+
plan.route.routeKey,
|
|
969
|
+
routeReverse,
|
|
970
|
+
request,
|
|
971
|
+
env,
|
|
972
|
+
url,
|
|
973
|
+
variables,
|
|
974
|
+
nonce,
|
|
975
|
+
handleStore,
|
|
976
|
+
isPartial,
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Shared render execution: wraps handleRscRendering (or revalidateAfterAction)
|
|
981
|
+
// in route middleware and timeout handling. Consolidates the pattern used by
|
|
982
|
+
// action-revalidate, full-render, and partial-render modes.
|
|
983
|
+
async function executeRenderWithMiddleware(
|
|
984
|
+
routeMiddleware: import("../router/middleware-types.js").CollectedMiddleware[],
|
|
985
|
+
negotiated: boolean,
|
|
986
|
+
routeKey: string,
|
|
987
|
+
routeReverse: ReturnType<typeof createReverseFunction>,
|
|
988
|
+
request: Request,
|
|
989
|
+
env: TEnv,
|
|
990
|
+
url: URL,
|
|
991
|
+
variables: Record<string, any>,
|
|
992
|
+
nonce: string | undefined,
|
|
993
|
+
handleStore: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
994
|
+
isPartial: boolean,
|
|
995
|
+
actionContinuation?: ActionContinuation,
|
|
996
|
+
): Promise<Response> {
|
|
997
|
+
const renderHandler = async (): Promise<Response> => {
|
|
998
|
+
try {
|
|
999
|
+
let response: Response;
|
|
1000
|
+
if (actionContinuation) {
|
|
1001
|
+
response = await revalidateAfterAction(
|
|
1002
|
+
handlerCtx,
|
|
1003
|
+
request,
|
|
1004
|
+
env,
|
|
1005
|
+
url,
|
|
1006
|
+
handleStore,
|
|
1007
|
+
actionContinuation,
|
|
1008
|
+
);
|
|
1009
|
+
} else {
|
|
1010
|
+
response = await handleRscRendering(
|
|
1011
|
+
handlerCtx,
|
|
1012
|
+
request,
|
|
1013
|
+
env,
|
|
1014
|
+
url,
|
|
1015
|
+
isPartial,
|
|
1016
|
+
handleStore,
|
|
1017
|
+
nonce,
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
if (negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
1021
|
+
response.headers.append("Vary", "Accept");
|
|
1022
|
+
}
|
|
1023
|
+
return response;
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
// Check if middleware/handler returned Response
|
|
1026
|
+
if (error instanceof Response) {
|
|
1027
|
+
// An action revalidation render is delivered to the client over the
|
|
1028
|
+
// same Flight-parsing path as a partial navigation, so a Response
|
|
1029
|
+
// thrown during it must be converted exactly like a partial one
|
|
1030
|
+
// (raw 200 -> hard-nav hint, 3xx -> Flight redirect). Without this,
|
|
1031
|
+
// the no-middleware path returns the raw Response (the with-middleware
|
|
1032
|
+
// path is already covered by the isPartial || actionContinuation
|
|
1033
|
+
// guard below).
|
|
1034
|
+
const treatAsPartial = isPartial || actionContinuation != null;
|
|
1035
|
+
|
|
1036
|
+
// During partial (client-side navigation), a 200 Response from a handler
|
|
1037
|
+
// means the route serves raw content (JSON, text, etc.), not JSX.
|
|
1038
|
+
// Signal the browser to hard-navigate so it renders the raw response.
|
|
1039
|
+
if (treatAsPartial && error.status === 200) {
|
|
1040
|
+
console.warn(
|
|
1041
|
+
`[RSC] Route handler at ${url.pathname} returned a Response during client-side navigation. ` +
|
|
1042
|
+
`Falling back to hard navigation. Use data-external on the <Link> to avoid the extra round-trip.`,
|
|
1043
|
+
);
|
|
1044
|
+
return createResponseWithMergedHeaders(null, {
|
|
1045
|
+
status: 200,
|
|
1046
|
+
headers: {
|
|
1047
|
+
"X-RSC-Reload": stripInternalParams(url).toString(),
|
|
1048
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
1049
|
+
},
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (treatAsPartial) {
|
|
1054
|
+
const intercepted = interceptRedirectForPartial(
|
|
1055
|
+
error,
|
|
1056
|
+
createRedirectFlightResponse,
|
|
1057
|
+
);
|
|
1058
|
+
if (intercepted) return intercepted;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
return error;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Render 404 page for unmatched routes
|
|
1065
|
+
if (isRouteNotFoundError(error)) {
|
|
1066
|
+
callOnError(error, "routing", {
|
|
1067
|
+
request,
|
|
1068
|
+
url,
|
|
1069
|
+
env,
|
|
1070
|
+
handledByBoundary: true,
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
const notFoundOption = router.notFound;
|
|
1074
|
+
const notFoundComponent =
|
|
1075
|
+
typeof notFoundOption === "function"
|
|
1076
|
+
? notFoundOption({ pathname: url.pathname })
|
|
1077
|
+
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
1078
|
+
|
|
1079
|
+
const notFoundSegment = {
|
|
1080
|
+
id: "notFound",
|
|
1081
|
+
namespace: "notFound",
|
|
1082
|
+
type: "route" as const,
|
|
1083
|
+
index: 0,
|
|
1084
|
+
component: notFoundComponent,
|
|
1085
|
+
params: {},
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
const payload: RscPayload = {
|
|
1089
|
+
metadata: {
|
|
1090
|
+
pathname: url.pathname,
|
|
1091
|
+
routerId: router.id,
|
|
1092
|
+
basename: router.basename,
|
|
1093
|
+
segments: [notFoundSegment],
|
|
1094
|
+
matched: [],
|
|
1095
|
+
diff: [],
|
|
1096
|
+
isPartial: false,
|
|
1097
|
+
rootLayout: router.rootLayout,
|
|
1098
|
+
handles: handleStore.stream(),
|
|
1099
|
+
version,
|
|
1100
|
+
stateCookieName: router.resolvedStateCookieName,
|
|
1101
|
+
themeConfig: router.themeConfig,
|
|
1102
|
+
warmupEnabled: router.warmupEnabled,
|
|
1103
|
+
initialTheme: requireRequestContext().theme,
|
|
1104
|
+
},
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
const rscStream = renderToReadableStream(payload, {
|
|
1108
|
+
onError: (error: unknown) => {
|
|
1109
|
+
callOnError(error, "rendering", { request, url, env });
|
|
1110
|
+
},
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
if (isRscRequest(request, url, isPartial)) {
|
|
1114
|
+
return createResponseWithMergedHeaders(rscStream, {
|
|
1115
|
+
status: 404,
|
|
1116
|
+
headers: {
|
|
1117
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
1118
|
+
// Router identity for the client's pre-decode integrity check; a
|
|
1119
|
+
// same-app 404 matches and applies in place. See response-adapter.
|
|
1120
|
+
"X-RSC-Router-Id": router.id,
|
|
1121
|
+
},
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
const [ssrModule, streamMode] = await getSSRSetup(
|
|
1126
|
+
handlerCtx,
|
|
1127
|
+
request,
|
|
1128
|
+
env,
|
|
1129
|
+
url,
|
|
1130
|
+
requireRequestContext()._metricsStore,
|
|
1131
|
+
);
|
|
1132
|
+
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
1133
|
+
nonce,
|
|
1134
|
+
streamMode,
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
return createResponseWithMergedHeaders(htmlStream, {
|
|
1138
|
+
status: 404,
|
|
1139
|
+
headers: { "content-type": "text/html;charset=utf-8" },
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Report unhandled errors
|
|
1144
|
+
callOnError(error, "routing", {
|
|
1145
|
+
request,
|
|
1146
|
+
url,
|
|
1147
|
+
env,
|
|
1148
|
+
handledByBoundary: false,
|
|
1149
|
+
});
|
|
1150
|
+
console.error(`[RSC] Error:`, error);
|
|
1151
|
+
throw error;
|
|
799
1152
|
}
|
|
800
|
-
return response;
|
|
801
1153
|
};
|
|
802
1154
|
|
|
803
|
-
// Wrap the render path
|
|
804
|
-
// renderStartMs timeout so slow renders are caught before output.
|
|
1155
|
+
// Wrap the render path in a renderStartMs timeout
|
|
805
1156
|
const executeRender = async (): Promise<Response> => {
|
|
806
|
-
if (
|
|
1157
|
+
if (routeMiddleware.length > 0) {
|
|
807
1158
|
const mwResponse = await executeMiddleware(
|
|
808
|
-
buildRouteMiddlewareEntries<TEnv>(
|
|
1159
|
+
buildRouteMiddlewareEntries<TEnv>(routeMiddleware),
|
|
809
1160
|
request,
|
|
810
1161
|
env,
|
|
811
1162
|
variables,
|
|
@@ -813,10 +1164,7 @@ export function createRSCHandler<
|
|
|
813
1164
|
routeReverse,
|
|
814
1165
|
);
|
|
815
1166
|
|
|
816
|
-
if (
|
|
817
|
-
url.searchParams.has("_rsc_partial") ||
|
|
818
|
-
url.searchParams.has("_rsc_action")
|
|
819
|
-
) {
|
|
1167
|
+
if (isPartial || actionContinuation) {
|
|
820
1168
|
const intercepted = interceptRedirectForPartial(
|
|
821
1169
|
mwResponse,
|
|
822
1170
|
createRedirectFlightResponse,
|
|
@@ -827,7 +1175,6 @@ export function createRSCHandler<
|
|
|
827
1175
|
return finalizeResponse(mwResponse);
|
|
828
1176
|
}
|
|
829
1177
|
|
|
830
|
-
// No route middleware, proceed directly
|
|
831
1178
|
return renderHandler();
|
|
832
1179
|
};
|
|
833
1180
|
|
|
@@ -843,273 +1190,9 @@ export function createRSCHandler<
|
|
|
843
1190
|
url,
|
|
844
1191
|
"render-start",
|
|
845
1192
|
renderOutcome.durationMs,
|
|
846
|
-
|
|
1193
|
+
routeKey,
|
|
847
1194
|
);
|
|
848
1195
|
}
|
|
849
1196
|
return renderOutcome.result;
|
|
850
1197
|
}
|
|
851
|
-
|
|
852
|
-
// Inner request handler: rendering logic wrapped by route middleware.
|
|
853
|
-
// Handles action revalidation (when actionContinuation is present),
|
|
854
|
-
// loader fetches, and regular RSC rendering.
|
|
855
|
-
async function coreRequestHandlerInner(
|
|
856
|
-
request: Request,
|
|
857
|
-
env: TEnv,
|
|
858
|
-
url: URL,
|
|
859
|
-
variables: Record<string, any>,
|
|
860
|
-
nonce: string | undefined,
|
|
861
|
-
routeParams?: Record<string, string>,
|
|
862
|
-
routeKey?: string,
|
|
863
|
-
handleStore?: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
864
|
-
actionContinuation?: ActionContinuation,
|
|
865
|
-
): Promise<Response> {
|
|
866
|
-
const isPartial = url.searchParams.has("_rsc_partial");
|
|
867
|
-
const isAction =
|
|
868
|
-
request.headers.has("rsc-action") || url.searchParams.has("_rsc_action");
|
|
869
|
-
|
|
870
|
-
// Version mismatch detection - client may have stale code after HMR/deployment
|
|
871
|
-
// If versions don't match, tell the client to reload
|
|
872
|
-
const clientVersion = url.searchParams.get("_rsc_v");
|
|
873
|
-
if (version && clientVersion && clientVersion !== version) {
|
|
874
|
-
console.log(
|
|
875
|
-
`[RSC] Version mismatch: client=${clientVersion}, server=${version}. Forcing reload.`,
|
|
876
|
-
);
|
|
877
|
-
|
|
878
|
-
// For actions, reload current page (referer) if same origin.
|
|
879
|
-
// For navigation, load the target URL.
|
|
880
|
-
// Validate referer origin to prevent open redirect via crafted header.
|
|
881
|
-
let reloadUrl = stripInternalParams(url).toString();
|
|
882
|
-
if (isAction) {
|
|
883
|
-
const referer = request.headers.get("referer");
|
|
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
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
// Return special response that tells client to reload
|
|
897
|
-
return createResponseWithMergedHeaders(null, {
|
|
898
|
-
status: 200,
|
|
899
|
-
headers: {
|
|
900
|
-
"X-RSC-Reload": reloadUrl,
|
|
901
|
-
"content-type": "text/x-component;charset=utf-8",
|
|
902
|
-
},
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
// Debug manifest endpoint: ?__debug_manifest on any route.
|
|
906
|
-
// Always available in dev, requires allowDebugManifest option in production.
|
|
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
|
-
}
|
|
933
|
-
|
|
934
|
-
const store = handleStore ?? requireRequestContext()._handleStore;
|
|
935
|
-
|
|
936
|
-
try {
|
|
937
|
-
// Route params were already set in coreRequestHandler, but set again
|
|
938
|
-
// for callers that enter coreRequestHandlerInner directly.
|
|
939
|
-
if (routeParams) {
|
|
940
|
-
setRequestContextParams(routeParams, routeKey);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// ============================================================================
|
|
944
|
-
// ACTION REVALIDATION (action already executed, revalidate segments)
|
|
945
|
-
// ============================================================================
|
|
946
|
-
if (actionContinuation) {
|
|
947
|
-
return await revalidateAfterAction(
|
|
948
|
-
handlerCtx,
|
|
949
|
-
request,
|
|
950
|
-
env,
|
|
951
|
-
url,
|
|
952
|
-
store,
|
|
953
|
-
actionContinuation,
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// ============================================================================
|
|
958
|
-
// LOADER FETCH EXECUTION (data fetching with RSC serialization)
|
|
959
|
-
// ============================================================================
|
|
960
|
-
const isLoaderRequest = url.searchParams.has("_rsc_loader");
|
|
961
|
-
if (isLoaderRequest) {
|
|
962
|
-
return handleLoaderFetch(
|
|
963
|
-
handlerCtx,
|
|
964
|
-
request,
|
|
965
|
-
env,
|
|
966
|
-
url,
|
|
967
|
-
variables,
|
|
968
|
-
routeParams,
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// ============================================================================
|
|
973
|
-
// REGULAR RSC RENDERING (Navigation)
|
|
974
|
-
// ============================================================================
|
|
975
|
-
// Note: Must use "return await" for try/catch to catch async rejections
|
|
976
|
-
return await handleRscRendering(
|
|
977
|
-
handlerCtx,
|
|
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.`,
|
|
997
|
-
);
|
|
998
|
-
return createResponseWithMergedHeaders(null, {
|
|
999
|
-
status: 200,
|
|
1000
|
-
headers: {
|
|
1001
|
-
"X-RSC-Reload": stripInternalParams(url).toString(),
|
|
1002
|
-
"content-type": "text/x-component;charset=utf-8",
|
|
1003
|
-
},
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
if (isPartial) {
|
|
1008
|
-
const intercepted = interceptRedirectForPartial(
|
|
1009
|
-
error,
|
|
1010
|
-
createRedirectFlightResponse,
|
|
1011
|
-
);
|
|
1012
|
-
if (intercepted) return intercepted;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
return error;
|
|
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) {
|
|
1024
|
-
callOnError(error, "routing", {
|
|
1025
|
-
request,
|
|
1026
|
-
url,
|
|
1027
|
-
env,
|
|
1028
|
-
handledByBoundary: true, // Handled by notFound component
|
|
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 }),
|
|
1068
|
-
});
|
|
1069
|
-
|
|
1070
|
-
// Determine if this is an RSC request or HTML request.
|
|
1071
|
-
// Partial requests are always RSC (see main isRscRequest comment).
|
|
1072
|
-
const isRscRequest =
|
|
1073
|
-
isPartial ||
|
|
1074
|
-
(!request.headers.get("accept")?.includes("text/html") &&
|
|
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,
|
|
1088
|
-
request,
|
|
1089
|
-
env,
|
|
1090
|
-
url,
|
|
1091
|
-
requireRequestContext()._metricsStore,
|
|
1092
|
-
);
|
|
1093
|
-
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
1094
|
-
nonce,
|
|
1095
|
-
streamMode,
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
return createResponseWithMergedHeaders(htmlStream, {
|
|
1099
|
-
status: 404,
|
|
1100
|
-
headers: { "content-type": "text/html;charset=utf-8" },
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
// Report unhandled errors
|
|
1105
|
-
callOnError(error, "routing", {
|
|
1106
|
-
request,
|
|
1107
|
-
url,
|
|
1108
|
-
env,
|
|
1109
|
-
handledByBoundary: false,
|
|
1110
|
-
});
|
|
1111
|
-
console.error(`[RSC] Error:`, error);
|
|
1112
|
-
throw error;
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
1198
|
}
|