Skip to content

Commit 65db12f

Browse files
authored
[Segment Cache] Support <Link prefetch={true}> (vercel#74172)
This implements support in the Segment Cache for "full" link prefetches, typically initiated by passing `prefetch={true}` to a `<Link>`. The goal of a full prefetch is to request all the data needed for a navigation, both static and dynamic, so that once the navigation occurs, the router does not need to fetch any additional data. So, a full prefetch implicitly instructs the client cache to treat the response as static, even the dynamic data. The implementation is largely the same as what we do to support non-PPR-enabled routes — a request tree is sent to the server describing which segments are already cached and which ones need to be rendered. While constructing the request tree, if all the segments are already in the client cache, the request can be skipped entirely. The main difference is that a full prefetch will only reuse a cached segment entry if it does not contain any dynamic holes.
1 parent 4c09fa5 commit 65db12f

File tree

6 files changed

+498
-79
lines changed

6 files changed

+498
-79
lines changed

packages/next/src/client/components/app-router.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,12 @@ function Router({
279279
? // Unlike the old implementation, the Segment Cache doesn't store its
280280
// data in the router reducer state; it writes into a global mutable
281281
// cache. So we don't need to dispatch an action.
282-
(href) =>
282+
(href, options) =>
283283
prefetchWithSegmentCache(
284284
href,
285285
actionQueue.state.nextUrl,
286-
actionQueue.state.tree
286+
actionQueue.state.tree,
287+
options?.kind === PrefetchKind.FULL
287288
)
288289
: (href, options) => {
289290
// Use the old prefetch implementation.

packages/next/src/client/components/segment-cache/cache.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export type RouteCacheEntry =
147147

148148
export const enum FetchStrategy {
149149
PPR,
150+
Full,
150151
LoadingBoundary,
151152
}
152153

@@ -1005,24 +1006,31 @@ export async function fetchSegmentOnCacheMiss(
10051006
}
10061007
}
10071008

1008-
export async function fetchSegmentPrefetchesForPPRDisabledRoute(
1009+
export async function fetchSegmentPrefetchesUsingDynamicRequest(
10091010
task: PrefetchTask,
10101011
route: FulfilledRouteCacheEntry,
1012+
fetchStrategy: FetchStrategy,
10111013
dynamicRequestTree: FlightRouterState,
10121014
spawnedEntries: Map<string, PendingSegmentCacheEntry>
10131015
): Promise<PrefetchSubtaskResult<null> | null> {
10141016
const href = task.key.href
10151017
const nextUrl = task.key.nextUrl
10161018
const headers: RequestHeaders = {
10171019
[RSC_HEADER]: '1',
1018-
[NEXT_ROUTER_PREFETCH_HEADER]: '1',
10191020
[NEXT_ROUTER_STATE_TREE_HEADER]: encodeURIComponent(
10201021
JSON.stringify(dynamicRequestTree)
10211022
),
10221023
}
10231024
if (nextUrl !== null) {
10241025
headers[NEXT_URL] = nextUrl
10251026
}
1027+
// Only set the prefetch header if we're not doing a "full" prefetch. We
1028+
// omit the prefetch header from a full prefetch because it's essentially
1029+
// just a navigation request that happens ahead of time — it should include
1030+
// all the same data in the response.
1031+
if (fetchStrategy !== FetchStrategy.Full) {
1032+
headers[NEXT_ROUTER_PREFETCH_HEADER] = '1'
1033+
}
10261034
try {
10271035
const response = await fetchPrefetchResponse(href, headers)
10281036
if (!response || !response.ok || !response.body) {

packages/next/src/client/components/segment-cache/prefetch.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,24 @@ import { schedulePrefetchTask } from './scheduler'
77
* Entrypoint for prefetching a URL into the Segment Cache.
88
* @param href - The URL to prefetch. Typically this will come from a <Link>,
99
* or router.prefetch. It must be validated before we attempt to prefetch it.
10+
* @param nextUrl - A special header used by the server for interception routes.
11+
* Roughly corresponds to the current URL.
12+
* @param treeAtTimeOfPrefetch - The FlightRouterState at the time the prefetch
13+
* was requested. This is only used when PPR is disabled.
14+
* @param includeDynamicData - Whether to prefetch dynamic data, in addition to
15+
* static data. This is used by <Link prefetch={true}>.
1016
*/
1117
export function prefetch(
1218
href: string,
1319
nextUrl: string | null,
14-
treeAtTimeOfPrefetch: FlightRouterState
20+
treeAtTimeOfPrefetch: FlightRouterState,
21+
includeDynamicData: boolean
1522
) {
1623
const url = createPrefetchURL(href)
1724
if (url === null) {
1825
// This href should not be prefetched.
1926
return
2027
}
2128
const cacheKey = createCacheKey(url.href, nextUrl)
22-
schedulePrefetchTask(cacheKey, treeAtTimeOfPrefetch)
29+
schedulePrefetchTask(cacheKey, treeAtTimeOfPrefetch, includeDynamicData)
2330
}

0 commit comments

Comments
 (0)