@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100
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 +9 -0
- package/README.md +1037 -4
- package/dist/bin/rango.js +1619 -157
- package/dist/vite/index.js +5762 -2301
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +71 -63
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +367 -71
- package/skills/host-router/SKILL.md +218 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +176 -8
- package/skills/layout/SKILL.md +124 -3
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +474 -47
- package/skills/middleware/SKILL.md +207 -37
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +15 -11
- package/skills/parallel/SKILL.md +272 -1
- package/skills/prerender/SKILL.md +467 -65
- package/skills/rango/SKILL.md +89 -21
- package/skills/response-routes/SKILL.md +152 -91
- package/skills/route/SKILL.md +305 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/server-actions/SKILL.md +739 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +333 -86
- package/skills/use-cache/SKILL.md +324 -0
- package/skills/view-transitions/SKILL.md +212 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +136 -68
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +374 -561
- package/src/browser/navigation-client.ts +228 -70
- package/src/browser/navigation-store.ts +97 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +376 -315
- package/src/browser/prefetch/cache.ts +314 -0
- package/src/browser/prefetch/fetch.ts +282 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +191 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +152 -0
- package/src/browser/react/Link.tsx +255 -71
- package/src/browser/react/NavigationProvider.tsx +152 -24
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +55 -0
- package/src/browser/react/index.ts +15 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -120
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +78 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +99 -0
- package/src/browser/react/use-router.ts +83 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +85 -99
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +246 -64
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +158 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +84 -23
- package/src/build/generate-route-types.ts +39 -828
- package/src/build/index.ts +4 -5
- package/src/build/route-trie.ts +85 -32
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -307
- package/src/cache/cf/cf-cache-store.ts +573 -21
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +6 -1
- package/src/client.tsx +118 -302
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +55 -10
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +65 -45
- package/src/index.rsc.ts +138 -21
- package/src/index.ts +206 -51
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +25 -143
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +159 -13
- package/src/prerender.ts +397 -29
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +231 -121
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1134 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +483 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition.ts +1 -1431
- package/src/route-map-builder.ts +162 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +66 -9
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +418 -86
- package/src/router/intercept-resolution.ts +35 -20
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +359 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +98 -32
- package/src/router/match-api.ts +196 -261
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +441 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +415 -86
- package/src/router/match-middleware/cache-store.ts +91 -29
- package/src/router/match-middleware/intercept-resolution.ts +48 -21
- package/src/router/match-middleware/segment-resolution.ts +73 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +154 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +209 -0
- package/src/router/middleware.ts +373 -371
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +292 -52
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +152 -39
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +756 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1407 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1315
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +111 -39
- package/src/router/types.ts +17 -9
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +642 -2011
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +864 -1114
- package/src/rsc/helpers.ts +181 -19
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +395 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +360 -0
- package/src/rsc/rsc-rendering.ts +256 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +360 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +52 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +187 -38
- package/src/server/context.ts +333 -59
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +603 -109
- package/src/server.ts +35 -155
- package/src/ssr/index.tsx +107 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +764 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +209 -0
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +167 -0
- package/src/types.ts +1 -1757
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +108 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1282
- package/src/use-loader.tsx +161 -81
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +376 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +486 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -2063
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +98 -0
- package/src/vite/plugins/client-ref-dedup.ts +131 -0
- package/src/vite/plugins/client-ref-hashing.ts +117 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +816 -0
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +336 -0
- package/src/vite/plugins/version-injector.ts +109 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +497 -0
- package/src/vite/router-discovery.ts +1423 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +161 -0
- package/src/vite/utils/prerender-utils.ts +222 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/vite/expose-prerender-handler-id.ts +0 -429
- package/src/vite/package-resolution.ts +0 -125
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -70,9 +70,11 @@
|
|
|
70
70
|
* - No segments yielded from this middleware
|
|
71
71
|
*
|
|
72
72
|
* Loaders:
|
|
73
|
-
* - NEVER cached
|
|
73
|
+
* - NEVER cached in the segment cache
|
|
74
74
|
* - Always resolved fresh on every request
|
|
75
75
|
* - Ensures data freshness even with cached UI components
|
|
76
|
+
* - Segment cache staleness does NOT propagate to loader revalidation;
|
|
77
|
+
* loaders use their own revalidation rules (actionId, user-defined)
|
|
76
78
|
*
|
|
77
79
|
*
|
|
78
80
|
* REVALIDATION RULES
|
|
@@ -92,38 +94,199 @@
|
|
|
92
94
|
import type { ResolvedSegment } from "../../types.js";
|
|
93
95
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
94
96
|
import { getRouterContext } from "../router-context.js";
|
|
95
|
-
import
|
|
97
|
+
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
98
|
+
import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
|
|
99
|
+
import { treeHasStreaming } from "./segment-resolution.js";
|
|
100
|
+
import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
|
|
101
|
+
import type { HandleStore } from "../../server/handle-store.js";
|
|
102
|
+
import {
|
|
103
|
+
getRequestContext,
|
|
104
|
+
_getRequestContext,
|
|
105
|
+
} from "../../server/request-context.js";
|
|
96
106
|
|
|
97
107
|
// Lazily initialized prerender store singleton and dynamically imported deps.
|
|
98
108
|
// Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
|
|
99
109
|
// top-level, which breaks vitest (only URLs with file:, data:, node: schemes).
|
|
100
110
|
let prerenderStoreInstance: PrerenderStore | null | undefined;
|
|
101
|
-
let _deserializeSegments:
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
let _deserializeSegments:
|
|
112
|
+
| typeof import("../../cache/segment-codec.js").deserializeSegments
|
|
113
|
+
| undefined;
|
|
114
|
+
let _restoreHandles:
|
|
115
|
+
| typeof import("../../cache/handle-snapshot.js").restoreHandles
|
|
116
|
+
| undefined;
|
|
117
|
+
let _hashParams:
|
|
118
|
+
| typeof import("../../prerender/param-hash.js").hashParams
|
|
119
|
+
| undefined;
|
|
120
|
+
let _lazyGetRequestContext:
|
|
121
|
+
| typeof import("../../server/request-context.js").getRequestContext
|
|
122
|
+
| undefined;
|
|
123
|
+
|
|
124
|
+
function paramsEqual(
|
|
125
|
+
a: Record<string, string>,
|
|
126
|
+
b: Record<string, string>,
|
|
127
|
+
): boolean {
|
|
128
|
+
if (a === b) return true;
|
|
129
|
+
|
|
130
|
+
const keysA = Object.keys(a);
|
|
131
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
132
|
+
|
|
133
|
+
for (const key of keysA) {
|
|
134
|
+
if (a[key] !== b[key]) return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
104
139
|
|
|
105
140
|
async function ensurePrerenderDeps() {
|
|
106
141
|
if (!_deserializeSegments) {
|
|
107
|
-
const [
|
|
108
|
-
import("../../cache/
|
|
142
|
+
const [codec, snapshot, paramHash, reqCtx, store] = await Promise.all([
|
|
143
|
+
import("../../cache/segment-codec.js"),
|
|
144
|
+
import("../../cache/handle-snapshot.js"),
|
|
109
145
|
import("../../prerender/param-hash.js"),
|
|
110
146
|
import("../../server/request-context.js"),
|
|
111
147
|
import("../../prerender/store.js"),
|
|
112
148
|
]);
|
|
113
|
-
_deserializeSegments =
|
|
149
|
+
_deserializeSegments = codec.deserializeSegments;
|
|
150
|
+
_restoreHandles = snapshot.restoreHandles;
|
|
114
151
|
_hashParams = paramHash.hashParams;
|
|
115
|
-
|
|
152
|
+
_lazyGetRequestContext = reqCtx.getRequestContext;
|
|
116
153
|
if (prerenderStoreInstance === undefined) {
|
|
117
154
|
prerenderStoreInstance = store.createPrerenderStore();
|
|
118
155
|
}
|
|
119
156
|
}
|
|
120
157
|
}
|
|
121
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Shared yield logic for prerender and static handler store entries.
|
|
161
|
+
* Deserializes segments, replays handle data, yields segments with partial
|
|
162
|
+
* navigation nullification, and resolves fresh loaders.
|
|
163
|
+
*/
|
|
164
|
+
async function* yieldFromStore<TEnv>(
|
|
165
|
+
entry: PrerenderEntry,
|
|
166
|
+
ctx: MatchContext<TEnv>,
|
|
167
|
+
state: MatchPipelineState,
|
|
168
|
+
pipelineStart: number,
|
|
169
|
+
handleStoreRef?: HandleStore,
|
|
170
|
+
): AsyncGenerator<ResolvedSegment> {
|
|
171
|
+
const { resolveLoadersOnlyWithRevalidation, resolveLoadersOnly } =
|
|
172
|
+
getRouterContext<TEnv>();
|
|
173
|
+
|
|
174
|
+
if (
|
|
175
|
+
!_deserializeSegments ||
|
|
176
|
+
!_restoreHandles ||
|
|
177
|
+
!_hashParams ||
|
|
178
|
+
!_lazyGetRequestContext
|
|
179
|
+
) {
|
|
180
|
+
throw new Error("yieldFromStore called before ensurePrerenderDeps");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const segments = await _deserializeSegments(entry.segments);
|
|
184
|
+
|
|
185
|
+
// Replay handle data (same as runtime cache hit path).
|
|
186
|
+
// Prefer the eagerly-captured handleStoreRef to avoid ALS disruption in workerd.
|
|
187
|
+
const handleStore = handleStoreRef ?? _lazyGetRequestContext()?._handleStore;
|
|
188
|
+
if (handleStore) {
|
|
189
|
+
_restoreHandles(entry.handles, handleStore);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
state.cacheHit = true;
|
|
193
|
+
state.cacheSource = "prerender";
|
|
194
|
+
state.cachedSegments = segments;
|
|
195
|
+
state.cachedMatchedIds = segments.map((s) => s.id);
|
|
196
|
+
|
|
197
|
+
// Set streaming flag (once) and resolve render barrier.
|
|
198
|
+
const reqCtx = handleStoreRef ? undefined : _lazyGetRequestContext?.();
|
|
199
|
+
const barrierReqCtx = reqCtx ?? _getRequestContext();
|
|
200
|
+
if (barrierReqCtx) {
|
|
201
|
+
if (barrierReqCtx._treeHasStreaming === undefined) {
|
|
202
|
+
barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
|
|
203
|
+
}
|
|
204
|
+
barrierReqCtx._resolveRenderBarrier(segments);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// For partial navigation, nullify components the client already has
|
|
208
|
+
// so parent layouts stay live (client keeps its existing versions).
|
|
209
|
+
// When params changed (e.g., different guide slug), the segments have
|
|
210
|
+
// different content, so we must NOT nullify.
|
|
211
|
+
const paramsChanged =
|
|
212
|
+
!ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
|
|
213
|
+
for (const segment of segments) {
|
|
214
|
+
if (
|
|
215
|
+
!ctx.isFullMatch &&
|
|
216
|
+
!paramsChanged &&
|
|
217
|
+
ctx.clientSegmentSet.has(segment.id)
|
|
218
|
+
) {
|
|
219
|
+
segment.component = null;
|
|
220
|
+
segment.loading = undefined;
|
|
221
|
+
}
|
|
222
|
+
yield segment;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Resolve loaders fresh (loaders are never pre-rendered/cached)
|
|
226
|
+
const ms = ctx.metricsStore;
|
|
227
|
+
const loaderStart = performance.now();
|
|
228
|
+
|
|
229
|
+
if (ctx.isFullMatch) {
|
|
230
|
+
if (resolveLoadersOnly) {
|
|
231
|
+
const loaderSegments = await ctx.Store.run(() =>
|
|
232
|
+
resolveLoadersOnly(ctx.entries, ctx.handlerContext),
|
|
233
|
+
);
|
|
234
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
235
|
+
for (const segment of loaderSegments) {
|
|
236
|
+
yield segment;
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
if (resolveLoadersOnlyWithRevalidation) {
|
|
243
|
+
const loaderResult = await ctx.Store.run(() =>
|
|
244
|
+
resolveLoadersOnlyWithRevalidation(
|
|
245
|
+
ctx.entries,
|
|
246
|
+
ctx.handlerContext,
|
|
247
|
+
ctx.clientSegmentSet,
|
|
248
|
+
ctx.prevParams,
|
|
249
|
+
ctx.request,
|
|
250
|
+
ctx.prevUrl,
|
|
251
|
+
ctx.url,
|
|
252
|
+
ctx.routeKey,
|
|
253
|
+
ctx.actionContext,
|
|
254
|
+
ctx.stale || undefined,
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
state.matchedIds = [
|
|
258
|
+
...state.cachedMatchedIds!,
|
|
259
|
+
...loaderResult.matchedIds,
|
|
260
|
+
];
|
|
261
|
+
for (const segment of loaderResult.segments) {
|
|
262
|
+
yield segment;
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (ms) {
|
|
270
|
+
const loaderEnd = performance.now();
|
|
271
|
+
ms.metrics.push({
|
|
272
|
+
label: "pipeline:loader-resolve",
|
|
273
|
+
duration: loaderEnd - loaderStart,
|
|
274
|
+
startTime: loaderStart - ms.requestStart,
|
|
275
|
+
depth: 1,
|
|
276
|
+
});
|
|
277
|
+
ms.metrics.push({
|
|
278
|
+
label: "pipeline:cache-hit",
|
|
279
|
+
duration: loaderEnd - pipelineStart,
|
|
280
|
+
startTime: pipelineStart - ms.requestStart,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
122
285
|
/**
|
|
123
286
|
* Async generator middleware type
|
|
124
287
|
*/
|
|
125
288
|
export type GeneratorMiddleware<T> = (
|
|
126
|
-
source: AsyncGenerator<T
|
|
289
|
+
source: AsyncGenerator<T>,
|
|
127
290
|
) => AsyncGenerator<T>;
|
|
128
291
|
|
|
129
292
|
/**
|
|
@@ -141,14 +304,21 @@ export type GeneratorMiddleware<T> = (
|
|
|
141
304
|
*/
|
|
142
305
|
export function withCacheLookup<TEnv>(
|
|
143
306
|
ctx: MatchContext<TEnv>,
|
|
144
|
-
state: MatchPipelineState
|
|
307
|
+
state: MatchPipelineState,
|
|
145
308
|
): GeneratorMiddleware<ResolvedSegment> {
|
|
146
309
|
return async function* (
|
|
147
|
-
source: AsyncGenerator<ResolvedSegment
|
|
310
|
+
source: AsyncGenerator<ResolvedSegment>,
|
|
148
311
|
): AsyncGenerator<ResolvedSegment> {
|
|
149
312
|
const pipelineStart = performance.now();
|
|
150
313
|
const ms = ctx.metricsStore;
|
|
151
314
|
|
|
315
|
+
// Eagerly capture the HandleStore before any async operations.
|
|
316
|
+
// In workerd/Cloudflare, dynamic imports and fetch() inside the pipeline
|
|
317
|
+
// can disrupt AsyncLocalStorage, causing getRequestContext() to return
|
|
318
|
+
// undefined afterward. Capturing the reference early ensures handle replay
|
|
319
|
+
// and handler handle-push work regardless of ALS state.
|
|
320
|
+
const handleStoreRef = _getRequestContext()?._handleStore;
|
|
321
|
+
|
|
152
322
|
const {
|
|
153
323
|
evaluateRevalidation,
|
|
154
324
|
buildEntryRevalidateMap,
|
|
@@ -158,83 +328,127 @@ export function withCacheLookup<TEnv>(
|
|
|
158
328
|
|
|
159
329
|
// Prerender lookup: check build-time cached data before runtime cache.
|
|
160
330
|
// Prerender data is available regardless of runtime cache configuration.
|
|
161
|
-
|
|
331
|
+
// Skip for HMR requests — the dev prerender endpoint reads from a stale
|
|
332
|
+
// RouterRegistry snapshot; rendering fresh ensures edits are visible.
|
|
333
|
+
const isHmr = !!ctx.request.headers.get("X-RSC-HMR");
|
|
334
|
+
if (!ctx.isAction && !isHmr && ctx.matched.pr) {
|
|
162
335
|
await ensurePrerenderDeps();
|
|
163
336
|
if (prerenderStoreInstance) {
|
|
164
337
|
const paramHash = _hashParams!(ctx.matched.params);
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// Replay handle data (same as runtime cache hit path)
|
|
170
|
-
const handleStore = _getRequestContext!()?._handleStore;
|
|
171
|
-
if (handleStore) {
|
|
172
|
-
for (const [segId, segHandles] of Object.entries(entry.handles)) {
|
|
173
|
-
if (Object.keys(segHandles).length > 0) {
|
|
174
|
-
handleStore.replaySegmentData(segId, segHandles);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
338
|
+
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
339
|
+
(entry) => entry.type === "route" && entry.isPassthrough === true,
|
|
340
|
+
);
|
|
178
341
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
342
|
+
if (ctx.isIntercept) {
|
|
343
|
+
// Intercept navigation: try intercept-specific prerender entry
|
|
344
|
+
const entry = await prerenderStoreInstance.get(
|
|
345
|
+
ctx.matched.routeKey,
|
|
346
|
+
paramHash + "/i",
|
|
347
|
+
{
|
|
348
|
+
pathname: ctx.pathname,
|
|
349
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
350
|
+
},
|
|
351
|
+
);
|
|
352
|
+
if (entry) {
|
|
353
|
+
yield* yieldFromStore(
|
|
354
|
+
entry,
|
|
355
|
+
ctx,
|
|
356
|
+
state,
|
|
357
|
+
pipelineStart,
|
|
358
|
+
handleStoreRef,
|
|
359
|
+
);
|
|
360
|
+
return;
|
|
192
361
|
}
|
|
362
|
+
// No intercept prerender -- fall through to normal pipeline
|
|
363
|
+
// (skip non-intercept prerender to let intercept-resolution run)
|
|
364
|
+
} else {
|
|
365
|
+
// Normal navigation: existing behavior
|
|
366
|
+
const entry = await prerenderStoreInstance.get(
|
|
367
|
+
ctx.matched.routeKey,
|
|
368
|
+
paramHash,
|
|
369
|
+
{
|
|
370
|
+
pathname: ctx.pathname,
|
|
371
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
if (entry) {
|
|
375
|
+
yield* yieldFromStore(
|
|
376
|
+
entry,
|
|
377
|
+
ctx,
|
|
378
|
+
state,
|
|
379
|
+
pipelineStart,
|
|
380
|
+
handleStoreRef,
|
|
381
|
+
);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
193
387
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
388
|
+
// Dev-mode static handler interception for non-Node.js runtimes.
|
|
389
|
+
// __PRERENDER_DEV_URL is set by the Vite plugin when the RSC environment
|
|
390
|
+
// lacks a Node.js module runner (e.g. workerd, Deno workers). In those
|
|
391
|
+
// runtimes, handlers that depend on Node APIs like node:fs can't run
|
|
392
|
+
// in-process. We redirect them to the /__rsc_prerender endpoint which
|
|
393
|
+
// resolves segments in a Node.js temp server, same as prerender routes.
|
|
394
|
+
// In Node.js dev mode this variable is undefined -- handlers run
|
|
395
|
+
// in-process where Node APIs work, so no interception is needed.
|
|
396
|
+
if (!ctx.isAction && !ctx.matched.pr && globalThis.__PRERENDER_DEV_URL) {
|
|
397
|
+
const hasStatic = ctx.entries.some(
|
|
398
|
+
(e) =>
|
|
399
|
+
(e.type === "layout" ||
|
|
400
|
+
e.type === "route" ||
|
|
401
|
+
e.type === "parallel") &&
|
|
402
|
+
e.isStaticPrerender,
|
|
403
|
+
);
|
|
404
|
+
if (hasStatic) {
|
|
405
|
+
await ensurePrerenderDeps();
|
|
406
|
+
if (prerenderStoreInstance) {
|
|
407
|
+
const paramHash = _hashParams!(ctx.matched.params);
|
|
408
|
+
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
409
|
+
(entry) => entry.type === "route" && entry.isPassthrough === true,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
if (ctx.isIntercept) {
|
|
413
|
+
const entry = await prerenderStoreInstance.get(
|
|
414
|
+
ctx.matched.routeKey,
|
|
415
|
+
paramHash + "/i",
|
|
416
|
+
{
|
|
417
|
+
pathname: ctx.pathname,
|
|
418
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
419
|
+
},
|
|
420
|
+
);
|
|
421
|
+
if (entry) {
|
|
422
|
+
yield* yieldFromStore(
|
|
423
|
+
entry,
|
|
424
|
+
ctx,
|
|
425
|
+
state,
|
|
426
|
+
pipelineStart,
|
|
427
|
+
handleStoreRef,
|
|
199
428
|
);
|
|
200
|
-
|
|
201
|
-
for (const segment of loaderSegments) {
|
|
202
|
-
yield segment;
|
|
203
|
-
}
|
|
204
|
-
} else {
|
|
205
|
-
state.matchedIds = state.cachedMatchedIds!;
|
|
429
|
+
return;
|
|
206
430
|
}
|
|
431
|
+
// No intercept prerender -- fall through to normal pipeline
|
|
207
432
|
} else {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
433
|
+
const entry = await prerenderStoreInstance.get(
|
|
434
|
+
ctx.matched.routeKey,
|
|
435
|
+
paramHash,
|
|
436
|
+
{
|
|
437
|
+
pathname: ctx.pathname,
|
|
438
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
439
|
+
},
|
|
440
|
+
);
|
|
441
|
+
if (entry) {
|
|
442
|
+
yield* yieldFromStore(
|
|
443
|
+
entry,
|
|
444
|
+
ctx,
|
|
445
|
+
state,
|
|
446
|
+
pipelineStart,
|
|
447
|
+
handleStoreRef,
|
|
221
448
|
);
|
|
222
|
-
|
|
223
|
-
...state.cachedMatchedIds!,
|
|
224
|
-
...loaderResult.matchedIds,
|
|
225
|
-
];
|
|
226
|
-
for (const segment of loaderResult.segments) {
|
|
227
|
-
yield segment;
|
|
228
|
-
}
|
|
229
|
-
} else {
|
|
230
|
-
state.matchedIds = state.cachedMatchedIds!;
|
|
449
|
+
return;
|
|
231
450
|
}
|
|
232
451
|
}
|
|
233
|
-
|
|
234
|
-
if (ms) {
|
|
235
|
-
ms.metrics.push({ label: "pipeline:cache-lookup", duration: performance.now() - pipelineStart, startTime: pipelineStart - ms.requestStart });
|
|
236
|
-
}
|
|
237
|
-
return;
|
|
238
452
|
}
|
|
239
453
|
}
|
|
240
454
|
}
|
|
@@ -244,7 +458,11 @@ export function withCacheLookup<TEnv>(
|
|
|
244
458
|
// Cache miss - pass through to segment resolution
|
|
245
459
|
yield* source;
|
|
246
460
|
if (ms) {
|
|
247
|
-
ms.metrics.push({
|
|
461
|
+
ms.metrics.push({
|
|
462
|
+
label: "pipeline:cache-miss",
|
|
463
|
+
duration: performance.now() - pipelineStart,
|
|
464
|
+
startTime: pipelineStart - ms.requestStart,
|
|
465
|
+
});
|
|
248
466
|
}
|
|
249
467
|
return;
|
|
250
468
|
}
|
|
@@ -253,30 +471,54 @@ export function withCacheLookup<TEnv>(
|
|
|
253
471
|
const cacheResult = await ctx.cacheScope.lookupRoute(
|
|
254
472
|
ctx.pathname,
|
|
255
473
|
ctx.matched.params,
|
|
256
|
-
ctx.isIntercept
|
|
474
|
+
ctx.isIntercept,
|
|
257
475
|
);
|
|
258
476
|
|
|
259
477
|
if (!cacheResult) {
|
|
260
478
|
// Cache miss - pass through to segment resolution
|
|
261
479
|
yield* source;
|
|
262
480
|
if (ms) {
|
|
263
|
-
ms.metrics.push({
|
|
481
|
+
ms.metrics.push({
|
|
482
|
+
label: "pipeline:cache-miss",
|
|
483
|
+
duration: performance.now() - pipelineStart,
|
|
484
|
+
startTime: pipelineStart - ms.requestStart,
|
|
485
|
+
});
|
|
264
486
|
}
|
|
265
487
|
return;
|
|
266
488
|
}
|
|
267
489
|
|
|
268
490
|
// Cache HIT
|
|
269
491
|
state.cacheHit = true;
|
|
492
|
+
state.cacheSource = "runtime";
|
|
270
493
|
state.shouldRevalidate = cacheResult.shouldRevalidate;
|
|
271
494
|
state.cachedSegments = cacheResult.segments;
|
|
272
495
|
state.cachedMatchedIds = cacheResult.segments.map((s) => s.id);
|
|
273
496
|
|
|
274
|
-
// Apply revalidation to cached segments
|
|
275
|
-
|
|
497
|
+
// Apply revalidation to cached segments.
|
|
498
|
+
// For full matches or empty client segment sets, this map is unnecessary:
|
|
499
|
+
// we never run segment-level revalidation and can stream segments directly.
|
|
500
|
+
const canCheckSegmentRevalidation =
|
|
501
|
+
!ctx.isFullMatch &&
|
|
502
|
+
ctx.clientSegmentSet.size > 0 &&
|
|
503
|
+
!!buildEntryRevalidateMap;
|
|
504
|
+
const entryRevalidateMap = canCheckSegmentRevalidation
|
|
505
|
+
? buildEntryRevalidateMap(ctx.entries)
|
|
506
|
+
: undefined;
|
|
276
507
|
|
|
277
508
|
for (const segment of cacheResult.segments) {
|
|
278
509
|
// Skip segments client doesn't have - they need their component
|
|
279
510
|
if (!ctx.clientSegmentSet.has(segment.id)) {
|
|
511
|
+
if (isTraceActive()) {
|
|
512
|
+
pushRevalidationTraceEntry({
|
|
513
|
+
segmentId: segment.id,
|
|
514
|
+
segmentType: segment.type,
|
|
515
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
516
|
+
source: "cache-hit",
|
|
517
|
+
defaultShouldRevalidate: true,
|
|
518
|
+
finalShouldRevalidate: true,
|
|
519
|
+
reason: "new-segment",
|
|
520
|
+
});
|
|
521
|
+
}
|
|
280
522
|
yield segment;
|
|
281
523
|
continue;
|
|
282
524
|
}
|
|
@@ -289,8 +531,53 @@ export function withCacheLookup<TEnv>(
|
|
|
289
531
|
|
|
290
532
|
// Look up revalidation rules for this segment
|
|
291
533
|
const entryInfo = entryRevalidateMap?.get(segment.id);
|
|
534
|
+
|
|
535
|
+
// Even without explicit revalidation rules, route segments and their
|
|
536
|
+
// children must re-render when params or search params change — the
|
|
537
|
+
// handler reads ctx.params/ctx.searchParams so different values produce
|
|
538
|
+
// different content. Matches evaluateRevalidation's default logic.
|
|
539
|
+
const searchChanged = ctx.prevUrl.search !== ctx.url.search;
|
|
540
|
+
const routeParamsChanged = !paramsEqual(
|
|
541
|
+
ctx.matched.params,
|
|
542
|
+
ctx.prevParams,
|
|
543
|
+
);
|
|
544
|
+
const shouldDefaultRevalidate =
|
|
545
|
+
(searchChanged || routeParamsChanged) &&
|
|
546
|
+
(segment.type === "route" ||
|
|
547
|
+
(segment.belongsToRoute &&
|
|
548
|
+
(segment.type === "layout" || segment.type === "parallel")));
|
|
549
|
+
|
|
292
550
|
if (!entryInfo || entryInfo.revalidate.length === 0) {
|
|
551
|
+
if (shouldDefaultRevalidate) {
|
|
552
|
+
// Params or search params changed — must re-render even without custom rules
|
|
553
|
+
if (isTraceActive()) {
|
|
554
|
+
pushRevalidationTraceEntry({
|
|
555
|
+
segmentId: segment.id,
|
|
556
|
+
segmentType: segment.type,
|
|
557
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
558
|
+
source: "cache-hit",
|
|
559
|
+
defaultShouldRevalidate: true,
|
|
560
|
+
finalShouldRevalidate: true,
|
|
561
|
+
reason: routeParamsChanged
|
|
562
|
+
? "cached-params-changed"
|
|
563
|
+
: "cached-search-changed",
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
yield segment;
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
293
569
|
// No revalidation rules, use default behavior (skip if client has)
|
|
570
|
+
if (isTraceActive()) {
|
|
571
|
+
pushRevalidationTraceEntry({
|
|
572
|
+
segmentId: segment.id,
|
|
573
|
+
segmentType: segment.type,
|
|
574
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
575
|
+
source: "cache-hit",
|
|
576
|
+
defaultShouldRevalidate: false,
|
|
577
|
+
finalShouldRevalidate: false,
|
|
578
|
+
reason: "cached-no-rules",
|
|
579
|
+
});
|
|
580
|
+
}
|
|
294
581
|
segment.component = null;
|
|
295
582
|
segment.loading = undefined;
|
|
296
583
|
yield segment;
|
|
@@ -312,8 +599,24 @@ export function withCacheLookup<TEnv>(
|
|
|
312
599
|
routeKey: ctx.routeKey,
|
|
313
600
|
context: ctx.handlerContext,
|
|
314
601
|
actionContext: ctx.actionContext,
|
|
602
|
+
stale: cacheResult.shouldRevalidate || ctx.stale || undefined,
|
|
603
|
+
traceSource: "cache-hit",
|
|
315
604
|
});
|
|
316
605
|
|
|
606
|
+
const routerCtx = getRouterContext<TEnv>();
|
|
607
|
+
if (routerCtx.telemetry) {
|
|
608
|
+
const tSink = resolveSink(routerCtx.telemetry);
|
|
609
|
+
safeEmit(tSink, {
|
|
610
|
+
type: "revalidation.decision",
|
|
611
|
+
timestamp: performance.now(),
|
|
612
|
+
requestId: routerCtx.requestId,
|
|
613
|
+
segmentId: segment.id,
|
|
614
|
+
pathname: ctx.pathname,
|
|
615
|
+
routeKey: ctx.routeKey,
|
|
616
|
+
shouldRevalidate,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
317
620
|
if (!shouldRevalidate) {
|
|
318
621
|
// Client has it, no revalidation needed
|
|
319
622
|
segment.component = null;
|
|
@@ -323,15 +626,25 @@ export function withCacheLookup<TEnv>(
|
|
|
323
626
|
yield segment;
|
|
324
627
|
}
|
|
325
628
|
|
|
629
|
+
// Set streaming flag (once) and resolve render barrier.
|
|
630
|
+
const barrierReqCtx = _getRequestContext();
|
|
631
|
+
if (barrierReqCtx) {
|
|
632
|
+
if (barrierReqCtx._treeHasStreaming === undefined) {
|
|
633
|
+
barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
|
|
634
|
+
}
|
|
635
|
+
barrierReqCtx._resolveRenderBarrier(cacheResult.segments);
|
|
636
|
+
}
|
|
637
|
+
|
|
326
638
|
// Resolve loaders fresh (loaders are NOT cached by default)
|
|
327
639
|
// This ensures fresh data even on cache hit
|
|
328
640
|
const Store = ctx.Store;
|
|
641
|
+
const loaderStart = performance.now();
|
|
329
642
|
|
|
330
643
|
if (ctx.isFullMatch) {
|
|
331
644
|
// Full match (document request) - simple loader resolution without revalidation
|
|
332
645
|
if (resolveLoadersOnly) {
|
|
333
646
|
const loaderSegments = await Store.run(() =>
|
|
334
|
-
resolveLoadersOnly(ctx.entries, ctx.handlerContext)
|
|
647
|
+
resolveLoadersOnly(ctx.entries, ctx.handlerContext),
|
|
335
648
|
);
|
|
336
649
|
|
|
337
650
|
// Update state - full match doesn't track matchedIds separately
|
|
@@ -357,8 +670,13 @@ export function withCacheLookup<TEnv>(
|
|
|
357
670
|
ctx.prevUrl,
|
|
358
671
|
ctx.url,
|
|
359
672
|
ctx.routeKey,
|
|
360
|
-
ctx.actionContext
|
|
361
|
-
|
|
673
|
+
ctx.actionContext,
|
|
674
|
+
// Loaders are never cached in the segment cache, so segment
|
|
675
|
+
// staleness (cacheResult.shouldRevalidate) must not propagate.
|
|
676
|
+
// But browser-sent staleness (ctx.stale) — indicating an action
|
|
677
|
+
// happened in this or another tab — must still reach loaders.
|
|
678
|
+
ctx.stale || undefined,
|
|
679
|
+
),
|
|
362
680
|
);
|
|
363
681
|
|
|
364
682
|
// Update state with fresh loader matchedIds
|
|
@@ -376,7 +694,18 @@ export function withCacheLookup<TEnv>(
|
|
|
376
694
|
}
|
|
377
695
|
}
|
|
378
696
|
if (ms) {
|
|
379
|
-
|
|
697
|
+
const loaderEnd = performance.now();
|
|
698
|
+
ms.metrics.push({
|
|
699
|
+
label: "pipeline:loader-resolve",
|
|
700
|
+
duration: loaderEnd - loaderStart,
|
|
701
|
+
startTime: loaderStart - ms.requestStart,
|
|
702
|
+
depth: 1,
|
|
703
|
+
});
|
|
704
|
+
ms.metrics.push({
|
|
705
|
+
label: "pipeline:cache-hit",
|
|
706
|
+
duration: loaderEnd - pipelineStart,
|
|
707
|
+
startTime: pipelineStart - ms.requestStart,
|
|
708
|
+
});
|
|
380
709
|
}
|
|
381
710
|
};
|
|
382
711
|
}
|