@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17
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 +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- 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 +270 -30
- package/skills/host-router/SKILL.md +82 -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 +294 -30
- 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 +203 -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 +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- 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 +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- 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 +67 -40
- 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 +86 -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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- 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 +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -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 +5 -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 +278 -96
- 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 +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -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/taint.ts +55 -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 +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- 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 +52 -26
- package/src/index.ts +100 -38
- 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 +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- 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 +7 -17
- 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 +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- 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 +192 -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 +6 -28
- 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 +242 -75
- package/src/router/segment-resolution/helpers.ts +64 -25
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- 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 +2 -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 +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- 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 +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- 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 +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- 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 +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- 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 +348 -128
- package/src/ssr/index.tsx +23 -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 +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- 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 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- 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 +49 -98
- 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 +89 -0
- 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 +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- 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 +92 -42
- 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/router/middleware-cookies.ts +0 -55
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
runWithRequestContext,
|
|
6
6
|
type RequestContext,
|
|
7
7
|
} from "../server/request-context.js";
|
|
8
|
-
import { contextGet, contextSet } from "../context-var.js";
|
|
8
|
+
import { contextGet, contextSet, hasContextVars } from "../context-var.js";
|
|
9
9
|
import {
|
|
10
10
|
createPrerenderContext,
|
|
11
11
|
createStaticContext,
|
|
12
12
|
createReverseFunction,
|
|
13
13
|
} from "./handler-context.js";
|
|
14
|
-
import {
|
|
14
|
+
import { detectPrerenderPassthrough } from "../prerender.js";
|
|
15
15
|
import { isRouteRootScoped } from "../route-map-builder.js";
|
|
16
16
|
import { setupBuildUse } from "./loader-resolution.js";
|
|
17
17
|
import { loadManifest } from "./manifest.js";
|
|
@@ -54,13 +54,21 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
54
54
|
deps: PrerenderMatchDeps<TEnv>,
|
|
55
55
|
buildVars?: Record<string, any>,
|
|
56
56
|
isPassthroughRoute?: boolean,
|
|
57
|
+
buildEnv?: TEnv,
|
|
58
|
+
/** Dev-only: check getParams() for passthrough routes to skip unknown params. */
|
|
59
|
+
devMode?: boolean,
|
|
57
60
|
): Promise<{
|
|
58
61
|
segments: SerializedSegmentData[];
|
|
59
|
-
|
|
62
|
+
/** RSC-encoded handle map ("" when none) — see handle-snapshot.ts. Encoded in
|
|
63
|
+
* the producer (where the Flight codec resolves) so the node-side build/dev
|
|
64
|
+
* sinks can persist it without touching the codec. */
|
|
65
|
+
handles: string;
|
|
60
66
|
routeName: string;
|
|
61
67
|
params: Record<string, string>;
|
|
62
68
|
interceptSegments?: SerializedSegmentData[];
|
|
63
|
-
|
|
69
|
+
/** RSC-encoded MERGED (main + intercept) handle map for the intercept artifact;
|
|
70
|
+
* the sinks store it as-is (no longer merge raw records). */
|
|
71
|
+
interceptHandles?: string;
|
|
64
72
|
passthrough?: true;
|
|
65
73
|
} | null> {
|
|
66
74
|
// 1. Find the matching route entry
|
|
@@ -74,6 +82,17 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
74
82
|
// Build RouterContext for loadManifest/traverseBack
|
|
75
83
|
const routerCtx = deps.buildRouterContext();
|
|
76
84
|
|
|
85
|
+
// Passthrough sentinel result: an unknown-param Passthrough route falls
|
|
86
|
+
// through to the live handler at runtime, so no artifact is baked. A fresh
|
|
87
|
+
// object is returned per call (no site mutates or identity-compares it).
|
|
88
|
+
const passthroughResult = () => ({
|
|
89
|
+
segments: [],
|
|
90
|
+
handles: "",
|
|
91
|
+
routeName: matched.routeKey,
|
|
92
|
+
params: matchedParams,
|
|
93
|
+
passthrough: true as const,
|
|
94
|
+
});
|
|
95
|
+
|
|
77
96
|
return runWithRouterContext(routerCtx, async () => {
|
|
78
97
|
// 2. Load the manifest entry tree
|
|
79
98
|
const manifestEntry = await loadManifest(
|
|
@@ -90,21 +109,91 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
90
109
|
entries.push(entry);
|
|
91
110
|
}
|
|
92
111
|
|
|
112
|
+
// 3b. Dev-mode passthrough shortcut: if the route is a Passthrough route
|
|
113
|
+
// and has getParams(), check if the matched params are in the known list.
|
|
114
|
+
// In production, only known params are pre-rendered; unknown params fall
|
|
115
|
+
// through to the live handler. Mirror that behavior in dev mode to avoid
|
|
116
|
+
// rendering unknown params with build: true.
|
|
117
|
+
// Vars collected from getParams() probe — merged into render context below.
|
|
118
|
+
let devProbeBuildVars: Record<string, any> | undefined;
|
|
119
|
+
|
|
120
|
+
if (devMode && matchedPassthroughRoute) {
|
|
121
|
+
const routeEntry = entries.find(
|
|
122
|
+
(
|
|
123
|
+
e,
|
|
124
|
+
): e is EntryData & {
|
|
125
|
+
type: "route";
|
|
126
|
+
prerenderDef: { getParams: (ctx: any) => Promise<any[]> | any[] };
|
|
127
|
+
} =>
|
|
128
|
+
e.type === "route" &&
|
|
129
|
+
!!(e as any).isPassthrough &&
|
|
130
|
+
!!(e as any).prerenderDef?.getParams,
|
|
131
|
+
);
|
|
132
|
+
if (routeEntry) {
|
|
133
|
+
try {
|
|
134
|
+
const probeBuildVars: Record<string, any> = {};
|
|
135
|
+
const knownParamsList = await routeEntry.prerenderDef.getParams({
|
|
136
|
+
build: true as const,
|
|
137
|
+
dev: true,
|
|
138
|
+
set: ((keyOrVar: any, value: any) => {
|
|
139
|
+
contextSet(probeBuildVars, keyOrVar, value);
|
|
140
|
+
}) as any,
|
|
141
|
+
reverse: createReverseFunction(deps.mergedRouteMap),
|
|
142
|
+
get env() {
|
|
143
|
+
if (buildEnv !== undefined) return buildEnv;
|
|
144
|
+
throw new Error(
|
|
145
|
+
"[rango] ctx.env is not available during dev-mode getParams(). " +
|
|
146
|
+
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
// Compare only the keys returned by getParams — ignore mount params
|
|
151
|
+
// from include() prefixes that aren't part of the handler's params.
|
|
152
|
+
const isKnown = knownParamsList.some((known: Record<string, any>) => {
|
|
153
|
+
const knownKeys = Object.keys(known);
|
|
154
|
+
return knownKeys.every(
|
|
155
|
+
(k) => String(known[k]) === String(matchedParams[k]),
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
if (!isKnown) {
|
|
159
|
+
return passthroughResult();
|
|
160
|
+
}
|
|
161
|
+
// Preserve vars set by getParams() for the render context
|
|
162
|
+
if (hasContextVars(probeBuildVars)) {
|
|
163
|
+
devProbeBuildVars = probeBuildVars;
|
|
164
|
+
}
|
|
165
|
+
} catch (err: any) {
|
|
166
|
+
// Mirror production semantics (prerender-collection.ts):
|
|
167
|
+
// Skip errors are intentional — treat as passthrough.
|
|
168
|
+
// All other errors propagate so dev surfaces them.
|
|
169
|
+
if (err?.name === "Skip") {
|
|
170
|
+
return passthroughResult();
|
|
171
|
+
}
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
93
177
|
// 4. Create handle store for collecting handle data
|
|
94
178
|
const handleStore = createHandleStore();
|
|
95
179
|
|
|
96
180
|
// 5. Create a minimal request context with the handle store
|
|
97
|
-
// Shallow-copy getParams vars so each param set is independent
|
|
98
|
-
|
|
181
|
+
// Shallow-copy getParams vars so each param set is independent.
|
|
182
|
+
// In dev mode, merge vars from the getParams() probe if the caller
|
|
183
|
+
// didn't provide buildVars (production passes them from expandPrerenderRoutes).
|
|
184
|
+
const effectiveBuildVars = buildVars ?? devProbeBuildVars;
|
|
185
|
+
const variables: Record<string, any> = effectiveBuildVars
|
|
186
|
+
? { ...effectiveBuildVars }
|
|
187
|
+
: {};
|
|
99
188
|
const stubRes = new Response(null, { status: 200 });
|
|
100
189
|
const minimalRequestContext: RequestContext<TEnv> = {
|
|
101
|
-
env: {} as TEnv,
|
|
190
|
+
env: buildEnv ?? ({} as TEnv),
|
|
102
191
|
request: new Request("http://prerender" + pathname),
|
|
103
192
|
url: new URL("http://prerender" + pathname),
|
|
104
193
|
originalUrl: new URL("http://prerender" + pathname),
|
|
105
194
|
pathname,
|
|
106
195
|
searchParams: new URLSearchParams(),
|
|
107
|
-
|
|
196
|
+
_variables: variables,
|
|
108
197
|
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
109
198
|
set: ((keyOrVar: any, value: any) => {
|
|
110
199
|
contextSet(variables, keyOrVar, value);
|
|
@@ -117,16 +206,22 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
117
206
|
deleteCookie: () => {},
|
|
118
207
|
header: () => {},
|
|
119
208
|
setStatus: () => {},
|
|
209
|
+
_setStatus: () => {},
|
|
210
|
+
_rotateStateCookie: () => {},
|
|
211
|
+
_setKeepCacheDirective: () => {},
|
|
120
212
|
use: (() => {
|
|
121
213
|
throw new Error("use() not available during pre-rendering");
|
|
122
214
|
}) as any,
|
|
123
215
|
method: "GET",
|
|
124
216
|
_handleStore: handleStore,
|
|
217
|
+
_requestTags: new Set<string>(),
|
|
125
218
|
waitUntil: () => {},
|
|
126
219
|
onResponse: () => {},
|
|
127
220
|
_onResponseCallbacks: [],
|
|
128
221
|
setLocationState() {},
|
|
129
222
|
_locationState: undefined,
|
|
223
|
+
_renderBarrier: Promise.resolve(),
|
|
224
|
+
_resolveRenderBarrier: () => {},
|
|
130
225
|
_reportedErrors: new WeakSet<object>(),
|
|
131
226
|
reverse: createReverseFunction(
|
|
132
227
|
deps.mergedRouteMap,
|
|
@@ -139,7 +234,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
139
234
|
return runWithRequestContext(minimalRequestContext, async () => {
|
|
140
235
|
// 6. Create prerender context with synthetic URL.
|
|
141
236
|
// Prerender handlers get params, pathname, url, searchParams, search,
|
|
142
|
-
// reverse,
|
|
237
|
+
// reverse, use(handle), and optionally env (when buildEnv is configured).
|
|
143
238
|
const buildCtx = createPrerenderContext<TEnv>(
|
|
144
239
|
matchedParams,
|
|
145
240
|
pathname,
|
|
@@ -147,6 +242,8 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
147
242
|
matched.routeKey,
|
|
148
243
|
variables,
|
|
149
244
|
matchedPassthroughRoute,
|
|
245
|
+
buildEnv,
|
|
246
|
+
devMode,
|
|
150
247
|
);
|
|
151
248
|
|
|
152
249
|
// 7. Wire use() for handles only (loaders throw)
|
|
@@ -163,17 +260,13 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
163
260
|
{ skipLoaders: true },
|
|
164
261
|
);
|
|
165
262
|
|
|
166
|
-
// 9. Detect passthrough sentinel: handler returned ctx.passthrough()
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
params: matchedParams,
|
|
174
|
-
passthrough: true as const,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
263
|
+
// 9. Detect passthrough sentinel: handler returned ctx.passthrough().
|
|
264
|
+
// When the route declares loading(), the handler result is deferred so the
|
|
265
|
+
// component is a thenable resolving to the sentinel — detectPrerenderPassthrough
|
|
266
|
+
// resolves thenables before testing (a sync check would miss it and bake a
|
|
267
|
+
// corrupt artifact).
|
|
268
|
+
if (await detectPrerenderPassthrough(allSegments)) {
|
|
269
|
+
return passthroughResult();
|
|
177
270
|
}
|
|
178
271
|
|
|
179
272
|
// 10. Filter out any loader segments (belt-and-suspenders)
|
|
@@ -185,16 +278,22 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
185
278
|
|
|
186
279
|
// 12. Serialize segments using the cache serializer
|
|
187
280
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
281
|
+
const { encodeHandles } = await import("../cache/handle-snapshot.js");
|
|
188
282
|
const serializedSegments = await serializeSegments(nonLoaderSegments);
|
|
189
283
|
|
|
190
|
-
// 13. Collect handle data per segment (skip segments with no handle data)
|
|
191
|
-
|
|
284
|
+
// 13. Collect handle data per segment (skip segments with no handle data).
|
|
285
|
+
// Encoded through the Flight codec (not stored raw) so Promise/ReactNode
|
|
286
|
+
// handle values survive the JSON-serialized build artifact / dev wire —
|
|
287
|
+
// the same fix the runtime cache uses. Encode happens here, in the RSC
|
|
288
|
+
// environment where the codec resolves; the node-side sinks only persist.
|
|
289
|
+
const handlesRecord: Record<string, SegmentHandleData> = {};
|
|
192
290
|
for (const seg of nonLoaderSegments) {
|
|
193
291
|
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
194
292
|
if (Object.keys(segHandles).length > 0) {
|
|
195
|
-
|
|
293
|
+
handlesRecord[seg.id] = segHandles;
|
|
196
294
|
}
|
|
197
295
|
}
|
|
296
|
+
const handles = await encodeHandles(handlesRecord);
|
|
198
297
|
|
|
199
298
|
// Use the trie-level route key (e.g., "docs", "docs.article")
|
|
200
299
|
const routeName = matched.routeKey;
|
|
@@ -204,7 +303,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
204
303
|
// evaluation -- we pre-render all intercepts unconditionally and let
|
|
205
304
|
// runtime matching decide which to serve.
|
|
206
305
|
let interceptSegments: SerializedSegmentData[] | undefined;
|
|
207
|
-
let interceptHandles:
|
|
306
|
+
let interceptHandles: string | undefined;
|
|
208
307
|
|
|
209
308
|
const foundIntercepts: {
|
|
210
309
|
intercept: InterceptEntry;
|
|
@@ -212,24 +311,14 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
212
311
|
}[] = [];
|
|
213
312
|
let current: EntryData | null = manifestEntry;
|
|
214
313
|
while (current) {
|
|
215
|
-
|
|
216
|
-
|
|
314
|
+
// Flatten the entry and its sibling layouts into one source list, the
|
|
315
|
+
// same traversal findInterceptForRoute uses; the build keeps ALL matches
|
|
316
|
+
// (not just the innermost) and skips when(). intercept/layout are
|
|
317
|
+
// non-optional arrays, so empty ones are a no-op here.
|
|
318
|
+
for (const source of [current, ...current.layout]) {
|
|
319
|
+
for (const ic of source.intercept) {
|
|
217
320
|
if (ic.routeName === matched.routeKey) {
|
|
218
|
-
foundIntercepts.push({ intercept: ic, entry:
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (current.layout && current.layout.length > 0) {
|
|
223
|
-
for (const siblingLayout of current.layout) {
|
|
224
|
-
if (siblingLayout.intercept && siblingLayout.intercept.length > 0) {
|
|
225
|
-
for (const ic of siblingLayout.intercept) {
|
|
226
|
-
if (ic.routeName === matched.routeKey) {
|
|
227
|
-
foundIntercepts.push({
|
|
228
|
-
intercept: ic,
|
|
229
|
-
entry: siblingLayout,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
321
|
+
foundIntercepts.push({ intercept: ic, entry: source });
|
|
233
322
|
}
|
|
234
323
|
}
|
|
235
324
|
}
|
|
@@ -240,6 +329,18 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
240
329
|
const interceptResolvedSegments: typeof nonLoaderSegments = [];
|
|
241
330
|
|
|
242
331
|
for (const { intercept, entry: parentEntry } of foundIntercepts) {
|
|
332
|
+
// setupBuildUse keys handle pushes by ctx._currentSegmentId. The main
|
|
333
|
+
// resolveAllSegments pass left it on the route's segment id, so pin it
|
|
334
|
+
// to THIS intercept's slot id before resolving the handler/layout --
|
|
335
|
+
// otherwise the intercept's ctx.use() handle pushes land in the wrong
|
|
336
|
+
// bucket and getDataForSegment(seg.id) below drops them from the baked
|
|
337
|
+
// artifact. Re-pinned per iteration so multiple intercepts targeting
|
|
338
|
+
// the same route each get their own id (mirrors fresh.ts parallel-slot
|
|
339
|
+
// pinning).
|
|
340
|
+
const interceptSegmentId = `${parentEntry.shortCode}.${intercept.slotName}`;
|
|
341
|
+
(buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
342
|
+
interceptSegmentId;
|
|
343
|
+
|
|
243
344
|
// Resolve handler
|
|
244
345
|
const handlerRaw =
|
|
245
346
|
typeof intercept.handler === "function"
|
|
@@ -266,7 +367,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
266
367
|
}
|
|
267
368
|
|
|
268
369
|
interceptResolvedSegments.push({
|
|
269
|
-
id:
|
|
370
|
+
id: interceptSegmentId,
|
|
270
371
|
namespace: `intercept:${intercept.routeName}`,
|
|
271
372
|
type: "parallel" as const,
|
|
272
373
|
index: 0,
|
|
@@ -286,13 +387,20 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
286
387
|
interceptSegments = await serializeSegments(
|
|
287
388
|
interceptResolvedSegments,
|
|
288
389
|
);
|
|
289
|
-
|
|
390
|
+
const interceptHandlesRecord: Record<string, SegmentHandleData> = {};
|
|
290
391
|
for (const seg of interceptResolvedSegments) {
|
|
291
392
|
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
292
393
|
if (Object.keys(segHandles).length > 0) {
|
|
293
|
-
|
|
394
|
+
interceptHandlesRecord[seg.id] = segHandles;
|
|
294
395
|
}
|
|
295
396
|
}
|
|
397
|
+
// The intercept artifact serves main + intercept segments together, so
|
|
398
|
+
// encode the MERGED handle map here (the sinks no longer merge raw
|
|
399
|
+
// records — they store this pre-encoded string as-is).
|
|
400
|
+
interceptHandles = await encodeHandles({
|
|
401
|
+
...handlesRecord,
|
|
402
|
+
...interceptHandlesRecord,
|
|
403
|
+
});
|
|
296
404
|
}
|
|
297
405
|
}
|
|
298
406
|
|
|
@@ -319,7 +427,9 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
319
427
|
handlerId: string,
|
|
320
428
|
mergedRouteMap: Record<string, string>,
|
|
321
429
|
routeName?: string,
|
|
322
|
-
|
|
430
|
+
buildEnv?: TEnv,
|
|
431
|
+
devMode?: boolean,
|
|
432
|
+
): Promise<{ encoded: string; handles: string } | null> {
|
|
323
433
|
const syntheticUrl = new URL("http://prerender/");
|
|
324
434
|
const syntheticRequest = new Request(syntheticUrl);
|
|
325
435
|
|
|
@@ -329,13 +439,13 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
329
439
|
// Minimal request context so setupBuildUse can find the HandleStore
|
|
330
440
|
const stubRes = new Response(null, { status: 200 });
|
|
331
441
|
const minimalRequestContext: RequestContext<TEnv> = {
|
|
332
|
-
env: {} as TEnv,
|
|
442
|
+
env: buildEnv ?? ({} as TEnv),
|
|
333
443
|
request: syntheticRequest,
|
|
334
444
|
url: syntheticUrl,
|
|
335
445
|
originalUrl: syntheticUrl,
|
|
336
446
|
pathname: "/",
|
|
337
447
|
searchParams: syntheticUrl.searchParams,
|
|
338
|
-
|
|
448
|
+
_variables: {},
|
|
339
449
|
get: () => undefined as any,
|
|
340
450
|
set: () => {},
|
|
341
451
|
params: {},
|
|
@@ -346,16 +456,22 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
346
456
|
deleteCookie: () => {},
|
|
347
457
|
header: () => {},
|
|
348
458
|
setStatus: () => {},
|
|
459
|
+
_setStatus: () => {},
|
|
460
|
+
_rotateStateCookie: () => {},
|
|
461
|
+
_setKeepCacheDirective: () => {},
|
|
349
462
|
use: (() => {
|
|
350
463
|
throw new Error("use() not available during static pre-rendering");
|
|
351
464
|
}) as any,
|
|
352
465
|
method: "GET",
|
|
353
466
|
_handleStore: handleStore,
|
|
467
|
+
_requestTags: new Set<string>(),
|
|
354
468
|
waitUntil: () => {},
|
|
355
469
|
onResponse: () => {},
|
|
356
470
|
_onResponseCallbacks: [],
|
|
357
471
|
setLocationState() {},
|
|
358
472
|
_locationState: undefined,
|
|
473
|
+
_renderBarrier: Promise.resolve(),
|
|
474
|
+
_resolveRenderBarrier: () => {},
|
|
359
475
|
_reportedErrors: new WeakSet<object>(),
|
|
360
476
|
reverse: createReverseFunction(
|
|
361
477
|
mergedRouteMap,
|
|
@@ -366,9 +482,13 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
366
482
|
};
|
|
367
483
|
|
|
368
484
|
return runWithRequestContext(minimalRequestContext, async () => {
|
|
369
|
-
// Static handlers get only reverse
|
|
370
|
-
|
|
371
|
-
|
|
485
|
+
// Static handlers get only reverse, use(handle), and optionally env.
|
|
486
|
+
const buildCtx = createStaticContext<TEnv>(
|
|
487
|
+
mergedRouteMap,
|
|
488
|
+
routeName,
|
|
489
|
+
buildEnv,
|
|
490
|
+
devMode,
|
|
491
|
+
);
|
|
372
492
|
|
|
373
493
|
// Set segment ID so handle pushes are keyed correctly
|
|
374
494
|
(buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
@@ -377,23 +497,41 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
377
497
|
setupBuildUse(buildCtx);
|
|
378
498
|
|
|
379
499
|
const raw = await handler(buildCtx);
|
|
380
|
-
|
|
500
|
+
|
|
501
|
+
// Static handlers must return a ReactNode. A returned Response (e.g. an
|
|
502
|
+
// accidental redirect()) would otherwise be serialized as a corrupt build
|
|
503
|
+
// artifact; fail loudly instead. The fresh/revalidation paths route handler
|
|
504
|
+
// results through handleHandlerResult, which throws Responses; the static
|
|
505
|
+
// build path bypasses that, so guard here.
|
|
506
|
+
if (raw instanceof Response) {
|
|
507
|
+
throw new TypeError(
|
|
508
|
+
`Static handler "${routeName}" returned a Response. Static handlers must return a ReactNode; ` +
|
|
509
|
+
`Responses (redirects, file responses) are not supported during static pre-rendering.`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
381
512
|
|
|
382
513
|
const segment: ResolvedSegment = {
|
|
383
514
|
id: handlerId,
|
|
384
515
|
namespace: handlerId,
|
|
385
516
|
type: "layout",
|
|
386
517
|
index: 0,
|
|
387
|
-
component,
|
|
518
|
+
component: raw,
|
|
388
519
|
params: {},
|
|
389
520
|
belongsToRoute: false,
|
|
390
521
|
};
|
|
391
522
|
|
|
392
523
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
524
|
+
const { encodeHandleValue } = await import("../cache/handle-snapshot.js");
|
|
393
525
|
const [serialized] = await serializeSegments([segment]);
|
|
394
526
|
|
|
395
|
-
// Collect handle data pushed during rendering
|
|
396
|
-
|
|
527
|
+
// Collect handle data pushed during rendering and Flight-encode it (so
|
|
528
|
+
// Promise/ReactNode handle values survive the JSON build artifact). "" when
|
|
529
|
+
// nothing was pushed.
|
|
530
|
+
const segHandles = handleStore.getDataForSegment(handlerId);
|
|
531
|
+
const handles =
|
|
532
|
+
Object.keys(segHandles).length > 0
|
|
533
|
+
? await encodeHandleValue(segHandles)
|
|
534
|
+
: "";
|
|
397
535
|
|
|
398
536
|
return { encoded: serialized.encoded, handles };
|
|
399
537
|
});
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { traverseBack } from "./pattern-matching.js";
|
|
3
|
-
import { collectRouteMiddleware } from "./middleware.js";
|
|
4
|
-
import {
|
|
5
|
-
parseAcceptTypes,
|
|
6
|
-
RSC_RESPONSE_TYPE,
|
|
7
|
-
pickNegotiateVariant,
|
|
8
|
-
} from "./content-negotiation.js";
|
|
1
|
+
import { negotiateRoute } from "./content-negotiation.js";
|
|
9
2
|
import { runWithRouterLogContext, withRouterLogScope } from "./logging.js";
|
|
10
3
|
import type { EntryData } from "../server/context";
|
|
11
4
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
12
5
|
import type { MiddlewareFn } from "./middleware.js";
|
|
6
|
+
import { resolveRoute } from "./route-snapshot.js";
|
|
13
7
|
|
|
14
8
|
export interface PreviewMatchDeps<TEnv = any> {
|
|
15
9
|
findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
|
|
@@ -42,110 +36,46 @@ export async function previewMatch<TEnv = any>(
|
|
|
42
36
|
const url = new URL(request.url);
|
|
43
37
|
const pathname = url.pathname;
|
|
44
38
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
// Route resolution via snapshot (lite mode: skip entries/cacheScope
|
|
40
|
+
// since previewMatch only needs matched, manifestEntry, routeMiddleware,
|
|
41
|
+
// and responseType)
|
|
42
|
+
const result = await resolveRoute<TEnv>(pathname, {
|
|
43
|
+
findMatch: deps.findMatch,
|
|
44
|
+
lite: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!result) {
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// Skip redirect check - will be handled in full match
|
|
52
|
-
if (
|
|
52
|
+
if (result.type === "redirect") {
|
|
53
53
|
return { routeMiddleware: undefined };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
const manifestEntry
|
|
58
|
-
|
|
59
|
-
matched.routeKey,
|
|
60
|
-
pathname,
|
|
61
|
-
undefined, // No metrics store for preview
|
|
62
|
-
false, // isSSR - doesn't matter for preview
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Collect route-level middleware from entry tree
|
|
66
|
-
// Includes middleware from orphan layouts (inline layouts within routes)
|
|
67
|
-
const routeMiddleware = collectRouteMiddleware(
|
|
68
|
-
traverseBack(manifestEntry),
|
|
69
|
-
matched.params,
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// Check for response type (from trie match or manifest entry)
|
|
73
|
-
const responseType =
|
|
74
|
-
matched.responseType ||
|
|
75
|
-
(manifestEntry.type === "route"
|
|
76
|
-
? manifestEntry.responseType
|
|
77
|
-
: undefined);
|
|
78
|
-
|
|
79
|
-
// Content negotiation: when negotiate variants exist, pick the best
|
|
80
|
-
// handler based on the Accept header. Uses q-values and client order
|
|
81
|
-
// as tiebreaker (matching Express/Hono behavior). RSC routes participate
|
|
82
|
-
// as text/html candidates so browsers naturally get HTML without
|
|
83
|
-
// special-casing.
|
|
84
|
-
if (matched.negotiateVariants && matched.negotiateVariants.length > 0) {
|
|
85
|
-
const acceptEntries = parseAcceptTypes(
|
|
86
|
-
request.headers.get("accept") || "",
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
// Build candidate list preserving definition order.
|
|
90
|
-
// For wildcard (*/*) and no-Accept fallback, the first candidate wins.
|
|
91
|
-
const variants = matched.negotiateVariants;
|
|
92
|
-
let candidates: Array<{ routeKey: string; responseType: string }>;
|
|
93
|
-
if (responseType) {
|
|
94
|
-
// Primary is response-type — include it as a candidate
|
|
95
|
-
candidates = [
|
|
96
|
-
...variants,
|
|
97
|
-
{ routeKey: matched.routeKey, responseType },
|
|
98
|
-
];
|
|
99
|
-
} else {
|
|
100
|
-
// Primary is RSC — insert as text/html candidate in definition order
|
|
101
|
-
const rscCandidate = {
|
|
102
|
-
routeKey: matched.routeKey,
|
|
103
|
-
responseType: RSC_RESPONSE_TYPE,
|
|
104
|
-
};
|
|
105
|
-
candidates = matched.rscFirst
|
|
106
|
-
? [rscCandidate, ...variants]
|
|
107
|
-
: [...variants, rscCandidate];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const variant = pickNegotiateVariant(acceptEntries, candidates);
|
|
56
|
+
const snapshot = result.snapshot;
|
|
57
|
+
const { matched, manifestEntry, routeMiddleware, responseType } =
|
|
58
|
+
snapshot;
|
|
111
59
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
traverseBack(negotiateEntry),
|
|
129
|
-
matched.params,
|
|
130
|
-
);
|
|
131
|
-
return {
|
|
132
|
-
routeMiddleware:
|
|
133
|
-
variantMiddleware.length > 0 ? variantMiddleware : undefined,
|
|
134
|
-
responseType: variant.responseType,
|
|
135
|
-
handler:
|
|
136
|
-
negotiateEntry.type === "route"
|
|
137
|
-
? negotiateEntry.handler
|
|
138
|
-
: undefined,
|
|
139
|
-
params: matched.params,
|
|
140
|
-
negotiated: true,
|
|
141
|
-
manifestEntry: negotiateEntry,
|
|
142
|
-
routeKey: matched.routeKey,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
60
|
+
const negotiation = await negotiateRoute(request, pathname, snapshot);
|
|
61
|
+
if (negotiation) {
|
|
62
|
+
return {
|
|
63
|
+
routeMiddleware:
|
|
64
|
+
negotiation.routeMiddleware.length > 0
|
|
65
|
+
? negotiation.routeMiddleware
|
|
66
|
+
: undefined,
|
|
67
|
+
responseType: negotiation.responseType,
|
|
68
|
+
handler: negotiation.handler,
|
|
69
|
+
params: matched.params,
|
|
70
|
+
manifestEntry: negotiation.manifestEntry,
|
|
71
|
+
routeKey: matched.routeKey,
|
|
72
|
+
// omitted unless a variant negotiated, preserving the prior public
|
|
73
|
+
// shape (absent for plain response routes, not negotiated:false)
|
|
74
|
+
...(negotiation.negotiated ? { negotiated: true } : {}),
|
|
75
|
+
};
|
|
145
76
|
}
|
|
146
77
|
|
|
147
|
-
//
|
|
148
|
-
// negotiated so the handler sets Vary: Accept on the response.
|
|
78
|
+
// No negotiation or RSC won — return default route info
|
|
149
79
|
const hasVariants =
|
|
150
80
|
matched.negotiateVariants && matched.negotiateVariants.length > 0;
|
|
151
81
|
return {
|