@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c
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/AGENTS.md +8 -0
- package/README.md +126 -16
- package/dist/bin/rango.js +319 -95
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2724 -1053
- package/package.json +68 -14
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +11 -9
- package/skills/hooks/SKILL.md +243 -29
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +68 -19
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +190 -23
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +18 -10
- package/skills/migrate-nextjs/SKILL.md +43 -19
- package/skills/migrate-react-router/SKILL.md +8 -2
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +18 -7
- package/skills/prerender/SKILL.md +65 -60
- package/skills/rango/SKILL.md +251 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +115 -48
- package/skills/route/SKILL.md +46 -5
- package/skills/router-setup/SKILL.md +30 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -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 +122 -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 +123 -0
- package/skills/typesafety/SKILL.md +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- 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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- 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 +94 -25
- package/src/browser/navigation-client.ts +121 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +115 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +147 -128
- package/src/browser/prefetch/cache.ts +107 -56
- package/src/browser/prefetch/fetch.ts +204 -34
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +30 -7
- package/src/browser/react/NavigationProvider.tsx +283 -118
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -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 +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- 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 +99 -8
- package/src/browser/rsc-router.tsx +91 -24
- package/src/browser/scroll-restoration.ts +30 -17
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +214 -55
- package/src/browser/types.ts +80 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +117 -14
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +117 -23
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +55 -28
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +224 -41
- package/src/cache/cache-scope.ts +188 -82
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +36 -61
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- 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 +96 -12
- package/src/index.ts +94 -14
- package/src/internal-debug.ts +11 -10
- 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/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +34 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +82 -55
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +27 -43
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +10 -47
- package/src/router/loader-resolution.ts +155 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +9 -24
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +44 -74
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +247 -166
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +85 -94
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +104 -65
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +83 -35
- package/src/router/router-options.ts +136 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +97 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +272 -320
- 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 +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +162 -64
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +110 -55
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +264 -220
- package/src/rsc/helpers.ts +100 -6
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +117 -11
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +88 -188
- package/src/rsc/rsc-rendering.ts +98 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +281 -117
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +23 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +31 -4
- package/src/segment-system.tsx +254 -143
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +163 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +287 -178
- package/src/ssr/index.tsx +21 -16
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -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 +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -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 +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +29 -12
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +126 -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 +96 -68
- 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 +44 -0
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +2 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +126 -8
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- 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-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +109 -118
- package/src/vite/router-discovery.ts +718 -119
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- 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/directive-prologue.ts +40 -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 +54 -39
- package/src/vite/utils/shared-utils.ts +90 -41
- package/src/browser/action-response-classifier.ts +0 -99
- 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/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
package/src/rsc/handler.ts
CHANGED
|
@@ -8,12 +8,11 @@
|
|
|
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
|
-
requireRequestContext,
|
|
17
16
|
getRequestContext,
|
|
18
17
|
_getRequestContext,
|
|
19
18
|
createRequestContext,
|
|
@@ -31,7 +30,11 @@ import {
|
|
|
31
30
|
interceptRedirectForPartial,
|
|
32
31
|
buildRouteMiddlewareEntries,
|
|
33
32
|
} from "./helpers.js";
|
|
34
|
-
import {
|
|
33
|
+
import { guardOutgoingRedirect } from "./redirect-guard.js";
|
|
34
|
+
import {
|
|
35
|
+
isWebSocketUpgradeResponse,
|
|
36
|
+
appendVaryAccept,
|
|
37
|
+
} from "../response-utils.js";
|
|
35
38
|
import {
|
|
36
39
|
handleResponseRoute,
|
|
37
40
|
type ResponseRouteMatch,
|
|
@@ -45,8 +48,6 @@ import {
|
|
|
45
48
|
createReverseFunction,
|
|
46
49
|
stripInternalParams,
|
|
47
50
|
} from "../router/handler-context.js";
|
|
48
|
-
import { getRouterContext } from "../router/router-context.js";
|
|
49
|
-
import { resolveSink, safeEmit } from "../router/telemetry.js";
|
|
50
51
|
import { contextSet } from "../context-var.js";
|
|
51
52
|
import {
|
|
52
53
|
hasCachedManifest,
|
|
@@ -57,6 +58,8 @@ import {
|
|
|
57
58
|
getRouterTrie,
|
|
58
59
|
} from "../route-map-builder.js";
|
|
59
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";
|
|
60
63
|
import { buildRouterTrieFromUrlpatterns } from "./manifest-init.js";
|
|
61
64
|
import { handleProgressiveEnhancement } from "./progressive-enhancement.js";
|
|
62
65
|
import {
|
|
@@ -65,7 +68,10 @@ import {
|
|
|
65
68
|
type ActionContinuation,
|
|
66
69
|
} from "./server-action.js";
|
|
67
70
|
import { handleLoaderFetch } from "./loader-fetch.js";
|
|
68
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
checkRequestOrigin,
|
|
73
|
+
ORIGIN_CHECK_PHASE_BY_MODE,
|
|
74
|
+
} from "./origin-guard.js";
|
|
69
75
|
import { handleRscRendering } from "./rsc-rendering.js";
|
|
70
76
|
import {
|
|
71
77
|
withTimeout,
|
|
@@ -78,10 +84,12 @@ import {
|
|
|
78
84
|
appendMetric,
|
|
79
85
|
buildMetricsTiming,
|
|
80
86
|
} from "../router/metrics.js";
|
|
87
|
+
import { observePhase, observeEvent, PHASES } from "../router/instrument.js";
|
|
81
88
|
import {
|
|
82
89
|
startSSRSetup,
|
|
83
90
|
getSSRSetup,
|
|
84
91
|
mayNeedSSR,
|
|
92
|
+
isRscRequest,
|
|
85
93
|
SSR_SETUP_VAR,
|
|
86
94
|
} from "./ssr-setup.js";
|
|
87
95
|
import {
|
|
@@ -123,12 +131,35 @@ import {
|
|
|
123
131
|
* });
|
|
124
132
|
* ```
|
|
125
133
|
*/
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Response that tells the client to do a full document navigation. Shared by
|
|
137
|
+
* the terminal reload plans (version-mismatch and app-switch): an empty 200
|
|
138
|
+
* carrying X-RSC-Reload, which the client turns into window.location.href.
|
|
139
|
+
*/
|
|
140
|
+
function createReloadResponse(reloadUrl: string) {
|
|
141
|
+
return createResponseWithMergedHeaders(null, {
|
|
142
|
+
status: 200,
|
|
143
|
+
headers: {
|
|
144
|
+
"X-RSC-Reload": reloadUrl,
|
|
145
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
126
150
|
export function createRSCHandler<
|
|
127
151
|
TEnv = unknown,
|
|
128
152
|
TRoutes extends Record<string, string> = Record<string, string>,
|
|
129
153
|
>(options: CreateRSCHandlerOptions<TEnv, TRoutes>) {
|
|
130
154
|
const { router, version = VERSION, nonce: nonceProvider } = options;
|
|
131
155
|
|
|
156
|
+
// Handler-owned registry of explicit per-scope stores from cache({ store }).
|
|
157
|
+
// Lives in the closure so it is scoped per handler (multi-router deployments
|
|
158
|
+
// get separate registries) and accumulates every explicit store this handler
|
|
159
|
+
// resolves across requests. updateTag()/revalidateTag() iterate it to reach
|
|
160
|
+
// stores not covered by the app-level ctx._cacheStore.
|
|
161
|
+
const explicitTaggedStores = new Set<SegmentCacheStore>();
|
|
162
|
+
|
|
132
163
|
// Use provided deps or default to @vitejs/plugin-rsc/rsc exports
|
|
133
164
|
const deps = options.deps ?? rscDeps;
|
|
134
165
|
const {
|
|
@@ -214,24 +245,16 @@ export function createRSCHandler<
|
|
|
214
245
|
metadata: { timeout: true, phase, durationMs },
|
|
215
246
|
});
|
|
216
247
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
actionId,
|
|
228
|
-
durationMs,
|
|
229
|
-
customHandler: !!router.onTimeout,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
} catch {
|
|
233
|
-
// Router context may not be available
|
|
234
|
-
}
|
|
248
|
+
observeEvent({
|
|
249
|
+
type: "request.timeout",
|
|
250
|
+
timestamp: performance.now(),
|
|
251
|
+
phase,
|
|
252
|
+
pathname: url.pathname,
|
|
253
|
+
routeKey,
|
|
254
|
+
actionId,
|
|
255
|
+
durationMs,
|
|
256
|
+
customHandler: !!router.onTimeout,
|
|
257
|
+
});
|
|
235
258
|
|
|
236
259
|
if (router.onTimeout) {
|
|
237
260
|
try {
|
|
@@ -263,16 +286,27 @@ export function createRSCHandler<
|
|
|
263
286
|
function createRedirectFlightResponse(
|
|
264
287
|
redirectUrl: string,
|
|
265
288
|
locationState?: Record<string, unknown>,
|
|
289
|
+
external?: boolean,
|
|
266
290
|
): Response {
|
|
267
291
|
const redirectPayload: RscPayload = {
|
|
268
292
|
metadata: {
|
|
269
293
|
pathname: redirectUrl,
|
|
270
294
|
segments: [],
|
|
271
|
-
redirect: { url: redirectUrl },
|
|
295
|
+
redirect: { url: redirectUrl, ...(external && { external: true }) },
|
|
272
296
|
...(locationState && { locationState }),
|
|
273
297
|
},
|
|
274
298
|
};
|
|
275
|
-
const rscStream = renderToReadableStream<RscPayload>(redirectPayload
|
|
299
|
+
const rscStream = renderToReadableStream<RscPayload>(redirectPayload, {
|
|
300
|
+
onError: (error: unknown) => {
|
|
301
|
+
const reqCtx = _getRequestContext<TEnv>();
|
|
302
|
+
if (!reqCtx) return;
|
|
303
|
+
callOnError(error, "rendering", {
|
|
304
|
+
request: reqCtx.request,
|
|
305
|
+
url: reqCtx.url,
|
|
306
|
+
env: reqCtx.env,
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
});
|
|
276
310
|
return createResponseWithMergedHeaders(rscStream, {
|
|
277
311
|
status: 200,
|
|
278
312
|
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
@@ -353,7 +387,7 @@ export function createRSCHandler<
|
|
|
353
387
|
// Resolve cache store configuration
|
|
354
388
|
// Priority: options.cache (handler override) > router.cache (router default)
|
|
355
389
|
// Store is enabled only if: config provided, enabled, and no ?__no_cache query param
|
|
356
|
-
let cacheStore
|
|
390
|
+
let cacheStore: SegmentCacheStore | undefined;
|
|
357
391
|
const cacheOption = options.cache ?? router.cache;
|
|
358
392
|
if (cacheOption && !url.searchParams.has("__no_cache")) {
|
|
359
393
|
const cacheConfig =
|
|
@@ -420,9 +454,12 @@ export function createRSCHandler<
|
|
|
420
454
|
url,
|
|
421
455
|
variables,
|
|
422
456
|
cacheStore,
|
|
457
|
+
explicitTaggedStores,
|
|
423
458
|
cacheProfiles: router.cacheProfiles,
|
|
424
459
|
executionContext: executionCtx,
|
|
425
460
|
themeConfig: router.themeConfig,
|
|
461
|
+
stateCookieName: router.resolvedStateCookieName,
|
|
462
|
+
version,
|
|
426
463
|
});
|
|
427
464
|
if (earlyMetricsStore) {
|
|
428
465
|
requestContext._debugPerformance = true;
|
|
@@ -432,7 +469,7 @@ export function createRSCHandler<
|
|
|
432
469
|
// can surface non-fatal errors through the router's onError callback.
|
|
433
470
|
requestContext._reportBackgroundError = (
|
|
434
471
|
error: unknown,
|
|
435
|
-
category:
|
|
472
|
+
category: CacheErrorCategory,
|
|
436
473
|
) => {
|
|
437
474
|
callOnError(error, "cache", {
|
|
438
475
|
request,
|
|
@@ -465,81 +502,104 @@ export function createRSCHandler<
|
|
|
465
502
|
// Store basename on request context (scoped per-request via existing ALS)
|
|
466
503
|
requestContext._basename = router.basename;
|
|
467
504
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
);
|
|
505
|
+
// Resolved span tracing for this request (read at each traced phase).
|
|
506
|
+
requestContext._tracing = router.tracing;
|
|
507
|
+
|
|
508
|
+
// The "rango.request" span is opened inside the request context so the
|
|
509
|
+
// Cloudflare runner can read executionContext.tracing, and so every nested
|
|
510
|
+
// phase span (and the platform's automatic KV/D1/fetch spans) nests under
|
|
511
|
+
// it. Construction-bound: the span ends when the Response is built, never
|
|
512
|
+
// wrapping the streamed body. metric:false — handler:total is metered
|
|
513
|
+
// directly below (a grand total incl. the pre-context bootstrap timings).
|
|
514
|
+
// When tracing is off this is a direct pass-through.
|
|
515
|
+
return runWithRequestContext(requestContext, () =>
|
|
516
|
+
observePhase(PHASES.request, async (span) => {
|
|
517
|
+
span.setAttribute("http.method", request.method);
|
|
518
|
+
// The matched route template is not known until match() runs later, so
|
|
519
|
+
// emit the concrete path as url.path (low-level), NOT http.route — the
|
|
520
|
+
// latter is reserved for the low-cardinality template (OTel convention).
|
|
521
|
+
span.setAttribute("url.path", url.pathname);
|
|
522
|
+
|
|
523
|
+
// Core handler logic (wrapped by middleware)
|
|
524
|
+
const coreHandler = async (): Promise<Response> => {
|
|
525
|
+
return coreRequestHandler(request, env, url, variables, nonce);
|
|
526
|
+
};
|
|
485
527
|
|
|
486
|
-
if
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
528
|
+
// Execute middleware chain if any, otherwise call core handler directly
|
|
529
|
+
let response: Response;
|
|
530
|
+
if (matchedMiddleware.length > 0) {
|
|
531
|
+
const mwResponse = await executeMiddleware(
|
|
532
|
+
matchedMiddleware,
|
|
533
|
+
request,
|
|
534
|
+
env,
|
|
535
|
+
variables,
|
|
536
|
+
coreHandler,
|
|
537
|
+
createReverseFunction(getRequiredRouteMap()),
|
|
493
538
|
);
|
|
494
|
-
|
|
539
|
+
|
|
540
|
+
if (
|
|
541
|
+
url.searchParams.has("_rsc_partial") ||
|
|
542
|
+
url.searchParams.has("_rsc_action")
|
|
543
|
+
) {
|
|
544
|
+
const intercepted = interceptRedirectForPartial(
|
|
545
|
+
mwResponse,
|
|
546
|
+
createRedirectFlightResponse,
|
|
547
|
+
);
|
|
548
|
+
response = intercepted ?? finalizeResponse(mwResponse);
|
|
549
|
+
} else {
|
|
550
|
+
response = finalizeResponse(mwResponse);
|
|
551
|
+
}
|
|
495
552
|
} else {
|
|
496
|
-
response =
|
|
553
|
+
response = await coreHandler();
|
|
497
554
|
}
|
|
498
|
-
} else {
|
|
499
|
-
response = await coreHandler();
|
|
500
|
-
}
|
|
501
555
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
556
|
+
// Finalize metrics after all middleware (including post-next work)
|
|
557
|
+
// has completed so :post spans are captured in the timeline.
|
|
558
|
+
// Handler timing parts are always emitted (even without debug metrics)
|
|
559
|
+
// so non-debug requests still get bootstrap Server-Timing entries.
|
|
560
|
+
const handlerTimingArr: string[] = variables.__handlerTiming || [];
|
|
561
|
+
// Preserve any existing Server-Timing set by response routes or middleware
|
|
562
|
+
const existingTiming = response.headers.get("Server-Timing");
|
|
563
|
+
const timingParts = existingTiming
|
|
564
|
+
? [existingTiming, ...handlerTimingArr]
|
|
565
|
+
: [...handlerTimingArr];
|
|
566
|
+
|
|
567
|
+
const metricsStore = requestContext._metricsStore;
|
|
568
|
+
if (metricsStore) {
|
|
569
|
+
// When the store was created at handler start (earlyMetricsStore),
|
|
570
|
+
// handler:total covers the full request. When ctx.debugPerformance()
|
|
571
|
+
// created the store mid-request, use its requestStart to avoid a
|
|
572
|
+
// negative startTime offset.
|
|
573
|
+
const totalStart = earlyMetricsStore
|
|
574
|
+
? handlerStart
|
|
575
|
+
: metricsStore.requestStart;
|
|
576
|
+
appendMetric(
|
|
577
|
+
metricsStore,
|
|
578
|
+
"handler:total",
|
|
579
|
+
totalStart,
|
|
580
|
+
performance.now() - totalStart,
|
|
581
|
+
);
|
|
582
|
+
const metricsTiming = buildMetricsTiming(
|
|
583
|
+
request.method,
|
|
584
|
+
url.pathname,
|
|
585
|
+
metricsStore,
|
|
586
|
+
);
|
|
587
|
+
if (metricsTiming) timingParts.push(metricsTiming);
|
|
588
|
+
}
|
|
535
589
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
590
|
+
const fullTiming = timingParts.join(", ");
|
|
591
|
+
if (fullTiming && !isWebSocketUpgradeResponse(response)) {
|
|
592
|
+
response.headers.set("Server-Timing", fullTiming);
|
|
593
|
+
}
|
|
540
594
|
|
|
541
|
-
|
|
542
|
-
|
|
595
|
+
// Single open-redirect chokepoint: every response (PE, full-page,
|
|
596
|
+
// middleware short-circuit, response-route) funnels through here, so
|
|
597
|
+
// guarding browser-followed (3xx) redirects once covers them all and any
|
|
598
|
+
// future redirect exit. Soft SPA/Flight redirects are 200/204 and pass
|
|
599
|
+
// through untouched (validated client-side instead).
|
|
600
|
+
return guardOutgoingRedirect(response, url.origin, router.basename);
|
|
601
|
+
}),
|
|
602
|
+
);
|
|
543
603
|
};
|
|
544
604
|
|
|
545
605
|
// Core request handling logic (separated for middleware wrapping).
|
|
@@ -596,10 +656,7 @@ export function createRSCHandler<
|
|
|
596
656
|
routerId: router.id,
|
|
597
657
|
});
|
|
598
658
|
} catch (error) {
|
|
599
|
-
if (
|
|
600
|
-
error instanceof RouteNotFoundError ||
|
|
601
|
-
(error instanceof Error && error.name === "RouteNotFoundError")
|
|
602
|
-
) {
|
|
659
|
+
if (isRouteNotFoundError(error)) {
|
|
603
660
|
// Let the render path handle 404 — match()/matchPartial() will
|
|
604
661
|
// re-throw RouteNotFoundError and the catch block in
|
|
605
662
|
// executeRenderWithMiddleware renders the not-found page.
|
|
@@ -640,24 +697,18 @@ export function createRSCHandler<
|
|
|
640
697
|
console.log(
|
|
641
698
|
`[RSC] Version mismatch: client=${url.searchParams.get("_rsc_v")}, server=${version}. Forcing reload.`,
|
|
642
699
|
);
|
|
643
|
-
return
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
700
|
+
return createReloadResponse(plan.reloadUrl);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (plan.mode === "app-switch") {
|
|
704
|
+
// Cross-app SPA navigation crossed a host-router app boundary. Force a
|
|
705
|
+
// real document navigation so the target app's document is re-established
|
|
706
|
+
// (stylesheets, theme, warmup, prefetch-TTL). See request-classification.
|
|
707
|
+
return createReloadResponse(plan.reloadUrl);
|
|
650
708
|
}
|
|
651
709
|
|
|
652
710
|
// ---- 3. Origin guard (gate for action/loader/PE modes) ----
|
|
653
|
-
const originPhase
|
|
654
|
-
plan.mode === "action"
|
|
655
|
-
? "action"
|
|
656
|
-
: plan.mode === "loader"
|
|
657
|
-
? "loader"
|
|
658
|
-
: plan.mode === "pe-render"
|
|
659
|
-
? "pe-form"
|
|
660
|
-
: null;
|
|
711
|
+
const originPhase = ORIGIN_CHECK_PHASE_BY_MODE[plan.mode];
|
|
661
712
|
if (originPhase) {
|
|
662
713
|
const originResult = await checkRequestOrigin(
|
|
663
714
|
request,
|
|
@@ -685,23 +736,15 @@ export function createRSCHandler<
|
|
|
685
736
|
},
|
|
686
737
|
});
|
|
687
738
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
phase: originPhase,
|
|
698
|
-
origin: request.headers.get("origin"),
|
|
699
|
-
host: request.headers.get("host"),
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
} catch {
|
|
703
|
-
// Router context may not be available
|
|
704
|
-
}
|
|
739
|
+
observeEvent({
|
|
740
|
+
type: "request.origin-rejected",
|
|
741
|
+
timestamp: performance.now(),
|
|
742
|
+
method: request.method,
|
|
743
|
+
pathname: url.pathname,
|
|
744
|
+
phase: originPhase,
|
|
745
|
+
origin: request.headers.get("origin"),
|
|
746
|
+
host: request.headers.get("host"),
|
|
747
|
+
});
|
|
705
748
|
|
|
706
749
|
return originResult;
|
|
707
750
|
}
|
|
@@ -731,11 +774,11 @@ export function createRSCHandler<
|
|
|
731
774
|
nonce: string | undefined,
|
|
732
775
|
): Promise<Response> {
|
|
733
776
|
// Common setup
|
|
734
|
-
const handleStore =
|
|
777
|
+
const handleStore = getRequestContext()._handleStore;
|
|
735
778
|
|
|
736
779
|
// Wire up error reporting for late streaming-handle failures
|
|
737
780
|
handleStore.onError = (error: Error) => {
|
|
738
|
-
const reqCtx =
|
|
781
|
+
const reqCtx = getRequestContext();
|
|
739
782
|
callOnError(error, "handler", {
|
|
740
783
|
request,
|
|
741
784
|
url,
|
|
@@ -743,23 +786,15 @@ export function createRSCHandler<
|
|
|
743
786
|
params: reqCtx.params as Record<string, string>,
|
|
744
787
|
handledByBoundary: true,
|
|
745
788
|
});
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
pathname: url.pathname,
|
|
756
|
-
routeKey: reqCtx._routeName,
|
|
757
|
-
params: reqCtx.params as Record<string, string>,
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
} catch {
|
|
761
|
-
// Router context may not be available (e.g. prerender path)
|
|
762
|
-
}
|
|
789
|
+
observeEvent({
|
|
790
|
+
type: "handler.error",
|
|
791
|
+
timestamp: performance.now(),
|
|
792
|
+
error,
|
|
793
|
+
handledByBoundary: true,
|
|
794
|
+
pathname: url.pathname,
|
|
795
|
+
routeKey: reqCtx._routeName,
|
|
796
|
+
params: reqCtx.params as Record<string, string>,
|
|
797
|
+
});
|
|
763
798
|
};
|
|
764
799
|
|
|
765
800
|
// Set route params early so all execution paths can access ctx.params.
|
|
@@ -767,7 +802,7 @@ export function createRSCHandler<
|
|
|
767
802
|
// instead of calling resolveRoute again.
|
|
768
803
|
if (plan.mode !== "redirect") {
|
|
769
804
|
setRequestContextParams(plan.route.params, plan.route.routeKey);
|
|
770
|
-
|
|
805
|
+
getRequestContext()._classifiedRoute = plan.route;
|
|
771
806
|
}
|
|
772
807
|
|
|
773
808
|
const routeReverse = createReverseFunction(getRequiredRouteMap());
|
|
@@ -808,7 +843,9 @@ export function createRSCHandler<
|
|
|
808
843
|
}
|
|
809
844
|
const response = responseOutcome.result;
|
|
810
845
|
if (plan.negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
811
|
-
|
|
846
|
+
// handleResponseRoute (callHandlerWithVary) already appends Vary: Accept
|
|
847
|
+
// for negotiated responses; dedup so we don't emit Vary: Accept, Accept.
|
|
848
|
+
appendVaryAccept(response);
|
|
812
849
|
}
|
|
813
850
|
return response;
|
|
814
851
|
}
|
|
@@ -816,14 +853,28 @@ export function createRSCHandler<
|
|
|
816
853
|
// SSR setup: kick off in parallel for modes that need HTML rendering.
|
|
817
854
|
// Placed after response-route short-circuit so response/mime routes
|
|
818
855
|
// never pay for SSR work.
|
|
819
|
-
|
|
856
|
+
//
|
|
857
|
+
// Only kick off when the request will actually render HTML, so the
|
|
858
|
+
// eager loadSSRModule() + user resolveStreaming() are not started (and
|
|
859
|
+
// never consumed) for a request that returns an RSC stream — that wasted
|
|
860
|
+
// work also leaves an orphaned Promise.all that can reject (D7). PE form
|
|
861
|
+
// submissions always render HTML (handleProgressiveEnhancement renders via
|
|
862
|
+
// getSSRSetup regardless of Accept). For full/partial-render and action,
|
|
863
|
+
// the render-time HTML decision is exactly !isRscRequest — mayNeedSSR is
|
|
864
|
+
// the coarse transport pre-filter, isRscRequest is the precise Accept call
|
|
865
|
+
// (it, unlike mayNeedSSR, treats a MISSING Accept as RSC). Both must pass.
|
|
866
|
+
const willRenderHtml =
|
|
867
|
+
plan.mode === "pe-render" ||
|
|
868
|
+
(mayNeedSSR(request, url) &&
|
|
869
|
+
!isRscRequest(request, url, plan.mode === "partial-render"));
|
|
870
|
+
if (plan.mode !== "loader" && willRenderHtml) {
|
|
820
871
|
variables[SSR_SETUP_VAR] = startSSRSetup(
|
|
821
872
|
handlerCtx,
|
|
822
873
|
request,
|
|
823
874
|
env,
|
|
824
875
|
url,
|
|
825
876
|
router.debugPerformance
|
|
826
|
-
? () =>
|
|
877
|
+
? () => getRequestContext()._metricsStore
|
|
827
878
|
: undefined,
|
|
828
879
|
);
|
|
829
880
|
}
|
|
@@ -864,14 +915,20 @@ export function createRSCHandler<
|
|
|
864
915
|
if (plan.mode === "action") {
|
|
865
916
|
let actionContinuation: ActionContinuation | undefined;
|
|
866
917
|
try {
|
|
918
|
+
// Instrument the action execution as its own phase (action:<actionId> +
|
|
919
|
+
// rango.action), so a POST shows the mutation time AND which action ran,
|
|
920
|
+
// not just the downstream revalidation render. The action's own
|
|
921
|
+
// loaders/fetches nest under rango.action.
|
|
867
922
|
const actionOutcome = await withTimeout(
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
923
|
+
observePhase(PHASES.action(plan.actionId), () =>
|
|
924
|
+
executeServerAction(
|
|
925
|
+
handlerCtx,
|
|
926
|
+
request,
|
|
927
|
+
env,
|
|
928
|
+
url,
|
|
929
|
+
plan.actionId,
|
|
930
|
+
handleStore,
|
|
931
|
+
),
|
|
875
932
|
),
|
|
876
933
|
router.timeouts.actionMs,
|
|
877
934
|
"action",
|
|
@@ -924,47 +981,17 @@ export function createRSCHandler<
|
|
|
924
981
|
);
|
|
925
982
|
}
|
|
926
983
|
|
|
927
|
-
//
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
plan.
|
|
934
|
-
|
|
935
|
-
request,
|
|
936
|
-
env,
|
|
937
|
-
url,
|
|
938
|
-
variables,
|
|
939
|
-
nonce,
|
|
940
|
-
handleStore,
|
|
941
|
-
isPartial,
|
|
942
|
-
);
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// PE that fell through (handleProgressiveEnhancement returned null)
|
|
946
|
-
// falls back to full render
|
|
947
|
-
if (plan.mode === "pe-render") {
|
|
948
|
-
return executeRenderWithMiddleware(
|
|
949
|
-
plan.route.routeMiddleware,
|
|
950
|
-
false,
|
|
951
|
-
plan.route.routeKey,
|
|
952
|
-
routeReverse,
|
|
953
|
-
request,
|
|
954
|
-
env,
|
|
955
|
-
url,
|
|
956
|
-
variables,
|
|
957
|
-
nonce,
|
|
958
|
-
handleStore,
|
|
959
|
-
false,
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// Redirect plan that wasn't handled above (full-page redirect — let
|
|
964
|
-
// the pipeline handle it via match() which returns { redirect: url })
|
|
984
|
+
// Full render, partial render, fallen-through PE, and full-page redirect all
|
|
985
|
+
// render through the same middleware-wrapped path. Only full/partial-render
|
|
986
|
+
// carry negotiation + the partial flag; pe/redirect render plainly.
|
|
987
|
+
const isPartial = plan.mode === "partial-render";
|
|
988
|
+
const negotiated =
|
|
989
|
+
plan.mode === "full-render" || plan.mode === "partial-render"
|
|
990
|
+
? plan.negotiated
|
|
991
|
+
: false;
|
|
965
992
|
return executeRenderWithMiddleware(
|
|
966
993
|
plan.route.routeMiddleware,
|
|
967
|
-
|
|
994
|
+
negotiated,
|
|
968
995
|
plan.route.routeKey,
|
|
969
996
|
routeReverse,
|
|
970
997
|
request,
|
|
@@ -973,7 +1000,7 @@ export function createRSCHandler<
|
|
|
973
1000
|
variables,
|
|
974
1001
|
nonce,
|
|
975
1002
|
handleStore,
|
|
976
|
-
|
|
1003
|
+
isPartial,
|
|
977
1004
|
);
|
|
978
1005
|
}
|
|
979
1006
|
|
|
@@ -990,7 +1017,7 @@ export function createRSCHandler<
|
|
|
990
1017
|
url: URL,
|
|
991
1018
|
variables: Record<string, any>,
|
|
992
1019
|
nonce: string | undefined,
|
|
993
|
-
handleStore: ReturnType<typeof
|
|
1020
|
+
handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
|
|
994
1021
|
isPartial: boolean,
|
|
995
1022
|
actionContinuation?: ActionContinuation,
|
|
996
1023
|
): Promise<Response> {
|
|
@@ -1018,16 +1045,27 @@ export function createRSCHandler<
|
|
|
1018
1045
|
);
|
|
1019
1046
|
}
|
|
1020
1047
|
if (negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
1021
|
-
response
|
|
1048
|
+
// handleRscRendering bakes `accept` into the RSC response's Vary list;
|
|
1049
|
+
// dedup so the negotiated append does not list accept twice.
|
|
1050
|
+
appendVaryAccept(response);
|
|
1022
1051
|
}
|
|
1023
1052
|
return response;
|
|
1024
1053
|
} catch (error) {
|
|
1025
1054
|
// Check if middleware/handler returned Response
|
|
1026
1055
|
if (error instanceof Response) {
|
|
1056
|
+
// An action revalidation render is delivered to the client over the
|
|
1057
|
+
// same Flight-parsing path as a partial navigation, so a Response
|
|
1058
|
+
// thrown during it must be converted exactly like a partial one
|
|
1059
|
+
// (raw 200 -> hard-nav hint, 3xx -> Flight redirect). Without this,
|
|
1060
|
+
// the no-middleware path returns the raw Response (the with-middleware
|
|
1061
|
+
// path is already covered by the isPartial || actionContinuation
|
|
1062
|
+
// guard below).
|
|
1063
|
+
const treatAsPartial = isPartial || actionContinuation != null;
|
|
1064
|
+
|
|
1027
1065
|
// During partial (client-side navigation), a 200 Response from a handler
|
|
1028
1066
|
// means the route serves raw content (JSON, text, etc.), not JSX.
|
|
1029
1067
|
// Signal the browser to hard-navigate so it renders the raw response.
|
|
1030
|
-
if (
|
|
1068
|
+
if (treatAsPartial && error.status === 200) {
|
|
1031
1069
|
console.warn(
|
|
1032
1070
|
`[RSC] Route handler at ${url.pathname} returned a Response during client-side navigation. ` +
|
|
1033
1071
|
`Falling back to hard navigation. Use data-external on the <Link> to avoid the extra round-trip.`,
|
|
@@ -1041,7 +1079,7 @@ export function createRSCHandler<
|
|
|
1041
1079
|
});
|
|
1042
1080
|
}
|
|
1043
1081
|
|
|
1044
|
-
if (
|
|
1082
|
+
if (treatAsPartial) {
|
|
1045
1083
|
const intercepted = interceptRedirectForPartial(
|
|
1046
1084
|
error,
|
|
1047
1085
|
createRedirectFlightResponse,
|
|
@@ -1053,10 +1091,7 @@ export function createRSCHandler<
|
|
|
1053
1091
|
}
|
|
1054
1092
|
|
|
1055
1093
|
// Render 404 page for unmatched routes
|
|
1056
|
-
|
|
1057
|
-
error instanceof RouteNotFoundError ||
|
|
1058
|
-
(error instanceof Error && error.name === "RouteNotFoundError");
|
|
1059
|
-
if (isRouteNotFound) {
|
|
1094
|
+
if (isRouteNotFoundError(error)) {
|
|
1060
1095
|
callOnError(error, "routing", {
|
|
1061
1096
|
request,
|
|
1062
1097
|
url,
|
|
@@ -1087,13 +1122,23 @@ export function createRSCHandler<
|
|
|
1087
1122
|
segments: [notFoundSegment],
|
|
1088
1123
|
matched: [],
|
|
1089
1124
|
diff: [],
|
|
1125
|
+
// Shape parity with buildFullPayload (rsc-rendering.ts): the 404
|
|
1126
|
+
// payload carries params/resolvedIds/prefetchCacheTTL the same way a
|
|
1127
|
+
// matched full render does. resolvedIds mirrors the rendered segment
|
|
1128
|
+
// list (the single notFound segment) like the error-boundary path
|
|
1129
|
+
// (match-api.ts) does for its boundary segment.
|
|
1130
|
+
resolvedIds: [notFoundSegment.id],
|
|
1131
|
+
params: {},
|
|
1090
1132
|
isPartial: false,
|
|
1091
1133
|
rootLayout: router.rootLayout,
|
|
1092
1134
|
handles: handleStore.stream(),
|
|
1093
1135
|
version,
|
|
1136
|
+
prefetchCacheTTL: router.prefetchCacheTTL,
|
|
1137
|
+
stateCookieName: router.resolvedStateCookieName,
|
|
1094
1138
|
themeConfig: router.themeConfig,
|
|
1095
1139
|
warmupEnabled: router.warmupEnabled,
|
|
1096
|
-
|
|
1140
|
+
strictMode: router.strictMode,
|
|
1141
|
+
initialTheme: getRequestContext().theme,
|
|
1097
1142
|
},
|
|
1098
1143
|
};
|
|
1099
1144
|
|
|
@@ -1103,16 +1148,15 @@ export function createRSCHandler<
|
|
|
1103
1148
|
},
|
|
1104
1149
|
});
|
|
1105
1150
|
|
|
1106
|
-
|
|
1107
|
-
isPartial ||
|
|
1108
|
-
(!request.headers.get("accept")?.includes("text/html") &&
|
|
1109
|
-
!url.searchParams.has("__html")) ||
|
|
1110
|
-
url.searchParams.has("__rsc");
|
|
1111
|
-
|
|
1112
|
-
if (isRscRequest) {
|
|
1151
|
+
if (isRscRequest(request, url, isPartial)) {
|
|
1113
1152
|
return createResponseWithMergedHeaders(rscStream, {
|
|
1114
1153
|
status: 404,
|
|
1115
|
-
headers: {
|
|
1154
|
+
headers: {
|
|
1155
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
1156
|
+
// Router identity for the client's pre-decode integrity check; a
|
|
1157
|
+
// same-app 404 matches and applies in place. See response-adapter.
|
|
1158
|
+
"X-RSC-Router-Id": router.id,
|
|
1159
|
+
},
|
|
1116
1160
|
});
|
|
1117
1161
|
}
|
|
1118
1162
|
|
|
@@ -1121,7 +1165,7 @@ export function createRSCHandler<
|
|
|
1121
1165
|
request,
|
|
1122
1166
|
env,
|
|
1123
1167
|
url,
|
|
1124
|
-
|
|
1168
|
+
getRequestContext()._metricsStore,
|
|
1125
1169
|
);
|
|
1126
1170
|
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
1127
1171
|
nonce,
|