Skip to content

Commit 8ffa6c7

Browse files
authored
Ensure global cache handlers are used properly (vercel#74626)
Currently any cache handlers configured under the global symbol are overridden by the default built-in cache handle unexpectedly. The default built-in cache handler should only be used if no user configured one is available and no global symbol provided one is available. This also fixes `expireTags` not being called on configured cache handlers at all due to that part not being wired up. Regression tests for the above are also added to ensure this is behaving as expected.
1 parent 94bda49 commit 8ffa6c7

File tree

20 files changed

+288
-75
lines changed

20 files changed

+288
-75
lines changed

packages/next/src/build/static-paths/app.ts

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,21 @@ import type { ParamValue, Params } from '../../server/request/params'
22
import type { AppPageModule } from '../../server/route-modules/app-page/module'
33
import type { AppSegment } from '../segment-config/app/app-segments'
44
import type { StaticPathsResult } from './types'
5-
import type { CacheHandler } from '../../server/lib/incremental-cache'
65

76
import path from 'path'
87
import { AfterRunner } from '../../server/after/run-with-after'
98
import { createWorkStore } from '../../server/async-storage/work-store'
109
import { FallbackMode } from '../../lib/fallback'
11-
import { formatDynamicImportPath } from '../../lib/format-dynamic-import-path'
1210
import { getRouteMatcher } from '../../shared/lib/router/utils/route-matcher'
1311
import {
1412
getRouteRegex,
1513
type RouteRegex,
1614
} from '../../shared/lib/router/utils/route-regex'
17-
import { IncrementalCache } from '../../server/lib/incremental-cache'
18-
import { interopDefault } from '../../lib/interop-default'
19-
import { nodeFs } from '../../server/lib/node-fs-methods'
15+
import type { IncrementalCache } from '../../server/lib/incremental-cache'
2016
import { normalizePathname, encodeParam } from './utils'
21-
import * as ciEnvironment from '../../server/ci-info'
2217
import escapePathDelimiters from '../../shared/lib/router/utils/escape-path-delimiters'
18+
import { createIncrementalCache } from '../../export/helpers/create-incremental-cache'
19+
import type { NextConfigComplete } from '../../server/config-shared'
2320

2421
/**
2522
* Compares two parameters to see if they're equal.
@@ -263,6 +260,7 @@ export async function buildAppStaticPaths({
263260
cacheHandler,
264261
cacheLifeProfiles,
265262
requestHeaders,
263+
cacheHandlers,
266264
maxMemoryCacheSize,
267265
fetchCacheKeyPrefix,
268266
nextConfigOutput,
@@ -280,6 +278,7 @@ export async function buildAppStaticPaths({
280278
isrFlushToDisk?: boolean
281279
fetchCacheKeyPrefix?: string
282280
cacheHandler?: string
281+
cacheHandlers?: NextConfigComplete['experimental']['cacheHandlers']
283282
cacheLifeProfiles?: {
284283
[profile: string]: import('../../server/use-cache/cache-life').CacheLife
285284
}
@@ -302,33 +301,16 @@ export async function buildAppStaticPaths({
302301

303302
ComponentMod.patchFetch()
304303

305-
let CurCacheHandler: typeof CacheHandler | undefined
306-
if (cacheHandler) {
307-
CurCacheHandler = interopDefault(
308-
await import(formatDynamicImportPath(dir, cacheHandler)).then(
309-
(mod) => mod.default || mod
310-
)
311-
)
312-
}
313-
314-
const incrementalCache = new IncrementalCache({
315-
fs: nodeFs,
316-
dev: true,
304+
const incrementalCache = await createIncrementalCache({
305+
dir,
306+
distDir,
317307
dynamicIO,
318-
flushToDisk: isrFlushToDisk,
319-
serverDistDir: path.join(distDir, 'server'),
320-
fetchCacheKeyPrefix,
321-
maxMemoryCacheSize,
322-
getPrerenderManifest: () => ({
323-
version: -1 as any, // letting us know this doesn't conform to spec
324-
routes: {},
325-
dynamicRoutes: {},
326-
notFoundRoutes: [],
327-
preview: null as any, // `preview` is special case read in next-dev-server
328-
}),
329-
CurCacheHandler,
308+
cacheHandler,
309+
cacheHandlers,
330310
requestHeaders,
331-
minimalMode: ciEnvironment.hasNextSupport,
311+
fetchCacheKeyPrefix,
312+
flushToDisk: isrFlushToDisk,
313+
cacheMaxMemorySize: maxMemoryCacheSize,
332314
})
333315

334316
const regex = getRouteRegex(page)

packages/next/src/build/templates/edge-ssr-app.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,24 @@ import type { NextConfigComplete } from '../../server/config-shared'
1313
import { PAGE_TYPES } from '../../lib/page-types'
1414
import { setReferenceManifestsSingleton } from '../../server/app-render/encryption-utils'
1515
import { createServerModuleMap } from '../../server/app-render/action-utils'
16+
import {
17+
cacheHandlerGlobal,
18+
cacheHandlersSymbol,
19+
} from '../../server/use-cache/constants'
1620

1721
declare const incrementalCacheHandler: any
1822
// OPTIONAL_IMPORT:incrementalCacheHandler
1923

2024
const cacheHandlers = {}
2125

22-
if (!(globalThis as any).__nextCacheHandlers) {
23-
;(globalThis as any).__nextCacheHandlers = cacheHandlers
26+
if (!cacheHandlerGlobal.__nextCacheHandlers) {
27+
cacheHandlerGlobal.__nextCacheHandlers = cacheHandlers
28+
29+
if (!cacheHandlerGlobal.__nextCacheHandlers.default) {
30+
cacheHandlerGlobal.__nextCacheHandlers.default =
31+
cacheHandlerGlobal[cacheHandlersSymbol]?.DefaultCache ||
32+
cacheHandlerGlobal.__nextCacheHandlers.__nextDefault
33+
}
2434
}
2535

2636
const Document: DocumentType = null!

packages/next/src/build/templates/edge-ssr.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import type { RequestData } from '../../server/web/types'
2323
import type { BuildManifest } from '../../server/get-page-files'
2424
import type { NextConfigComplete } from '../../server/config-shared'
2525
import type { PAGE_TYPES } from '../../lib/page-types'
26+
import {
27+
cacheHandlerGlobal,
28+
cacheHandlersSymbol,
29+
} from '../../server/use-cache/constants'
2630

2731
// injected by the loader afterwards.
2832
declare const pagesType: PAGE_TYPES
@@ -42,8 +46,14 @@ declare const user500RouteModuleOptions: any
4246

4347
const cacheHandlers = {}
4448

45-
if (!(globalThis as any).__nextCacheHandlers) {
46-
;(globalThis as any).__nextCacheHandlers = cacheHandlers
49+
if (!cacheHandlerGlobal.__nextCacheHandlers) {
50+
cacheHandlerGlobal.__nextCacheHandlers = cacheHandlers
51+
52+
if (!cacheHandlerGlobal.__nextCacheHandlers.default) {
53+
cacheHandlerGlobal.__nextCacheHandlers.default =
54+
cacheHandlerGlobal[cacheHandlersSymbol]?.DefaultCache ||
55+
cacheHandlerGlobal.__nextCacheHandlers.__nextDefault
56+
}
4757
}
4858

4959
const pageMod = {

packages/next/src/build/webpack/loaders/next-edge-app-route-loader/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const EdgeAppRouteLoader: webpack.LoaderDefinitionFunction<EdgeAppRouteLoaderQue
3636
const cacheHandlers = JSON.parse(cacheHandlersStringified || '{}')
3737

3838
if (!cacheHandlers.default) {
39-
cacheHandlers.default = require.resolve(
39+
cacheHandlers.__nextDefault = require.resolve(
4040
'../../../../server/lib/cache-handlers/default'
4141
)
4242
}

packages/next/src/build/webpack/loaders/next-edge-ssr-loader/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction<EdgeSSRLoaderQuery> =
8585
const cacheHandlers = JSON.parse(cacheHandlersStringified || '{}')
8686

8787
if (!cacheHandlers.default) {
88-
cacheHandlers.default = require.resolve(
88+
cacheHandlers.__nextDefault = require.resolve(
8989
'../../../../server/lib/cache-handlers/default'
9090
)
9191
}

packages/next/src/export/helpers/create-incremental-cache.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { hasNextSupport } from '../../server/ci-info'
44
import { nodeFs } from '../../server/lib/node-fs-methods'
55
import { interopDefault } from '../../lib/interop-default'
66
import { formatDynamicImportPath } from '../../lib/format-dynamic-import-path'
7+
import { cacheHandlerGlobal } from '../../server/use-cache/constants'
8+
import DefaultCacheHandler from '../../server/lib/cache-handlers/default'
79

810
export async function createIncrementalCache({
911
cacheHandler,
@@ -14,6 +16,7 @@ export async function createIncrementalCache({
1416
dir,
1517
flushToDisk,
1618
cacheHandlers,
19+
requestHeaders,
1720
}: {
1821
dynamicIO: boolean
1922
cacheHandler?: string
@@ -22,6 +25,7 @@ export async function createIncrementalCache({
2225
distDir: string
2326
dir: string
2427
flushToDisk?: boolean
28+
requestHeaders?: Record<string, string | string[] | undefined>
2529
cacheHandlers?: Record<string, string | undefined>
2630
}) {
2731
// Custom cache handler overrides.
@@ -34,8 +38,8 @@ export async function createIncrementalCache({
3438
)
3539
}
3640

37-
if (!(globalThis as any).__nextCacheHandlers && cacheHandlers) {
38-
;(globalThis as any).__nextCacheHandlers = {}
41+
if (!cacheHandlerGlobal.__nextCacheHandlers && cacheHandlers) {
42+
cacheHandlerGlobal.__nextCacheHandlers = {}
3943

4044
for (const key of Object.keys(cacheHandlers)) {
4145
if (cacheHandlers[key]) {
@@ -46,11 +50,15 @@ export async function createIncrementalCache({
4650
)
4751
}
4852
}
53+
54+
if (!cacheHandlers.default) {
55+
cacheHandlerGlobal.__nextCacheHandlers.default = DefaultCacheHandler
56+
}
4957
}
5058

5159
const incrementalCache = new IncrementalCache({
5260
dev: false,
53-
requestHeaders: {},
61+
requestHeaders: requestHeaders || {},
5462
flushToDisk,
5563
dynamicIO,
5664
maxMemoryCacheSize: cacheMaxMemorySize,

packages/next/src/server/base-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1359,7 +1359,7 @@ export default abstract class Server<
13591359
) || []
13601360

13611361
for (const handler of Object.values(_globalThis.__nextCacheHandlers)) {
1362-
if (typeof handler.receiveExpiredTags === 'function') {
1362+
if (typeof handler?.receiveExpiredTags === 'function') {
13631363
await handler.receiveExpiredTags(...expiredTags)
13641364
}
13651365
}

packages/next/src/server/dev/next-dev-server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ export default class DevServer extends Server {
770770
isAppPath,
771771
requestHeaders,
772772
cacheHandler: this.nextConfig.cacheHandler,
773+
cacheHandlers: this.nextConfig.experimental.cacheHandlers,
773774
cacheLifeProfiles: this.nextConfig.experimental.cacheLife,
774775
fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
775776
isrFlushToDisk: this.nextConfig.experimental.isrFlushToDisk,

packages/next/src/server/dev/static-paths-worker.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { InvariantError } from '../../shared/lib/invariant-error'
1818
import { collectRootParamKeys } from '../../build/segment-config/app/collect-root-param-keys'
1919
import { buildAppStaticPaths } from '../../build/static-paths/app'
2020
import { buildPagesStaticPaths } from '../../build/static-paths/pages'
21+
import { createIncrementalCache } from '../../export/helpers/create-incremental-cache'
2122

2223
type RuntimeConfig = {
2324
pprConfig: ExperimentalPPRConfig | undefined
@@ -45,6 +46,7 @@ export async function loadStaticPaths({
4546
maxMemoryCacheSize,
4647
requestHeaders,
4748
cacheHandler,
49+
cacheHandlers,
4850
cacheLifeProfiles,
4951
nextConfigOutput,
5052
buildId,
@@ -65,6 +67,7 @@ export async function loadStaticPaths({
6567
maxMemoryCacheSize?: number
6668
requestHeaders: IncrementalCache['requestHeaders']
6769
cacheHandler?: string
70+
cacheHandlers?: NextConfigComplete['experimental']['cacheHandlers']
6871
cacheLifeProfiles?: {
6972
[profile: string]: import('../../server/use-cache/cache-life').CacheLife
7073
}
@@ -73,6 +76,20 @@ export async function loadStaticPaths({
7376
authInterrupts: boolean
7477
sriEnabled: boolean
7578
}): Promise<Partial<StaticPathsResult>> {
79+
// this needs to be initialized before loadComponents otherwise
80+
// "use cache" could be missing it's cache handlers
81+
await createIncrementalCache({
82+
dir,
83+
distDir,
84+
dynamicIO: false,
85+
cacheHandler,
86+
cacheHandlers,
87+
requestHeaders,
88+
fetchCacheKeyPrefix,
89+
flushToDisk: isrFlushToDisk,
90+
cacheMaxMemorySize: maxMemoryCacheSize,
91+
})
92+
7693
// update work memory runtime-config
7794
require('../../shared/lib/runtime-config.external').setConfig(config)
7895
setHttpClientAndAgentOptions({

packages/next/src/server/lib/cache-handlers/default.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const DefaultCacheHandler: CacheHandler = {
103103
}
104104
},
105105

106-
async unstable_expireTags(...tags) {
106+
async expireTags(...tags) {
107107
for (const tag of tags) {
108108
if (!tagsManifest.items[tag]) {
109109
tagsManifest.items[tag] = {}
@@ -113,8 +113,11 @@ const DefaultCacheHandler: CacheHandler = {
113113
}
114114
},
115115

116+
// This is only meant to invalidate in memory tags
117+
// not meant to be propagated like expireTags would
118+
// in multi-instance scenario
116119
async receiveExpiredTags(...tags): Promise<void> {
117-
return this.unstable_expireTags(...tags)
120+
return this.expireTags(...tags)
118121
},
119122
}
120123

packages/next/src/server/lib/cache-handlers/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ export interface CacheHandler {
4444

4545
set(cacheKey: string, entry: Promise<CacheEntry>): Promise<void>
4646

47-
// This is called when unstable_expireTags('') is called
47+
// This is called when expireTags('') is called
4848
// and should update tags manifest accordingly
49-
unstable_expireTags(...tags: string[]): Promise<void>
49+
expireTags(...tags: string[]): Promise<void>
5050

5151
// This is called when an action request sends
5252
// NEXT_CACHE_REVALIDATED_TAGS_HEADER and tells

packages/next/src/server/lib/incremental-cache/index.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,22 @@ export class IncrementalCache implements IncrementalCacheType {
249249
}
250250

251251
async revalidateTag(tags: string | string[]): Promise<void> {
252-
return this.cacheHandler?.revalidateTag?.(tags)
252+
const _globalThis: typeof globalThis & {
253+
__nextCacheHandlers?: Record<
254+
string,
255+
import('../cache-handlers/types').CacheHandler
256+
>
257+
} = globalThis
258+
259+
return Promise.all([
260+
// call expireTags on all configured cache handlers
261+
Object.values(_globalThis.__nextCacheHandlers || {}).map(
262+
(cacheHandler) =>
263+
typeof cacheHandler.expireTags === 'function' &&
264+
cacheHandler.expireTags(...(Array.isArray(tags) ? tags : [tags]))
265+
),
266+
this.cacheHandler?.revalidateTag?.(tags),
267+
]).then(() => {})
253268
}
254269

255270
// x-ref: https://github.com/facebook/react/blob/2655c9354d8e1c54ba888444220f63e836925caa/packages/react/src/ReactFetch.js#L23

packages/next/src/server/next-server.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import { InvariantError } from '../shared/lib/invariant-error'
109109
import { AwaiterOnce } from './after/awaiter'
110110
import { AsyncCallbackSet } from './lib/async-callback-set'
111111
import DefaultCacheHandler from './lib/cache-handlers/default'
112+
import { cacheHandlerGlobal, cacheHandlersSymbol } from './use-cache/constants'
112113

113114
export * from './base-server'
114115

@@ -379,8 +380,8 @@ export default class NextNodeServer extends BaseServer<
379380
protected async loadCustomCacheHandlers() {
380381
const { cacheHandlers } = this.nextConfig.experimental
381382

382-
if (!(globalThis as any).__nextCacheHandlers && cacheHandlers) {
383-
;(globalThis as any).__nextCacheHandlers = {}
383+
if (!cacheHandlerGlobal.__nextCacheHandlers && cacheHandlers) {
384+
cacheHandlerGlobal.__nextCacheHandlers = {}
384385

385386
for (const key of Object.keys(cacheHandlers)) {
386387
if (cacheHandlers[key]) {
@@ -393,7 +394,9 @@ export default class NextNodeServer extends BaseServer<
393394
}
394395

395396
if (!cacheHandlers.default) {
396-
;(globalThis as any).__nextCacheHandlers.default = DefaultCacheHandler
397+
cacheHandlerGlobal.__nextCacheHandlers.default =
398+
cacheHandlerGlobal[cacheHandlersSymbol]?.DefaultCache ||
399+
DefaultCacheHandler
397400
}
398401
}
399402
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { CacheHandler } from '../lib/cache-handlers/types'
2+
3+
// If the expire time is less than .
4+
export const DYNAMIC_EXPIRE = 300
5+
6+
export const cacheHandlersSymbol = Symbol.for('@next/cache-handlers')
7+
export const cacheHandlerGlobal: typeof globalThis & {
8+
[cacheHandlersSymbol]?: {
9+
RemoteCache?: CacheHandler
10+
DefaultCache?: CacheHandler
11+
}
12+
__nextCacheHandlers?: Record<string, CacheHandler>
13+
} = globalThis

0 commit comments

Comments
 (0)