Skip to content

Commit 83f685e

Browse files
piehserhalp
andauthored
fix: update cache handler to accommodate changes in next@canary (#2572)
* update react version needed by next@canary * tmp: just checking if canary tests will be happier * what's up with NODE_ENV? * missing PAGES * chore: use npm info to figure out react version needed for canary instead of hardcoding it * chore: drop package lock file when preparing fixtures * test: unset more things related to next's fetch patching * use correct cache kind for initial cache seeding depending on next version * small cleanup * typehell * just checking * proper NODE_ENV setting with explanation * any is bad * Update src/shared/cache-types.cts Co-authored-by: Philippe Serhal <[email protected]> * add note about deleting next-patch symbol --------- Co-authored-by: Philippe Serhal <[email protected]>
1 parent 4a53512 commit 83f685e

File tree

7 files changed

+194
-60
lines changed

7 files changed

+194
-60
lines changed

src/build/content/prerendered.ts

+24-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { satisfies } from 'semver'
1010

1111
import { encodeBlobKey } from '../../shared/blobkey.js'
1212
import type {
13-
CachedFetchValue,
13+
CachedFetchValueForMultipleVersions,
1414
NetlifyCachedAppPageValue,
1515
NetlifyCachedPageValue,
1616
NetlifyCachedRouteValue,
@@ -45,8 +45,11 @@ const writeCacheEntry = async (
4545
*/
4646
const routeToFilePath = (path: string) => (path === '/' ? '/index' : path)
4747

48-
const buildPagesCacheValue = async (path: string): Promise<NetlifyCachedPageValue> => ({
49-
kind: 'PAGE',
48+
const buildPagesCacheValue = async (
49+
path: string,
50+
shouldUseEnumKind: boolean,
51+
): Promise<NetlifyCachedPageValue> => ({
52+
kind: shouldUseEnumKind ? 'PAGES' : 'PAGE',
5053
html: await readFile(`${path}.html`, 'utf-8'),
5154
pageData: JSON.parse(await readFile(`${path}.json`, 'utf-8')),
5255
headers: undefined,
@@ -96,14 +99,17 @@ const buildAppCacheValue = async (
9699
const buildRouteCacheValue = async (
97100
path: string,
98101
initialRevalidateSeconds: number | false,
102+
shouldUseEnumKind: boolean,
99103
): Promise<NetlifyCachedRouteValue> => ({
100-
kind: 'ROUTE',
104+
kind: shouldUseEnumKind ? 'APP_ROUTE' : 'ROUTE',
101105
body: await readFile(`${path}.body`, 'base64'),
102106
...JSON.parse(await readFile(`${path}.meta`, 'utf-8')),
103107
revalidate: initialRevalidateSeconds,
104108
})
105109

106-
const buildFetchCacheValue = async (path: string): Promise<CachedFetchValue> => ({
110+
const buildFetchCacheValue = async (
111+
path: string,
112+
): Promise<CachedFetchValueForMultipleVersions> => ({
107113
kind: 'FETCH',
108114
...JSON.parse(await readFile(path, 'utf-8')),
109115
})
@@ -133,6 +139,13 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void>
133139
})
134140
: false
135141

142+
// https://github.com/vercel/next.js/pull/68602 changed the cache kind for Pages router pages from `PAGE` to `PAGES` and from `ROUTE` to `APP_ROUTE`.
143+
const shouldUseEnumKind = ctx.nextVersion
144+
? satisfies(ctx.nextVersion, '>=15.0.0-canary.114 <15.0.0-d || >15.0.0-rc.0', {
145+
includePrerelease: true,
146+
})
147+
: false
148+
136149
await Promise.all(
137150
Object.entries(manifest.routes).map(
138151
([route, meta]): Promise<void> =>
@@ -152,7 +165,10 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void>
152165
// if pages router returns 'notFound: true', build won't produce html and json files
153166
return
154167
}
155-
value = await buildPagesCacheValue(join(ctx.publishDir, 'server/pages', key))
168+
value = await buildPagesCacheValue(
169+
join(ctx.publishDir, 'server/pages', key),
170+
shouldUseEnumKind,
171+
)
156172
break
157173
case meta.dataRoute?.endsWith('.rsc'):
158174
value = await buildAppCacheValue(
@@ -164,14 +180,15 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void>
164180
value = await buildRouteCacheValue(
165181
join(ctx.publishDir, 'server/app', key),
166182
meta.initialRevalidateSeconds,
183+
shouldUseEnumKind,
167184
)
168185
break
169186
default:
170187
throw new Error(`Unrecognized content: ${route}`)
171188
}
172189

173190
// Netlify Forms are not support and require a workaround
174-
if (value.kind === 'PAGE' || value.kind === 'APP_PAGE') {
191+
if (value.kind === 'PAGE' || value.kind === 'PAGES' || value.kind === 'APP_PAGE') {
175192
verifyNetlifyForms(ctx, value.html)
176193
}
177194

src/run/handlers/cache.cts

+43-24
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import { type Span } from '@opentelemetry/api'
1111
import type { PrerenderManifest } from 'next/dist/build/index.js'
1212
import { NEXT_CACHE_TAGS_HEADER } from 'next/dist/lib/constants.js'
1313

14-
import type {
15-
CacheHandler,
16-
CacheHandlerContext,
17-
IncrementalCache,
18-
NetlifyCachedPageValue,
19-
NetlifyCachedRouteValue,
20-
NetlifyCacheHandlerValue,
21-
NetlifyIncrementalCacheValue,
14+
import {
15+
type CacheHandlerContext,
16+
type CacheHandlerForMultipleVersions,
17+
isCachedPageValue,
18+
isCachedRouteValue,
19+
type NetlifyCachedPageValue,
20+
type NetlifyCachedRouteValue,
21+
type NetlifyCacheHandlerValue,
22+
type NetlifyIncrementalCacheValue,
2223
} from '../../shared/cache-types.cjs'
2324
import { getRegionalBlobStore } from '../regional-blob-store.cjs'
2425

@@ -29,7 +30,7 @@ type TagManifest = { revalidatedAt: number }
2930

3031
type TagManifestBlobCache = Record<string, Promise<TagManifest>>
3132

32-
export class NetlifyCacheHandler implements CacheHandler {
33+
export class NetlifyCacheHandler implements CacheHandlerForMultipleVersions {
3334
options: CacheHandlerContext
3435
revalidatedTags: string[]
3536
blobStore: Store
@@ -132,13 +133,18 @@ export class NetlifyCacheHandler implements CacheHandler {
132133

133134
if (
134135
cacheValue.kind === 'PAGE' ||
136+
cacheValue.kind === 'PAGES' ||
135137
cacheValue.kind === 'APP_PAGE' ||
136-
cacheValue.kind === 'ROUTE'
138+
cacheValue.kind === 'ROUTE' ||
139+
cacheValue.kind === 'APP_ROUTE'
137140
) {
138141
if (cacheValue.headers?.[NEXT_CACHE_TAGS_HEADER]) {
139142
const cacheTags = (cacheValue.headers[NEXT_CACHE_TAGS_HEADER] as string).split(',')
140143
requestContext.responseCacheTags = cacheTags
141-
} else if (cacheValue.kind === 'PAGE' && typeof cacheValue.pageData === 'object') {
144+
} else if (
145+
(cacheValue.kind === 'PAGE' || cacheValue.kind === 'PAGES') &&
146+
typeof cacheValue.pageData === 'object'
147+
) {
142148
// pages router doesn't have cache tags headers in PAGE cache value
143149
// so we need to generate appropriate cache tags for it
144150
const cacheTags = [`_N_T_${key === '/index' ? '/' : key}`]
@@ -185,7 +191,9 @@ export class NetlifyCacheHandler implements CacheHandler {
185191
}
186192
}
187193

188-
async get(...args: Parameters<CacheHandler['get']>): ReturnType<CacheHandler['get']> {
194+
async get(
195+
...args: Parameters<CacheHandlerForMultipleVersions['get']>
196+
): ReturnType<CacheHandlerForMultipleVersions['get']> {
189197
return this.tracer.withActiveSpan('get cache key', async (span) => {
190198
const [key, ctx = {}] = args
191199
getLogger().debug(`[NetlifyCacheHandler.get]: ${key}`)
@@ -224,8 +232,12 @@ export class NetlifyCacheHandler implements CacheHandler {
224232
value: blob.value,
225233
}
226234

227-
case 'ROUTE': {
228-
span.addEvent('ROUTE', { lastModified: blob.lastModified, status: blob.value.status })
235+
case 'ROUTE':
236+
case 'APP_ROUTE': {
237+
span.addEvent(blob.value?.kind, {
238+
lastModified: blob.lastModified,
239+
status: blob.value.status,
240+
})
229241

230242
const valueWithoutRevalidate = this.captureRouteRevalidateAndRemoveFromObject(blob.value)
231243

@@ -237,8 +249,9 @@ export class NetlifyCacheHandler implements CacheHandler {
237249
},
238250
}
239251
}
240-
case 'PAGE': {
241-
span.addEvent('PAGE', { lastModified: blob.lastModified })
252+
case 'PAGE':
253+
case 'PAGES': {
254+
span.addEvent(blob.value?.kind, { lastModified: blob.lastModified })
242255

243256
const { revalidate, ...restOfPageValue } = blob.value
244257

@@ -250,7 +263,7 @@ export class NetlifyCacheHandler implements CacheHandler {
250263
}
251264
}
252265
case 'APP_PAGE': {
253-
span.addEvent('APP_PAGE', { lastModified: blob.lastModified })
266+
span.addEvent(blob.value?.kind, { lastModified: blob.lastModified })
254267

255268
const { revalidate, rscData, ...restOfPageValue } = blob.value
256269

@@ -272,18 +285,22 @@ export class NetlifyCacheHandler implements CacheHandler {
272285
}
273286

274287
private transformToStorableObject(
275-
data: Parameters<IncrementalCache['set']>[1],
276-
context: Parameters<IncrementalCache['set']>[2],
288+
data: Parameters<CacheHandlerForMultipleVersions['set']>[1],
289+
context: Parameters<CacheHandlerForMultipleVersions['set']>[2],
277290
): NetlifyIncrementalCacheValue | null {
278-
if (data?.kind === 'ROUTE') {
291+
if (!data) {
292+
return null
293+
}
294+
295+
if (isCachedRouteValue(data)) {
279296
return {
280297
...data,
281298
revalidate: context.revalidate,
282299
body: data.body.toString('base64'),
283300
}
284301
}
285302

286-
if (data?.kind === 'PAGE') {
303+
if (isCachedPageValue(data)) {
287304
return {
288305
...data,
289306
revalidate: context.revalidate,
@@ -301,7 +318,7 @@ export class NetlifyCacheHandler implements CacheHandler {
301318
return data
302319
}
303320

304-
async set(...args: Parameters<IncrementalCache['set']>) {
321+
async set(...args: Parameters<CacheHandlerForMultipleVersions['set']>) {
305322
return this.tracer.withActiveSpan('set cache key', async (span) => {
306323
const [key, data, context] = args
307324
const blobKey = await this.encodeBlobKey(key)
@@ -321,7 +338,7 @@ export class NetlifyCacheHandler implements CacheHandler {
321338
value,
322339
})
323340

324-
if (data?.kind === 'PAGE') {
341+
if (data?.kind === 'PAGE' || data?.kind === 'PAGES') {
325342
const requestContext = getRequestContext()
326343
if (requestContext?.didPagesRouterOnDemandRevalidate) {
327344
const tag = `_N_T_${key === '/index' ? '/' : key}`
@@ -397,8 +414,10 @@ export class NetlifyCacheHandler implements CacheHandler {
397414
cacheTags = [...tags, ...softTags]
398415
} else if (
399416
cacheEntry.value?.kind === 'PAGE' ||
417+
cacheEntry.value?.kind === 'PAGES' ||
400418
cacheEntry.value?.kind === 'APP_PAGE' ||
401-
cacheEntry.value?.kind === 'ROUTE'
419+
cacheEntry.value?.kind === 'ROUTE' ||
420+
cacheEntry.value?.kind === 'APP_ROUTE'
402421
) {
403422
cacheTags = (cacheEntry.value.headers?.[NEXT_CACHE_TAGS_HEADER] as string)?.split(',') || []
404423
} else {

src/run/next.cts

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ import { getRequestContext } from './handlers/request-context.cjs'
88
import { getTracer } from './handlers/tracer.cjs'
99
import { getRegionalBlobStore } from './regional-blob-store.cjs'
1010

11+
// https://github.com/vercel/next.js/pull/68193/files#diff-37243d614f1f5d3f7ea50bbf2af263f6b1a9a4f70e84427977781e07b02f57f1R49
12+
// This import resulted in importing unbundled React which depending if NODE_ENV is `production` or not would use
13+
// either development or production version of React. When not set to `production` it would use development version
14+
// which later cause mismatching problems when both development and production versions of React were loaded causing
15+
// react errors.
16+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
17+
// @ts-ignore ignoring readonly NODE_ENV
18+
process.env.NODE_ENV = 'production'
19+
1120
console.time('import next server')
1221

1322
// eslint-disable-next-line @typescript-eslint/no-var-requires

0 commit comments

Comments
 (0)