Skip to content

Commit 1142752

Browse files
authored
fix: support segment data in cache entries (#3093)
1 parent bfed78e commit 1142752

File tree

3 files changed

+52
-6
lines changed

3 files changed

+52
-6
lines changed

src/build/content/prerendered.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { join } from 'node:path'
55
import { trace } from '@opentelemetry/api'
66
import { wrapTracer } from '@opentelemetry/api/experimental'
77
import { glob } from 'fast-glob'
8+
import type { RouteMetadata } from 'next-with-cache-handler-v2/dist/export/routes/types.js'
89
import pLimit from 'p-limit'
910
import { satisfies } from 'semver'
1011

@@ -69,21 +70,44 @@ const buildPagesCacheValue = async (
6970
revalidate: initialRevalidateSeconds,
7071
})
7172

73+
const RSC_SEGMENTS_DIR_SUFFIX = '.segments'
74+
const RSC_SEGMENT_SUFFIX = '.segment.rsc'
75+
7276
const buildAppCacheValue = async (
7377
path: string,
7478
shouldUseAppPageKind: boolean,
7579
): Promise<NetlifyCachedAppPageValue | NetlifyCachedPageValue> => {
76-
const meta = JSON.parse(await readFile(`${path}.meta`, 'utf-8'))
80+
const meta = JSON.parse(await readFile(`${path}.meta`, 'utf-8')) as RouteMetadata
7781
const html = await readFile(`${path}.html`, 'utf-8')
7882

7983
// supporting both old and new cache kind for App Router pages - https://github.com/vercel/next.js/pull/65988
8084
if (shouldUseAppPageKind) {
85+
// segments are normalized and outputted separately for each segment, we denormalize it here and stitch
86+
// fully constructed segmentData to avoid data fetch waterfalls later in cache handler at runtime
87+
// https://github.com/vercel/next.js/blob/def2c6ba75dff754767379afb44c26c30bd3d96b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts#L185
88+
let segmentData: NetlifyCachedAppPageValue['segmentData']
89+
if (meta.segmentPaths) {
90+
const segmentsDir = path + RSC_SEGMENTS_DIR_SUFFIX
91+
92+
segmentData = Object.fromEntries(
93+
await Promise.all(
94+
meta.segmentPaths.map(async (segmentPath: string) => {
95+
const segmentDataFilePath = segmentsDir + segmentPath + RSC_SEGMENT_SUFFIX
96+
97+
const segmentContent = await readFile(segmentDataFilePath, 'base64')
98+
return [segmentPath, segmentContent]
99+
}),
100+
),
101+
)
102+
}
103+
81104
return {
82105
kind: 'APP_PAGE',
83106
html,
84107
rscData: await readFile(`${path}.rsc`, 'base64').catch(() =>
85108
readFile(`${path}.prefetch.rsc`, 'base64'),
86109
),
110+
segmentData,
87111
...meta,
88112
}
89113
}
@@ -97,7 +121,10 @@ const buildAppCacheValue = async (
97121
if (
98122
!meta.status &&
99123
rsc.includes('NEXT_NOT_FOUND') &&
100-
!meta.headers['x-next-cache-tags'].includes('/@')
124+
!(
125+
typeof meta.headers?.['x-next-cache-tags'] === 'string' &&
126+
meta.headers?.['x-next-cache-tags'].includes('/@')
127+
)
101128
) {
102129
meta.status = 404
103130
}

src/run/handlers/cache.cts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ export class NetlifyCacheHandler implements CacheHandlerForMultipleVersions {
364364
requestContext.isCacheableAppPage = true
365365
}
366366

367-
const { revalidate, rscData, ...restOfPageValue } = blob.value
367+
const { revalidate, rscData, segmentData, ...restOfPageValue } = blob.value
368368

369369
span.addEvent(blob.value?.kind, { lastModified: blob.lastModified, revalidate, ttl })
370370

@@ -375,6 +375,14 @@ export class NetlifyCacheHandler implements CacheHandlerForMultipleVersions {
375375
value: {
376376
...restOfPageValue,
377377
rscData: rscData ? Buffer.from(rscData, 'base64') : undefined,
378+
segmentData: segmentData
379+
? new Map(
380+
Object.entries(segmentData).map(([segmentPath, base64EncodedSegment]) => [
381+
segmentPath,
382+
Buffer.from(base64EncodedSegment, 'base64'),
383+
]),
384+
)
385+
: undefined,
378386
},
379387
}
380388
}
@@ -416,6 +424,14 @@ export class NetlifyCacheHandler implements CacheHandlerForMultipleVersions {
416424
revalidate: context.revalidate ?? context.cacheControl?.revalidate,
417425
cacheControl: context.cacheControl,
418426
rscData: data.rscData?.toString('base64'),
427+
segmentData: data.segmentData
428+
? Object.fromEntries(
429+
[...data.segmentData.entries()].map(([segmentPath, base64EncodedSegment]) => [
430+
segmentPath,
431+
base64EncodedSegment.toString('base64'),
432+
]),
433+
)
434+
: undefined,
419435
}
420436
}
421437

src/shared/cache-types.cts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
IncrementalCachedPageValue,
1010
IncrementalCacheValue,
1111
} from 'next/dist/server/response-cache/types.js'
12+
import type { IncrementalCachedAppPageValue as IncrementalCachedAppPageValueForNewVersions } from 'next-with-cache-handler-v2/dist/server/response-cache/types.js'
1213

1314
export type { CacheHandlerContext } from 'next/dist/server/lib/incremental-cache/index.js'
1415

@@ -44,17 +45,19 @@ type IncrementalCachedAppPageValueForMultipleVersions = Omit<
4445
'kind'
4546
> & {
4647
kind: 'APP_PAGE'
47-
}
48+
} & Pick<IncrementalCachedAppPageValueForNewVersions, 'segmentData'>
4849

4950
/**
5051
* Used for storing in blobs and reading from blobs
5152
*/
5253
export type NetlifyCachedAppPageValue = Omit<
5354
IncrementalCachedAppPageValueForMultipleVersions,
54-
'rscData'
55+
'rscData' | 'segmentData'
5556
> & {
56-
// Next.js stores rscData as buffer, while we store it as base64 encoded string
57+
// Next.js stores rscData as Buffer, while we store it as base64 encoded string
5758
rscData: string | undefined
59+
// Next.js stores segmentData as Map<string, Buffer>, while we store it as Record<string, string>, where value is base64 encoded string
60+
segmentData: Record<string, string> | undefined
5861
revalidate?: Parameters<CacheHandler['set']>[2]['revalidate']
5962
cacheControl?: CacheControl
6063
}

0 commit comments

Comments
 (0)