Skip to content

Commit

Permalink
[Segment Cache] Predictable fallback param encoding (#75166)
Browse files Browse the repository at this point in the history
When a segment includes a fallback parameter, during prerendering, a
random placeholder is used to generate the response. We then use this
response to generate a key path for each segment. We need this path to
be predictable so the server can create a rewrite for it; it can't
include random placeholders.

So, this updates the segment generation to replace the placeholder
values with a "template" string, e.g. `[paramName]`.
  • Loading branch information
acdlite authored Jan 22, 2025
1 parent f872b6d commit 8e2bd16
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 9 deletions.
21 changes: 14 additions & 7 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2960,7 +2960,8 @@ async function prerenderToStream(
flightData,
finalRenderPrerenderStore,
ComponentMod,
renderOpts
renderOpts,
fallbackRouteParams
)

if (serverIsDynamic || clientIsDynamic) {
Expand Down Expand Up @@ -3439,7 +3440,8 @@ async function prerenderToStream(
flightData,
finalClientPrerenderStore,
ComponentMod,
renderOpts
renderOpts,
fallbackRouteParams
)

const getServerInsertedHTML = makeGetServerInsertedHTML({
Expand Down Expand Up @@ -3571,7 +3573,8 @@ async function prerenderToStream(
flightData,
ssrPrerenderStore,
ComponentMod,
renderOpts
renderOpts,
fallbackRouteParams
)
}

Expand Down Expand Up @@ -3763,7 +3766,8 @@ async function prerenderToStream(
flightData,
prerenderLegacyStore,
ComponentMod,
renderOpts
renderOpts,
fallbackRouteParams
)
}

Expand Down Expand Up @@ -3927,7 +3931,8 @@ async function prerenderToStream(
flightData,
prerenderLegacyStore,
ComponentMod,
renderOpts
renderOpts,
fallbackRouteParams
)
}

Expand Down Expand Up @@ -4068,7 +4073,8 @@ async function collectSegmentData(
fullPageDataBuffer: Buffer,
prerenderStore: PrerenderStore,
ComponentMod: AppPageModule,
renderOpts: RenderOpts
renderOpts: RenderOpts,
fallbackRouteParams: FallbackRouteParams | null
): Promise<Map<string, Buffer> | undefined> {
// Per-segment prefetch data
//
Expand Down Expand Up @@ -4126,6 +4132,7 @@ async function collectSegmentData(
fullPageDataBuffer,
staleTime,
clientReferenceManifest.clientModules as ManifestNode,
serverConsumerManifest
serverConsumerManifest,
fallbackRouteParams
)
}
49 changes: 47 additions & 2 deletions packages/next/src/server/app-render/collect-segment-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
FlightRouterState,
InitialRSCPayload,
Segment as FlightRouterStateSegment,
DynamicParamTypesShort,
} from './types'
import type { ManifestNode } from '../../build/webpack/plugins/flight-manifest-plugin'

Expand All @@ -24,8 +25,10 @@ import {
encodeChildSegmentKey,
encodeSegment,
ROOT_SEGMENT_KEY,
type EncodedSegment,
} from './segment-value-encoding'
import { getDigestForWellKnownError } from './create-error-handler'
import type { FallbackRouteParams } from '../request/fallback-params'

// Contains metadata about the route tree. The client must fetch this before
// it can fetch any actual segment data.
Expand Down Expand Up @@ -75,7 +78,8 @@ export async function collectSegmentData(
fullPageDataBuffer: Buffer,
staleTime: number,
clientModules: ManifestNode,
serverConsumerManifest: any
serverConsumerManifest: any,
fallbackRouteParams: FallbackRouteParams | null
): Promise<Map<string, Buffer>> {
// Traverse the router tree and generate a prefetch response for each segment.

Expand Down Expand Up @@ -117,6 +121,7 @@ export async function collectSegmentData(
<PrefetchTreeData
shouldAssumePartialData={shouldAssumePartialData}
fullPageDataBuffer={fullPageDataBuffer}
fallbackRouteParams={fallbackRouteParams}
serverConsumerManifest={serverConsumerManifest}
clientModules={clientModules}
staleTime={staleTime}
Expand Down Expand Up @@ -147,6 +152,7 @@ export async function collectSegmentData(
async function PrefetchTreeData({
shouldAssumePartialData,
fullPageDataBuffer,
fallbackRouteParams,
serverConsumerManifest,
clientModules,
staleTime,
Expand All @@ -156,6 +162,7 @@ async function PrefetchTreeData({
shouldAssumePartialData: boolean
fullPageDataBuffer: Buffer
serverConsumerManifest: any
fallbackRouteParams: FallbackRouteParams | null
clientModules: ManifestNode
staleTime: number
segmentTasks: Array<Promise<[string, Buffer]>>
Expand Down Expand Up @@ -196,6 +203,7 @@ async function PrefetchTreeData({
flightRouterState,
buildId,
seedData,
fallbackRouteParams,
fullPageDataBuffer,
clientModules,
serverConsumerManifest,
Expand Down Expand Up @@ -227,6 +235,7 @@ function collectSegmentDataImpl(
route: FlightRouterState,
buildId: string,
seedData: CacheNodeSeedData | null,
fallbackRouteParams: FallbackRouteParams | null,
fullPageDataBuffer: Buffer,
clientModules: ManifestNode,
serverConsumerManifest: any,
Expand All @@ -244,16 +253,23 @@ function collectSegmentDataImpl(
const childSegment = childRoute[0]
const childSeedData =
seedDataChildren !== null ? seedDataChildren[parallelRouteKey] : null

const childKey = encodeChildSegmentKey(
key,
parallelRouteKey,
encodeSegment(childSegment)
Array.isArray(childSegment) && fallbackRouteParams !== null
? encodeSegmentWithPossibleFallbackParam(
childSegment,
fallbackRouteParams
)
: encodeSegment(childSegment)
)
const childTree = collectSegmentDataImpl(
shouldAssumePartialData,
childRoute,
buildId,
childSeedData,
fallbackRouteParams,
fullPageDataBuffer,
clientModules,
serverConsumerManifest,
Expand Down Expand Up @@ -298,6 +314,35 @@ function collectSegmentDataImpl(
}
}

function encodeSegmentWithPossibleFallbackParam(
segment: [string, string, DynamicParamTypesShort],
fallbackRouteParams: FallbackRouteParams
): EncodedSegment {
const name = segment[0]
if (!fallbackRouteParams.has(name)) {
// Normal case. No matching fallback parameter.
return encodeSegment(segment)
}
// This segment includes a fallback parameter. During prerendering, a random
// placeholder value was used; however, for segment prefetches, we need the
// segment path to be predictable so the server can create a rewrite for it.
// So, replace the placeholder segment value with a "template" string,
// e.g. `[name]`.
// TODO: This will become a bit cleaner once remove route parameters from the
// server response, and instead add them to the segment keys on the client.
// Instead of a string replacement, like we do here, route params will always
// be encoded in separate step from the rest of the segment, not just in the
// case of fallback params.
const encodedSegment = encodeSegment(segment)
const lastIndex = encodedSegment.lastIndexOf('$')
const encodedFallbackSegment =
// NOTE: This is guaranteed not to clash with the rest of the segment
// because non-simple characters (including [ and ]) trigger a base
// 64 encoding.
encodedSegment.substring(0, lastIndex + 1) + `[${name}]`
return encodedFallbackSegment as EncodedSegment
}

async function renderSegmentPrefetch(
shouldAssumePartialData: boolean,
buildId: string,
Expand Down

0 comments on commit 8e2bd16

Please sign in to comment.