@rangojs/router 0.21.0 → 0.22.0
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/dist/bin/rango.js +13 -1
- package/dist/types/browser/event-controller.d.ts +30 -13
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +14 -0
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +1 -1
- package/dist/types/cache/cf/cf-cache-store.d.ts +7 -1
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +23 -0
- package/dist/types/cache/index.d.ts +1 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +10 -1
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +63 -14
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +7 -1
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +7 -0
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +14 -0
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +30 -0
- package/dist/types/router/router-options.d.ts +24 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +2 -1
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +6 -4
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +8 -0
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +9 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +2 -0
- package/dist/types/server/request-context.d.ts +63 -2
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/flight.entry.d.ts +2 -0
- package/dist/types/testing/index.d.ts +4 -0
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/serve-shell-request.d.ts +34 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +7 -0
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/state.d.ts +11 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/vite/index.js +179 -496
- package/package.json +12 -2
- package/skills/cache-guide/SKILL.md +18 -17
- package/skills/caching/SKILL.md +40 -4
- package/skills/catalog.json +1 -1
- package/skills/cloudflare/SKILL.md +131 -0
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +22 -0
- package/skills/hooks/handle-and-actions.md +3 -4
- package/skills/hooks/navigation.md +10 -0
- package/skills/hooks/state.md +5 -4
- package/skills/host-router/SKILL.md +18 -0
- package/skills/loader/SKILL.md +6 -4
- package/skills/middleware/SKILL.md +1 -1
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +31 -8
- package/skills/prerender/SKILL.md +645 -8
- package/skills/rango/SKILL.md +1 -0
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/testing/SKILL.md +2 -1
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/serve-shell-request.md +104 -8
- package/skills/use-cache/SKILL.md +9 -5
- package/skills/vercel/SKILL.md +2 -0
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/browser/event-controller.ts +69 -25
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +75 -35
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +9 -2
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +48 -12
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/react/Link.tsx +37 -42
- package/src/browser/react/NavigationProvider.tsx +63 -3
- package/src/browser/react/context.ts +17 -0
- package/src/browser/react/location-state.ts +5 -19
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +4 -20
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +79 -39
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +26 -6
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +38 -36
- package/src/cache/cf/cf-cache-store.ts +16 -11
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +50 -0
- package/src/cache/index.ts +1 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +17 -0
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +69 -14
- package/src/cache/vercel/vercel-cache-store.ts +20 -6
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +7 -2
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +230 -38
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +33 -0
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +15 -17
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router.ts +107 -1
- package/src/rsc/handler.ts +51 -4
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +53 -11
- package/src/rsc/shell-build-manifest.ts +10 -8
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +108 -19
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +8 -0
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +15 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +13 -0
- package/src/server/request-context.ts +87 -1
- package/src/ssr/ssr-root.tsx +20 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +3 -0
- package/src/testing/index.ts +19 -0
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/render-route.tsx +25 -12
- package/src/testing/serve-shell-request.ts +190 -34
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +7 -0
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/shell-prerender-phase.ts +5 -5
- package/src/vite/discovery/state.ts +17 -1
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugins/virtual-entries.ts +8 -1
- package/src/vite/router-discovery.ts +14 -0
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/dist/types/browser/react/subscribe-to-nothing.d.ts +0 -7
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/browser/react/subscribe-to-nothing.ts +0 -7
- package/src/segment-content-promise.ts +0 -67
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
* - Request object
|
|
92
92
|
* - Action context (if POST)
|
|
93
93
|
*/
|
|
94
|
-
import type { ResolvedSegment } from "../../types.js";
|
|
94
|
+
import type { InternalHandlerContext, ResolvedSegment } from "../../types.js";
|
|
95
95
|
import { isPprEntry, type EntryData } from "../../server/context.js";
|
|
96
96
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
97
97
|
import { getRouterContext, type RouterContext } from "../router-context.js";
|
|
@@ -100,6 +100,17 @@ import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
|
|
|
100
100
|
import { treeHasStreaming } from "./segment-resolution.js";
|
|
101
101
|
import { loaderPins } from "../segment-resolution/loader-cache.js";
|
|
102
102
|
import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
|
|
103
|
+
import {
|
|
104
|
+
isStoredEntryStale,
|
|
105
|
+
readVerifiedStoredEntry,
|
|
106
|
+
serializePrerenderKey,
|
|
107
|
+
type PrerenderKey,
|
|
108
|
+
} from "../../prerender/writable-store.js";
|
|
109
|
+
import { IN_FLIGHT_LEADER_MAX_WAIT_MS } from "../../cache/cache-policy.js";
|
|
110
|
+
import type {
|
|
111
|
+
PrerenderTargetObject,
|
|
112
|
+
ResolvedPrerender,
|
|
113
|
+
} from "../../prerender/on-demand.js";
|
|
103
114
|
import {
|
|
104
115
|
_getRequestContext,
|
|
105
116
|
type RequestContext,
|
|
@@ -111,6 +122,7 @@ import {
|
|
|
111
122
|
import { ShellRecordUnavailableError } from "../../cache/shell-snapshot.js";
|
|
112
123
|
import { prerenderStoreShortCircuits } from "../navigation-snapshot.js";
|
|
113
124
|
import { paramsEqual } from "../params-util.js";
|
|
125
|
+
import { requestHeaders } from "../../server/request-headers.js";
|
|
114
126
|
|
|
115
127
|
// Lazily initialized prerender store singleton and dynamically imported deps.
|
|
116
128
|
// Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
|
|
@@ -128,10 +140,27 @@ let _restoreHandles:
|
|
|
128
140
|
let _decodeHandles:
|
|
129
141
|
| typeof import("../../cache/handle-snapshot.js").decodeHandles
|
|
130
142
|
| undefined;
|
|
143
|
+
let _restoreRecordHandles:
|
|
144
|
+
| typeof import("../../cache/handle-snapshot.js").restoreRecordHandles
|
|
145
|
+
| undefined;
|
|
131
146
|
let _hashParams:
|
|
132
147
|
| typeof import("../../prerender/param-hash.js").hashParams
|
|
133
148
|
| undefined;
|
|
134
149
|
|
|
150
|
+
/**
|
|
151
|
+
* @internal Replace the prerender store this module serves from, and return
|
|
152
|
+
* the one it replaced. For serveShellRequest (testing/serve-shell-request.ts),
|
|
153
|
+
* which installs the production store over the artifacts it bakes for a call
|
|
154
|
+
* and puts the previous one back after it.
|
|
155
|
+
*/
|
|
156
|
+
export function setPrerenderStoreForTests(
|
|
157
|
+
store: PrerenderStore | null | undefined,
|
|
158
|
+
): PrerenderStore | null | undefined {
|
|
159
|
+
const previous = prerenderStoreInstance;
|
|
160
|
+
prerenderStoreInstance = store;
|
|
161
|
+
return previous;
|
|
162
|
+
}
|
|
163
|
+
|
|
135
164
|
async function ensurePrerenderDeps() {
|
|
136
165
|
if (!_deserializeSegments) {
|
|
137
166
|
const [codec, snapshot, paramHash, store] = await Promise.all([
|
|
@@ -144,6 +173,7 @@ async function ensurePrerenderDeps() {
|
|
|
144
173
|
_fragmentSegments = codec.fragmentSegments;
|
|
145
174
|
_restoreHandles = snapshot.restoreHandles;
|
|
146
175
|
_decodeHandles = snapshot.decodeHandles;
|
|
176
|
+
_restoreRecordHandles = snapshot.restoreRecordHandles;
|
|
147
177
|
_hashParams = paramHash.hashParams;
|
|
148
178
|
if (prerenderStoreInstance === undefined) {
|
|
149
179
|
prerenderStoreInstance = store.createPrerenderStore();
|
|
@@ -240,12 +270,21 @@ async function* yieldFromStore<TEnv>(
|
|
|
240
270
|
reqCtx: RequestContext<TEnv> | undefined,
|
|
241
271
|
resolveLoadersOnly: RouterContext<TEnv>["resolveLoadersOnly"],
|
|
242
272
|
resolveLoadersOnlyWithRevalidation: RouterContext<TEnv>["resolveLoadersOnlyWithRevalidation"],
|
|
273
|
+
/**
|
|
274
|
+
* The entry may be newer than the client's copy: an on-demand overlay entry
|
|
275
|
+
* that a refresh (possibly inside this very action) replaced. On an action
|
|
276
|
+
* the route's own segments are then re-sent, as a live action re-render
|
|
277
|
+
* re-sends them (revalidation.ts "action:route-segment"); the immutable
|
|
278
|
+
* bundled manifest keeps the client's copy.
|
|
279
|
+
*/
|
|
280
|
+
replaceable?: boolean,
|
|
243
281
|
): AsyncGenerator<ResolvedSegment> {
|
|
244
282
|
if (
|
|
245
283
|
!_deserializeSegments ||
|
|
246
284
|
!_fragmentSegments ||
|
|
247
285
|
!_restoreHandles ||
|
|
248
286
|
!_decodeHandles ||
|
|
287
|
+
!_restoreRecordHandles ||
|
|
249
288
|
!_hashParams
|
|
250
289
|
) {
|
|
251
290
|
throw new Error("yieldFromStore called before ensurePrerenderDeps");
|
|
@@ -270,6 +309,16 @@ async function* yieldFromStore<TEnv>(
|
|
|
270
309
|
}
|
|
271
310
|
}
|
|
272
311
|
|
|
312
|
+
// Before the render barrier freezes the document snapshot.
|
|
313
|
+
const shellHandles = reqCtx?._shellImplicitCache?.prerenderHandles;
|
|
314
|
+
if (handleStore && shellHandles) {
|
|
315
|
+
await _restoreRecordHandles!(
|
|
316
|
+
handleStore,
|
|
317
|
+
shellHandles,
|
|
318
|
+
loaderPins(ctx.entries, reqCtx),
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
273
322
|
state.cacheHit = true;
|
|
274
323
|
state.cacheSource = "prerender";
|
|
275
324
|
state.cachedSegments = segments;
|
|
@@ -292,11 +341,16 @@ async function* yieldFromStore<TEnv>(
|
|
|
292
341
|
// different content, so we must NOT nullify.
|
|
293
342
|
const paramsChanged =
|
|
294
343
|
!ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
|
|
344
|
+
const resendRouteSegments = replaceable === true && ctx.isAction;
|
|
295
345
|
for (const segment of segments) {
|
|
296
346
|
if (
|
|
297
347
|
!ctx.isFullMatch &&
|
|
298
348
|
!paramsChanged &&
|
|
299
|
-
ctx.clientSegmentSet.has(segment.id)
|
|
349
|
+
ctx.clientSegmentSet.has(segment.id) &&
|
|
350
|
+
!(
|
|
351
|
+
resendRouteSegments &&
|
|
352
|
+
(segment.type === "route" || segment.belongsToRoute === true)
|
|
353
|
+
)
|
|
300
354
|
) {
|
|
301
355
|
keepClientSegment(segment);
|
|
302
356
|
}
|
|
@@ -345,11 +399,9 @@ export async function prerenderEntryExists(
|
|
|
345
399
|
).createPrerenderStore();
|
|
346
400
|
}
|
|
347
401
|
if (!prerenderStoreInstance) return false;
|
|
348
|
-
// Non-intercept variant only: whether the navigation
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
// artifact. A wrong guess is reclassified post-match from the match
|
|
352
|
-
// pipeline's `_pprReplayPostMatchReason` stamp.
|
|
402
|
+
// Non-intercept variant only: whether the navigation is an intercept (which
|
|
403
|
+
// reads `paramHash + "/i"`) resolves during the match. A wrong guess is
|
|
404
|
+
// reclassified post-match from `_pprReplayPostMatchReason`.
|
|
353
405
|
const entry = await prerenderStoreInstance.get(
|
|
354
406
|
routeKey,
|
|
355
407
|
_hashParams!(params),
|
|
@@ -363,6 +415,63 @@ export async function prerenderEntryExists(
|
|
|
363
415
|
return entry != null;
|
|
364
416
|
}
|
|
365
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Serialized overlay key -> start time of its in-flight `onRevalidate`.
|
|
420
|
+
* Dedupes the obvious Node wiring, `(t, env) => router.prerender(t, { env })`,
|
|
421
|
+
* and queue sends alike, per isolate. Entries older than
|
|
422
|
+
* IN_FLIGHT_LEADER_MAX_WAIT_MS count as free: a never-settling onRevalidate
|
|
423
|
+
* (hung queue send) or a killed execution context never runs `.finally`, and
|
|
424
|
+
* would otherwise pin the key for the isolate's lifetime.
|
|
425
|
+
*/
|
|
426
|
+
const overlayRevalidationsInFlight = new Map<string, number>();
|
|
427
|
+
|
|
428
|
+
/** @internal Tests only (testing/serve-shell-request.ts resetShellTestState). */
|
|
429
|
+
export function resetOverlayRevalidationsForTests(): void {
|
|
430
|
+
overlayRevalidationsInFlight.clear();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** @internal Exported for the eviction unit test. */
|
|
434
|
+
export function scheduleOverlayRevalidation<TEnv>(
|
|
435
|
+
serializedKey: string,
|
|
436
|
+
onRevalidate: NonNullable<ResolvedPrerender["config"]["onRevalidate"]>,
|
|
437
|
+
target: PrerenderTargetObject,
|
|
438
|
+
reqCtx: RequestContext<TEnv>,
|
|
439
|
+
now: number = Date.now(),
|
|
440
|
+
): void {
|
|
441
|
+
// waitUntil is a no-op under build contexts: nothing would clear the key.
|
|
442
|
+
if (reqCtx.build) return;
|
|
443
|
+
const startedAt = overlayRevalidationsInFlight.get(serializedKey);
|
|
444
|
+
if (
|
|
445
|
+
startedAt !== undefined &&
|
|
446
|
+
now - startedAt < IN_FLIGHT_LEADER_MAX_WAIT_MS
|
|
447
|
+
) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
overlayRevalidationsInFlight.set(serializedKey, now);
|
|
451
|
+
const env = reqCtx.env;
|
|
452
|
+
const executionContext = reqCtx.executionContext;
|
|
453
|
+
const reportError = reqCtx._reportBackgroundError;
|
|
454
|
+
// The stale entry still serves this request; this only schedules.
|
|
455
|
+
reqCtx.waitUntil(() =>
|
|
456
|
+
Promise.resolve()
|
|
457
|
+
.then(() => onRevalidate(target, env, executionContext))
|
|
458
|
+
.then(
|
|
459
|
+
() => {},
|
|
460
|
+
(err) => {
|
|
461
|
+
// A failed onRevalidate must not fail the response, but must reach
|
|
462
|
+
// onError like the runtime cache's stale-revalidation failures.
|
|
463
|
+
reportError?.(err, "stale-revalidation");
|
|
464
|
+
},
|
|
465
|
+
)
|
|
466
|
+
.finally(() => {
|
|
467
|
+
// An evicted-and-rescheduled entry belongs to the newer run.
|
|
468
|
+
if (overlayRevalidationsInFlight.get(serializedKey) === now) {
|
|
469
|
+
overlayRevalidationsInFlight.delete(serializedKey);
|
|
470
|
+
}
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
366
475
|
/**
|
|
367
476
|
* Look up a prerendered (build-time cached) entry for the current route and, on
|
|
368
477
|
* a hit, yield its segments. Returns true when an entry was served (the caller
|
|
@@ -378,31 +487,99 @@ async function* tryPrerenderLookup<TEnv>(
|
|
|
378
487
|
reqCtx: RequestContext<TEnv> | undefined,
|
|
379
488
|
resolveLoadersOnly: RouterContext<TEnv>["resolveLoadersOnly"],
|
|
380
489
|
resolveLoadersOnlyWithRevalidation: RouterContext<TEnv>["resolveLoadersOnlyWithRevalidation"],
|
|
490
|
+
isPassthroughPrerenderRoute: boolean,
|
|
491
|
+
overlay?: ResolvedPrerender,
|
|
381
492
|
): AsyncGenerator<ResolvedSegment, boolean> {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
(
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
493
|
+
// 1. Writable durable overlay (per-request, env-scoped), read before the
|
|
494
|
+
// bundled manifest: it is always newer. Only on-demand routes are read
|
|
495
|
+
// (router.prerender() refuses non-od routes, so a pr-only route always
|
|
496
|
+
// misses), and never for intercept navigations (only the main variant is
|
|
497
|
+
// written). Actions read it too: a plain on-demand route has no live
|
|
498
|
+
// handler, so skipping the overlay would 404 an overlay-only param
|
|
499
|
+
// (gateOnDemandProducer). A store error degrades to a miss
|
|
500
|
+
// (readVerifiedStoredEntry); a stale hit serves and may schedule onRevalidate.
|
|
501
|
+
if (overlay && ctx.matched.od && !ctx.isIntercept) {
|
|
502
|
+
const key: PrerenderKey = {
|
|
503
|
+
routerId: overlay.routerId,
|
|
504
|
+
version: overlay.version,
|
|
505
|
+
routeName: ctx.matched.routeKey,
|
|
506
|
+
paramHash: _hashParams!(ctx.matched.params),
|
|
507
|
+
};
|
|
508
|
+
const stored = await readVerifiedStoredEntry(
|
|
509
|
+
overlay.config.store,
|
|
510
|
+
key,
|
|
511
|
+
ctx.matched.params,
|
|
512
|
+
);
|
|
513
|
+
if (stored) {
|
|
514
|
+
// A stale "removed" marker is rechecked like a stale page: only a
|
|
515
|
+
// refresh that hit notFound() (or declined) stamps one with a ttl or
|
|
516
|
+
// tags. A prerender.remove() marker has neither and never gets here.
|
|
517
|
+
if (
|
|
518
|
+
overlay.config.onRevalidate &&
|
|
519
|
+
reqCtx &&
|
|
520
|
+
isStoredEntryStale(stored, Date.now())
|
|
521
|
+
) {
|
|
522
|
+
scheduleOverlayRevalidation(
|
|
523
|
+
serializePrerenderKey(key),
|
|
524
|
+
overlay.config.onRevalidate,
|
|
525
|
+
{
|
|
526
|
+
route: ctx.matched.routeKey,
|
|
527
|
+
params: ctx.matched.params,
|
|
528
|
+
} as PrerenderTargetObject,
|
|
529
|
+
reqCtx,
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
if (stored.removed === true) {
|
|
533
|
+
// The page was removed. Not served, and step 2 is skipped: the
|
|
534
|
+
// build-time entry below must not come back. The route's handler
|
|
535
|
+
// answers: a Passthrough route's live handler, or a plain route's
|
|
536
|
+
// gated producer, which `_prerenderRemoved` makes a 404 in dev too
|
|
537
|
+
// (gateOnDemandProducer).
|
|
538
|
+
(
|
|
539
|
+
ctx.handlerContext as InternalHandlerContext<any, TEnv>
|
|
540
|
+
)._prerenderRemoved = true;
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
yield* yieldFromStore(
|
|
544
|
+
stored.entry,
|
|
545
|
+
ctx,
|
|
546
|
+
state,
|
|
547
|
+
pipelineStart,
|
|
548
|
+
reqCtx,
|
|
549
|
+
resolveLoadersOnly,
|
|
550
|
+
resolveLoadersOnlyWithRevalidation,
|
|
551
|
+
true,
|
|
552
|
+
);
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// 2. Bundled build manifest (immutable). Absent for on-demand-only routes.
|
|
558
|
+
if (prerenderStoreInstance) {
|
|
559
|
+
const paramHash = _hashParams!(ctx.matched.params);
|
|
560
|
+
const entry = await prerenderStoreInstance.get(
|
|
561
|
+
ctx.matched.routeKey,
|
|
562
|
+
ctx.isIntercept ? paramHash + "/i" : paramHash,
|
|
563
|
+
{
|
|
564
|
+
pathname: ctx.pathname,
|
|
565
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
566
|
+
},
|
|
567
|
+
);
|
|
568
|
+
if (entry) {
|
|
569
|
+
yield* yieldFromStore(
|
|
570
|
+
entry,
|
|
571
|
+
ctx,
|
|
572
|
+
state,
|
|
573
|
+
pipelineStart,
|
|
574
|
+
reqCtx,
|
|
575
|
+
resolveLoadersOnly,
|
|
576
|
+
resolveLoadersOnlyWithRevalidation,
|
|
577
|
+
);
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return false;
|
|
406
583
|
}
|
|
407
584
|
|
|
408
585
|
/**
|
|
@@ -463,6 +640,8 @@ export function withCacheLookup<TEnv>(
|
|
|
463
640
|
? "intercept"
|
|
464
641
|
: undefined;
|
|
465
642
|
}
|
|
643
|
+
// Per-request writable prerender overlay (durable), resolved by the handler.
|
|
644
|
+
const prerenderOverlay = pipelineReqCtx?._prerender;
|
|
466
645
|
|
|
467
646
|
const {
|
|
468
647
|
evaluateRevalidation,
|
|
@@ -473,20 +652,30 @@ export function withCacheLookup<TEnv>(
|
|
|
473
652
|
resolveAllSegmentsWithRevalidation,
|
|
474
653
|
} = getRouterContext<TEnv>();
|
|
475
654
|
|
|
476
|
-
|
|
655
|
+
// An on-demand route may have no build-baked entry yet still needs a
|
|
656
|
+
// writable-overlay lookup, so od joins pr in the gate. The retained producer
|
|
657
|
+
// is NEVER run by this pipeline: a miss falls through like pr + miss.
|
|
658
|
+
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
659
|
+
(entry) => entry.type === "route" && entry.isPassthrough === true,
|
|
660
|
+
);
|
|
661
|
+
const overlayEligible =
|
|
662
|
+
ctx.matched.od === true && !requestHeaders(ctx.request).get("X-RSC-HMR");
|
|
663
|
+
if (
|
|
664
|
+
prerenderStoreShortCircuits(ctx.matched.pr, ctx.request) ||
|
|
665
|
+
overlayEligible
|
|
666
|
+
) {
|
|
477
667
|
// Actions normally re-render fresh and skip the prerender store. But a pure
|
|
478
668
|
// Prerender route's handler is evicted at build, so there is no fresh
|
|
479
669
|
// handler to run on an action re-render -- without the fallback the
|
|
480
670
|
// re-render falls through to the evicted handler and throws "No prerender
|
|
481
671
|
// data found". Serve the prerendered entry instead (the action ran already;
|
|
482
|
-
// its result is applied client-side via useActionState).
|
|
483
|
-
//
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
);
|
|
672
|
+
// its result is applied client-side via useActionState). A plain on-demand
|
|
673
|
+
// route is the same case, served from the overlay first (an overlay entry
|
|
674
|
+
// is newer than the build one, and may be the only one). Passthrough
|
|
675
|
+
// routes keep a liveHandler, so they still re-render fresh on actions.
|
|
487
676
|
if (!ctx.isAction || !isPassthroughPrerenderRoute) {
|
|
488
677
|
await ensurePrerenderDeps();
|
|
489
|
-
if (prerenderStoreInstance) {
|
|
678
|
+
if (prerenderStoreInstance || prerenderOverlay) {
|
|
490
679
|
const served = yield* tryPrerenderLookup(
|
|
491
680
|
ctx,
|
|
492
681
|
state,
|
|
@@ -494,6 +683,8 @@ export function withCacheLookup<TEnv>(
|
|
|
494
683
|
pipelineReqCtx,
|
|
495
684
|
resolveLoadersOnly,
|
|
496
685
|
resolveLoadersOnlyWithRevalidation,
|
|
686
|
+
isPassthroughPrerenderRoute,
|
|
687
|
+
prerenderOverlay,
|
|
497
688
|
);
|
|
498
689
|
if (served) return;
|
|
499
690
|
}
|
|
@@ -518,6 +709,7 @@ export function withCacheLookup<TEnv>(
|
|
|
518
709
|
pipelineReqCtx,
|
|
519
710
|
resolveLoadersOnly,
|
|
520
711
|
resolveLoadersOnlyWithRevalidation,
|
|
712
|
+
isPassthroughPrerenderRoute,
|
|
521
713
|
);
|
|
522
714
|
if (served) return;
|
|
523
715
|
}
|
|
@@ -330,6 +330,8 @@ export interface RouteMatchResult<TEnv = any> {
|
|
|
330
330
|
pr?: true;
|
|
331
331
|
/** Passthrough: handler kept for live fallback on unknown params (from trie) */
|
|
332
332
|
pt?: true;
|
|
333
|
+
/** On-demand prerender eligible (router.prerender() refreshable) */
|
|
334
|
+
od?: true;
|
|
333
335
|
/** Response type for non-RSC routes (json, text, image, any) */
|
|
334
336
|
responseType?: string;
|
|
335
337
|
/** Negotiate variants: response-type routes sharing this path */
|
|
@@ -455,6 +457,9 @@ export function findMatch<TEnv>(
|
|
|
455
457
|
const ptFlag = entry.passthroughRouteKeys?.has(routeKey)
|
|
456
458
|
? { pt: true as const }
|
|
457
459
|
: {};
|
|
460
|
+
const odFlag = entry.onDemandRouteKeys?.has(routeKey)
|
|
461
|
+
? { od: true as const }
|
|
462
|
+
: {};
|
|
458
463
|
|
|
459
464
|
const match = regex.exec(pathname);
|
|
460
465
|
if (match) {
|
|
@@ -484,6 +489,7 @@ export function findMatch<TEnv>(
|
|
|
484
489
|
redirectTo: pathname + "/",
|
|
485
490
|
...prFlag,
|
|
486
491
|
...ptFlag,
|
|
492
|
+
...odFlag,
|
|
487
493
|
};
|
|
488
494
|
} else if (trailingSlashMode === "never" && pathnameHasTrailingSlash) {
|
|
489
495
|
return {
|
|
@@ -493,6 +499,7 @@ export function findMatch<TEnv>(
|
|
|
493
499
|
redirectTo: pathname.slice(0, -1),
|
|
494
500
|
...prFlag,
|
|
495
501
|
...ptFlag,
|
|
502
|
+
...odFlag,
|
|
496
503
|
};
|
|
497
504
|
}
|
|
498
505
|
|
|
@@ -502,6 +509,7 @@ export function findMatch<TEnv>(
|
|
|
502
509
|
params,
|
|
503
510
|
...prFlag,
|
|
504
511
|
...ptFlag,
|
|
512
|
+
...odFlag,
|
|
505
513
|
};
|
|
506
514
|
}
|
|
507
515
|
|
|
@@ -520,6 +528,7 @@ export function findMatch<TEnv>(
|
|
|
520
528
|
params,
|
|
521
529
|
...prFlag,
|
|
522
530
|
...ptFlag,
|
|
531
|
+
...odFlag,
|
|
523
532
|
};
|
|
524
533
|
} else if (trailingSlashMode === "never") {
|
|
525
534
|
if (pathnameHasTrailingSlash) {
|
|
@@ -530,6 +539,7 @@ export function findMatch<TEnv>(
|
|
|
530
539
|
redirectTo: alternatePathname,
|
|
531
540
|
...prFlag,
|
|
532
541
|
...ptFlag,
|
|
542
|
+
...odFlag,
|
|
533
543
|
};
|
|
534
544
|
}
|
|
535
545
|
return {
|
|
@@ -538,6 +548,7 @@ export function findMatch<TEnv>(
|
|
|
538
548
|
params,
|
|
539
549
|
...prFlag,
|
|
540
550
|
...ptFlag,
|
|
551
|
+
...odFlag,
|
|
541
552
|
};
|
|
542
553
|
} else if (trailingSlashMode === "always") {
|
|
543
554
|
if (!pathnameHasTrailingSlash) {
|
|
@@ -548,6 +559,7 @@ export function findMatch<TEnv>(
|
|
|
548
559
|
redirectTo: alternatePathname,
|
|
549
560
|
...prFlag,
|
|
550
561
|
...ptFlag,
|
|
562
|
+
...odFlag,
|
|
551
563
|
};
|
|
552
564
|
}
|
|
553
565
|
return {
|
|
@@ -556,6 +568,7 @@ export function findMatch<TEnv>(
|
|
|
556
568
|
params,
|
|
557
569
|
...prFlag,
|
|
558
570
|
...ptFlag,
|
|
571
|
+
...odFlag,
|
|
559
572
|
};
|
|
560
573
|
} else {
|
|
561
574
|
const canonicalPath = hasTrailingSlash
|
|
@@ -568,6 +581,7 @@ export function findMatch<TEnv>(
|
|
|
568
581
|
redirectTo: canonicalPath,
|
|
569
582
|
...prFlag,
|
|
570
583
|
...ptFlag,
|
|
584
|
+
...odFlag,
|
|
571
585
|
};
|
|
572
586
|
}
|
|
573
587
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve the router-wide default Link prefetch strategy once, at router init.
|
|
3
|
-
* The resolved value ships to the client in payload metadata; the
|
|
4
|
-
*
|
|
5
|
-
* default-strategy.ts) and every `<Link>` without an explicit
|
|
6
|
-
* falls back to it.
|
|
3
|
+
* The resolved value ships to the client in payload metadata; the router
|
|
4
|
+
* store carries it (`NavigationStoreContext.defaultPrefetch`, see
|
|
5
|
+
* browser/prefetch/default-strategy.ts) and every `<Link>` without an explicit
|
|
6
|
+
* `prefetch` prop falls back to it.
|
|
7
7
|
*
|
|
8
8
|
* The type lives HERE (not in browser/react/Link.tsx) because both seats need
|
|
9
9
|
* it: this server-side resolver and the client Link/default-strategy modules.
|