Skip to content

Commit

Permalink
Export segment prefetch data during build
Browse files Browse the repository at this point in the history
Implements exporting segment prefetch data during build.

Although the goal is to support individual segments being requested by
the client, all of the segments for a page are generated simultaneously,
including during revalidations. This is to ensure consistency, because
it's possible for a mismatch between a layout and page segment can
cause the client to error during rendering. We want to preserve the
ability of the client to recover from such a mismatch by re-requesting
all the segments to get a consistent view of the page.

The segments data is generated during the same phase that we currently
generate prefetch data for the entire page.

In this commit, I have not yet implemented the actual segment
generation; I'm using placeholder data for now. I've only implemented
the plumbing of the data through the build process. I will implement
the segment generation in a separate PR.
  • Loading branch information
acdlite committed Oct 11, 2024
1 parent 9acefb8 commit 1c6fb80
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 0 deletions.
35 changes: 35 additions & 0 deletions packages/next/src/export/routes/app-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
NEXT_META_SUFFIX,
RSC_PREFETCH_SUFFIX,
RSC_SUFFIX,
RSC_SEGMENTS_DIR_SUFFIX,
RSC_SEGMENT_SUFFIX,
} from '../../lib/constants'
import { hasNextSupport } from '../../server/ci-info'
import { lazyRenderAppPage } from '../../server/route-modules/app-page/module.render'
Expand All @@ -28,6 +30,7 @@ export const enum ExportedAppPageFiles {
HTML = 'HTML',
FLIGHT = 'FLIGHT',
PREFETCH_FLIGHT = 'PREFETCH_FLIGHT',
PREFETCH_FLIGHT_SEGMENT = 'PREFETCH_FLIGHT_SEGMENT',
META = 'META',
POSTPONED = 'POSTPONED',
}
Expand Down Expand Up @@ -76,6 +79,7 @@ export async function exportAppPage(
postponed,
fetchTags,
fetchMetrics,
segmentFlightData,
} = metadata

// Ensure we don't postpone without having PPR enabled.
Expand Down Expand Up @@ -112,6 +116,7 @@ export async function exportAppPage(
throw new Error(`Invariant: failed to get page data for ${path}`)
}

let segmentPaths
if (flightData) {
// If PPR is enabled, we want to emit a prefetch rsc file for the page
// instead of the standard rsc. This is because the standard rsc will
Expand All @@ -120,11 +125,40 @@ export async function exportAppPage(
if (renderOpts.experimental.isRoutePPREnabled) {
// If PPR is enabled, we should emit the flight data as the prefetch
// payload.
// TODO: This will eventually be replaced by the per-segment prefetch
// output below.
await fileWriter(
ExportedAppPageFiles.PREFETCH_FLIGHT,
htmlFilepath.replace(/\.html$/, RSC_PREFETCH_SUFFIX),
flightData
)

if (segmentFlightData) {
// Emit the per-segment prefetch data. We emit them as separate files
// so that the cache handler has the option to treat each as a
// separate entry.
segmentPaths = []
const segmentsDir = htmlFilepath.replace(
/\.html$/,
RSC_SEGMENTS_DIR_SUFFIX
)
const tasks = []
for (const [segmentPath, buffer] of segmentFlightData.entries()) {
segmentPaths.push(segmentPath)
const segmentDataFilePath =
segmentPath === '/'
? segmentsDir + '/_index' + RSC_SEGMENT_SUFFIX
: segmentsDir + segmentPath + RSC_SEGMENT_SUFFIX
tasks.push(
fileWriter(
ExportedAppPageFiles.PREFETCH_FLIGHT_SEGMENT,
segmentDataFilePath,
buffer
)
)
}
await Promise.all(tasks)
}
} else {
// Writing the RSC payload to a file if we don't have PPR enabled.
await fileWriter(
Expand Down Expand Up @@ -175,6 +209,7 @@ export async function exportAppPage(
status,
headers,
postponed,
segmentPaths,
}

await fileWriter(
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/export/routes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export type RouteMetadata = {
status: number | undefined
headers: OutgoingHttpHeaders | undefined
postponed: string | undefined
segmentPaths: Array<string> | undefined
}
2 changes: 2 additions & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER =
'x-prerender-revalidate-if-generated'

export const RSC_PREFETCH_SUFFIX = '.prefetch.rsc'
export const RSC_SEGMENTS_DIR_SUFFIX = '.segments'
export const RSC_SEGMENT_SUFFIX = '.segment.rsc'
export const RSC_SUFFIX = '.rsc'
export const ACTION_SUFFIX = '.action'
export const NEXT_DATA_SUFFIX = '.json'
Expand Down
26 changes: 26 additions & 0 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,32 @@ async function renderToHTMLOrFlightImpl(
}
}

// Per-segment prefetch data
//
// All of the segments for a page are generated simultaneously, including
// during revalidations. This is to ensure consistency, because it's
// possible for a mismatch between a layout and page segment can cause the
// client to error during rendering. We want to preserve the ability of the
// client to recover from such a mismatch by re-requesting all the segments
// to get a consistent view of the page.
//
// TODO (Per Segment Prefetching): This is placeholder data. Populate with
// the actual data generated during prerender.
if (renderOpts.experimental.isRoutePPREnabled === true) {
const placeholder = Buffer.from(
'TODO (Per Segment Prefetching): Not yet implemented\n'
)
metadata.segmentFlightData = new Map([
// Root segment
['/', placeholder],
['/blog', placeholder],
// TODO: Update the client to use the same encoding for segment paths that
// we use here, so we don't have to convert between them. Needs to be
// filesystem safe.
['/blog/[post]-1-d', placeholder],
])
}

return new RenderResult(await streamToString(response.stream), options)
} else {
// We're rendering dynamically
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/server/render-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export type AppPageRenderResultMetadata = {
headers?: OutgoingHttpHeaders
fetchTags?: string
fetchMetrics?: FetchMetrics

segmentFlightData?: Map<string, Buffer>
}

export type PagesRenderResultMetadata = {
Expand Down

0 comments on commit 1c6fb80

Please sign in to comment.