Skip to content

Commit

Permalink
[Segment Cache] Add isHeadPartial
Browse files Browse the repository at this point in the history
Similar to the previous PR, but for the head, which is delivered
separately from the segments. We can only skip the dynamic request if
this value is `false`.
  • Loading branch information
acdlite committed Dec 6, 2024
1 parent 75f0518 commit bd794af
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const getFlightData = (): FlightData => {
<>
<title>About page!</title>
</>,
false,
],
]
}
Expand All @@ -52,7 +53,8 @@ describe('applyRouterStatePatchToTree', () => {

// Mirrors the way router-reducer values are passed in.
const flightDataPath = flightData[0]
const [treePatch /*, cacheNodeSeedData, head*/] = flightDataPath.slice(-3)
const [treePatch /*, cacheNodeSeedData, head, isHeadPartial*/] =
flightDataPath.slice(-4)
const flightSegmentPath = flightDataPath.slice(0, -4)

const newRouterStateTree = applyRouterStatePatchToTree(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const getFlightData = (): NormalizedFlightData[] => {
tree: ['about', { children: ['', {}] }],
seedData: ['about', <h1>SubTreeData Injected!</h1>, {}, null, false],
head: '<title>Head Injected!</title>',
isHeadPartial: false,
isRootRender: false,
},
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ describe('fillLazyItemsTillLeafWithHead', () => {

// Mirrors the way router-reducer values are passed in.
const flightDataPath = flightData[0]
const [treePatch, cacheNodeSeedData, head] = flightDataPath.slice(-3)
const [treePatch, cacheNodeSeedData, head /*, isHeadPartial */] =
flightDataPath.slice(-4)
fillLazyItemsTillLeafWithHead(
cache,
existingCache,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const getFlightData = (): NormalizedFlightData[] => {
tree: ['about', { children: ['', {}] }],
seedData: ['about', <h1>About Page!</h1>, {}, null, false],
head: '<title>About page!</title>',
isHeadPartial: false,
isRootRender: false,
},
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('shouldHardNavigate', () => {

// Mirrors the way router-reducer values are passed in.
const flightDataPath = flightData[0]
const flightSegmentPath = flightDataPath.slice(0, -3)
const flightSegmentPath = flightDataPath.slice(0, -4)

const result = shouldHardNavigate(
['', ...flightSegmentPath],
Expand Down Expand Up @@ -107,7 +107,7 @@ describe('shouldHardNavigate', () => {

// Mirrors the way router-reducer values are passed in.
const flightDataPath = flightData[0]
const flightSegmentPath = flightDataPath.slice(0, -3)
const flightSegmentPath = flightDataPath.slice(0, -4)

const result = shouldHardNavigate(
['', ...flightSegmentPath],
Expand Down Expand Up @@ -153,6 +153,7 @@ describe('shouldHardNavigate', () => {
],
[['id', '123', 'd'], {}, null],
null,
false,
],
]
}
Expand All @@ -164,7 +165,7 @@ describe('shouldHardNavigate', () => {

// Mirrors the way router-reducer values are passed in.
const flightDataPath = flightData[0]
const flightSegmentPath = flightDataPath.slice(0, -3)
const flightSegmentPath = flightDataPath.slice(0, -4)

const result = shouldHardNavigate(
['', ...flightSegmentPath],
Expand Down
7 changes: 7 additions & 0 deletions packages/next/src/client/components/segment-cache/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type PendingRouteCacheEntry = RouteCacheEntryShared & {
canonicalUrl: null
tree: null
head: null
isHeadPartial: true
}

type RejectedRouteCacheEntry = RouteCacheEntryShared & {
Expand All @@ -88,6 +89,7 @@ type RejectedRouteCacheEntry = RouteCacheEntryShared & {
canonicalUrl: null
tree: null
head: null
isHeadPartial: true
}

export type FulfilledRouteCacheEntry = RouteCacheEntryShared & {
Expand All @@ -96,6 +98,7 @@ export type FulfilledRouteCacheEntry = RouteCacheEntryShared & {
canonicalUrl: string
tree: TreePrefetch
head: React.ReactNode | null
isHeadPartial: boolean
}

export type RouteCacheEntry =
Expand Down Expand Up @@ -281,6 +284,7 @@ export function requestRouteCacheEntryFromCache(
blockedTasks: null,
tree: null,
head: null,
isHeadPartial: true,
// If the request takes longer than a minute, a subsequent request should
// retry instead of waiting for this one.
//
Expand Down Expand Up @@ -420,6 +424,7 @@ function fulfillRouteCacheEntry(
entry: PendingRouteCacheEntry,
tree: TreePrefetch,
head: React.ReactNode,
isHeadPartial: boolean,
staleAt: number,
couldBeIntercepted: boolean,
canonicalUrl: string
Expand All @@ -428,6 +433,7 @@ function fulfillRouteCacheEntry(
fulfilledEntry.status = EntryStatus.Fulfilled
fulfilledEntry.tree = tree
fulfilledEntry.head = head
fulfilledEntry.isHeadPartial = isHeadPartial
fulfilledEntry.staleAt = staleAt
fulfilledEntry.couldBeIntercepted = couldBeIntercepted
fulfilledEntry.canonicalUrl = canonicalUrl
Expand Down Expand Up @@ -532,6 +538,7 @@ async function fetchRouteOnCacheMiss(
entry,
serverData.tree,
serverData.head,
serverData.isHeadPartial,
Date.now() + serverData.staleTime,
couldBeIntercepted,
canonicalUrl
Expand Down
8 changes: 5 additions & 3 deletions packages/next/src/client/flight-data-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type NormalizedFlightData = {
tree: FlightRouterState
seedData: CacheNodeSeedData | null
head: React.ReactNode | null
isHeadPartial: boolean
isRootRender: boolean
}

Expand All @@ -31,9 +32,9 @@ export function getFlightDataPartsFromPath(
flightDataPath: FlightDataPath
): NormalizedFlightData {
// tree, seedData, and head are *always* the last three items in the `FlightDataPath`.
const [tree, seedData, head] = flightDataPath.slice(-3)
const [tree, seedData, head, isHeadPartial] = flightDataPath.slice(-4)
// The `FlightSegmentPath` is everything except the last three items. For a root render, it won't be present.
const segmentPath = flightDataPath.slice(0, -3)
const segmentPath = flightDataPath.slice(0, -4)

return {
// TODO: Unify these two segment path helpers. We are inconsistently pushing an empty segment ("")
Expand All @@ -47,7 +48,8 @@ export function getFlightDataPartsFromPath(
tree,
seedData,
head,
isRootRender: flightDataPath.length === 3,
isHeadPartial,
isRootRender: flightDataPath.length === 4,
}
}

Expand Down
33 changes: 31 additions & 2 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
RSCPayload,
FlightData,
InitialRSCPayload,
FlightDataPath,
} from './types'
import {
workAsyncStorage,
Expand Down Expand Up @@ -780,14 +781,31 @@ async function getRSCPayload(

const globalErrorStyles = await getGlobalErrorStyles(tree, ctx)

// Assume the head we're rendering contains only partial data if PPR is
// enabled and this is a statically generated response. This is used by the
// client Segment Cache after a prefetch to determine if it can skip the
// second request to fill in the dynamic data.
//
// See similar comment in create-component-tree.tsx for more context.
const isPossiblyPartialHead =
workStore.isStaticGeneration &&
ctx.renderOpts.experimental.isRoutePPREnabled === true

return {
// See the comment above the `Preloads` component (below) for why this is part of the payload
P: <Preloads preloadCallbacks={preloadCallbacks} />,
b: ctx.renderOpts.buildId,
p: ctx.assetPrefix,
c: prepareInitialCanonicalUrl(url),
i: !!couldBeIntercepted,
f: [[initialTree, seedData, initialHead]],
f: [
[
initialTree,
seedData,
initialHead,
isPossiblyPartialHead,
] as FlightDataPath,
],
m: missingSlots,
G: [GlobalError, globalErrorStyles],
s: typeof ctx.renderOpts.postponed === 'string',
Expand Down Expand Up @@ -877,13 +895,24 @@ async function getErrorRSCPayload(

const globalErrorStyles = await getGlobalErrorStyles(tree, ctx)

const isPossiblyPartialHead =
workStore.isStaticGeneration &&
ctx.renderOpts.experimental.isRoutePPREnabled === true

return {
b: ctx.renderOpts.buildId,
p: ctx.assetPrefix,
c: prepareInitialCanonicalUrl(url),
m: undefined,
i: false,
f: [[initialTree, initialSeedData, initialHead]],
f: [
[
initialTree,
initialSeedData,
initialHead,
isPossiblyPartialHead,
] as FlightDataPath,
],
G: [GlobalError, globalErrorStyles],
s: typeof ctx.renderOpts.postponed === 'string',
S: workStore.isStaticGeneration,
Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/server/app-render/collect-segment-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type RootTreePrefetch = {
buildId: string
tree: TreePrefetch
head: React.ReactNode | null
isHeadPartial: boolean
staleTime: number
}

Expand Down Expand Up @@ -194,6 +195,8 @@ async function PrefetchTreeData({
segmentTasks
)

const isHeadPartial = await isPartialRSCData(head, clientModules)

// Notify the abort controller that we're done processing the route tree.
// Anything async that happens after this point must be due to hanging
// promises in the original stream.
Expand All @@ -204,6 +207,7 @@ async function PrefetchTreeData({
buildId,
tree,
head,
isHeadPartial,
staleTime,
}
return treePrefetch
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/app-render/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export type FlightDataSegment = [
/* treePatch */ FlightRouterState,
/* cacheNodeSeedData */ CacheNodeSeedData | null, // Can be null during prefetch if there's no loading component
/* head */ React.ReactNode | null,
/* isHeadPartial */ boolean,
]

export type FlightDataPath =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,11 @@ export async function walkTreeWithFlightRouterState({
overriddenSegment,
routerState,
null,
// TODO: It's possible that all the segment data was prefetched during
// a navigation, but the head was not. Should we send it down
// here anyway?
null,
false,
] satisfies FlightDataSegment,
]
} else {
Expand Down Expand Up @@ -170,6 +174,7 @@ export async function walkTreeWithFlightRouterState({
routerState,
seedData,
rscPayloadHead,
false,
] satisfies FlightDataSegment,
]
}
Expand Down

0 comments on commit bd794af

Please sign in to comment.