Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Segment Cache] Send <head> during route prefetch #72890

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions packages/next/src/server/app-render/collect-segment-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { LoadingModuleData } from '../../shared/lib/app-router-context.shar
// it can fetch any actual segment data.
type RootTreePrefetch = {
tree: TreePrefetch
head: React.ReactNode | null
staleTime: number
}

Expand Down Expand Up @@ -81,6 +82,16 @@ export async function collectSegmentData(
await waitAtLeastOneReactRenderTask()
} catch {}

// Create an abort controller that we'll use to stop the stream.
const abortController = new AbortController()
const onCompletedProcessingRouteTree = async () => {
// Since all we're doing is decoding and re-encoding a cached prerender, if
// serializing the stream takes longer than a microtask, it must because of
// hanging promises caused by dynamic data.
await waitAtLeastOneReactRenderTask()
abortController.abort()
}

// Generate a stream for the route tree prefetch. While we're walking the
// tree, we'll also spawn additional tasks to generate the segment prefetches.
// The promises for these tasks are pushed to a mutable array that we will
Expand All @@ -97,13 +108,11 @@ export async function collectSegmentData(
clientModules={clientModules}
staleTime={staleTime}
segmentTasks={segmentTasks}
onCompletedProcessingRouteTree={onCompletedProcessingRouteTree}
/>,
clientModules,
{
// Unlike when rendering the segment streams, we do not pass an abort
// controller here. There's nothing dynamic in the prefetch metadata; we
// will always render the result. We do still have to account for hanging
// promises, but we use a different strategy. See PrefetchTreeData.
signal: abortController.signal,
onError() {
// Ignore any errors. These would have already been reported when
// we created the full page data.
Expand Down Expand Up @@ -131,12 +140,14 @@ async function PrefetchTreeData({
clientModules,
staleTime,
segmentTasks,
onCompletedProcessingRouteTree,
}: {
fullPageDataBuffer: Buffer
serverConsumerManifest: any
clientModules: ManifestNode
staleTime: number
segmentTasks: Array<Promise<[string, Buffer]>>
onCompletedProcessingRouteTree: () => void
}): Promise<RootTreePrefetch | null> {
// We're currently rendering a Flight response for the route tree prefetch.
// Inside this component, decode the Flight stream for the whole page. This is
Expand All @@ -161,6 +172,7 @@ async function PrefetchTreeData({
}
const flightRouterState: FlightRouterState = flightDataPaths[0][0]
const seedData: CacheNodeSeedData = flightDataPaths[0][1]
const head: React.ReactNode | null = flightDataPaths[0][2]

// Compute the route metadata tree by traversing the FlightRouterState. As we
// walk the tree, we will also spawn a task to produce a prefetch response for
Expand All @@ -176,9 +188,15 @@ async function PrefetchTreeData({
segmentTasks
)

// 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.
onCompletedProcessingRouteTree()

// Render the route tree to a special `/_tree` segment.
const treePrefetch: RootTreePrefetch = {
tree,
head,
staleTime,
}
return treePrefetch
Expand Down
Loading