From 31acc1e1210ffe779c4d2205cfb76b3c87f7aee3 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 2 May 2024 15:00:13 -0700 Subject: [PATCH 1/6] Update revalidateTag to batch tags in one request --- .../src/server/app-render/action-handler.ts | 27 ++++++++++++------- .../next/src/server/app-render/app-render.tsx | 9 ++++--- .../future/route-modules/app-route/module.ts | 7 +++-- .../lib/incremental-cache/fetch-cache.ts | 15 ++++++----- .../src/server/lib/incremental-cache/index.ts | 6 ++--- .../server/web/spec-extension/revalidate.ts | 9 ------- 6 files changed, 39 insertions(+), 34 deletions(-) diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index 5cf3c9e2c6fc9..26e45157e834e 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -113,9 +113,12 @@ async function addRevalidationHeader( requestStore: RequestStore } ) { - await Promise.all( - Object.values(staticGenerationStore.pendingRevalidates || []) - ) + await Promise.all([ + staticGenerationStore.incrementalCache?.revalidateTag( + staticGenerationStore.revalidatedTags || [] + ), + ...Object.values(staticGenerationStore.pendingRevalidates || {}), + ]) // If a tag was revalidated, the client router needs to invalidate all the // client router cache as they may be stale. And if a path was revalidated, the @@ -480,9 +483,12 @@ export async function handleAction({ if (isFetchAction) { res.statusCode = 500 - await Promise.all( - Object.values(staticGenerationStore.pendingRevalidates || []) - ) + await Promise.all([ + staticGenerationStore.incrementalCache?.revalidateTag( + staticGenerationStore.revalidatedTags || [] + ), + ...Object.values(staticGenerationStore.pendingRevalidates || {}), + ]) const promise = Promise.reject(error) try { @@ -867,9 +873,12 @@ export async function handleAction({ if (isFetchAction) { res.statusCode = 500 - await Promise.all( - Object.values(staticGenerationStore.pendingRevalidates || []) - ) + await Promise.all([ + staticGenerationStore.incrementalCache?.revalidateTag( + staticGenerationStore.revalidatedTags || [] + ), + ...Object.values(staticGenerationStore.pendingRevalidates || {}), + ]) const promise = Promise.reject(err) try { // we need to await the promise to trigger the rejection early diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 3a207806e4bea..3b92c350d3a40 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -1374,9 +1374,12 @@ async function renderToHTMLOrFlightImpl( // If we have pending revalidates, wait until they are all resolved. if (staticGenerationStore.pendingRevalidates) { - options.waitUntil = Promise.all( - Object.values(staticGenerationStore.pendingRevalidates) - ) + options.waitUntil = Promise.all([ + staticGenerationStore.incrementalCache?.revalidateTag( + staticGenerationStore.revalidatedTags || [] + ), + ...Object.values(staticGenerationStore.pendingRevalidates || {}), + ]) } addImplicitTags(staticGenerationStore) diff --git a/packages/next/src/server/future/route-modules/app-route/module.ts b/packages/next/src/server/future/route-modules/app-route/module.ts index dd362b38f21c3..ceee44ea9923e 100644 --- a/packages/next/src/server/future/route-modules/app-route/module.ts +++ b/packages/next/src/server/future/route-modules/app-route/module.ts @@ -395,11 +395,10 @@ export class AppRouteRouteModule extends RouteModule< context.renderOpts.fetchMetrics = staticGenerationStore.fetchMetrics - context.renderOpts.waitUntil = Promise.all( - Object.values( - staticGenerationStore.pendingRevalidates || [] + context.renderOpts.waitUntil = + staticGenerationStore.incrementalCache?.revalidateTag( + staticGenerationStore.revalidatedTags || [] ) - ) addImplicitTags(staticGenerationStore) ;(context.renderOpts as any).fetchTags = diff --git a/packages/next/src/server/lib/incremental-cache/fetch-cache.ts b/packages/next/src/server/lib/incremental-cache/fetch-cache.ts index 6b89c97ed2f8a..14126b0d7e9d9 100644 --- a/packages/next/src/server/lib/incremental-cache/fetch-cache.ts +++ b/packages/next/src/server/lib/incremental-cache/fetch-cache.ts @@ -126,11 +126,14 @@ export default class FetchCache implements CacheHandler { memoryCache?.reset() } - public async revalidateTag(tag: string) { + public async revalidateTag(tags: string | string[]) { + tags = typeof tags === 'string' ? [tags] : tags if (this.debug) { - console.log('revalidateTag', tag) + console.log('revalidateTag', tags) } + if (!tags.length) return + if (Date.now() < rateLimitedUntil) { if (this.debug) { console.log('rate limited ', rateLimitedUntil) @@ -140,9 +143,9 @@ export default class FetchCache implements CacheHandler { try { const res = await fetch( - `${ - this.cacheEndpoint - }/v1/suspense-cache/revalidate?tags=${encodeURIComponent(tag)}`, + `${this.cacheEndpoint}/v1/suspense-cache/revalidate?tags=${tags + .map((tag) => encodeURIComponent(tag)) + .join(',')}`, { method: 'POST', headers: this.headers, @@ -160,7 +163,7 @@ export default class FetchCache implements CacheHandler { throw new Error(`Request failed with status ${res.status}.`) } } catch (err) { - console.warn(`Failed to revalidate tag ${tag}`, err) + console.warn(`Failed to revalidate tag ${tags}`, err) } } diff --git a/packages/next/src/server/lib/incremental-cache/index.ts b/packages/next/src/server/lib/incremental-cache/index.ts index 7cf738ae2d6a2..1b81fb8992b9c 100644 --- a/packages/next/src/server/lib/incremental-cache/index.ts +++ b/packages/next/src/server/lib/incremental-cache/index.ts @@ -58,7 +58,7 @@ export class CacheHandler { ..._args: Parameters ): Promise {} - public async revalidateTag(_tag: string): Promise {} + public async revalidateTag(_tag: string | string[]): Promise {} public resetRequestCache(): void {} } @@ -280,7 +280,7 @@ export class IncrementalCache implements IncrementalCacheType { return unlockNext } - async revalidateTag(tag: string) { + async revalidateTag(tags: string | string[]) { if ( process.env.__NEXT_INCREMENTAL_CACHE_IPC_PORT && process.env.__NEXT_INCREMENTAL_CACHE_IPC_KEY && @@ -296,7 +296,7 @@ export class IncrementalCache implements IncrementalCacheType { }) } - return this.cacheHandler?.revalidateTag?.(tag) + return this.cacheHandler?.revalidateTag?.(tags) } // x-ref: https://github.com/facebook/react/blob/2655c9354d8e1c54ba888444220f63e836925caa/packages/react/src/ReactFetch.js#L23 diff --git a/packages/next/src/server/web/spec-extension/revalidate.ts b/packages/next/src/server/web/spec-extension/revalidate.ts index ef10e466fe07f..00996c7d188f2 100644 --- a/packages/next/src/server/web/spec-extension/revalidate.ts +++ b/packages/next/src/server/web/spec-extension/revalidate.ts @@ -68,15 +68,6 @@ function revalidate(tag: string, expression: string) { store.revalidatedTags.push(tag) } - if (!store.pendingRevalidates) { - store.pendingRevalidates = {} - } - store.pendingRevalidates[tag] = store.incrementalCache - .revalidateTag?.(tag) - .catch((err) => { - console.error(`revalidate failed for ${tag}`, err) - }) - // TODO: only revalidate if the path matches store.pathWasRevalidated = true } From 44220e43bc38676471217ff3ac0b5d7d2c983857 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 2 May 2024 15:46:21 -0700 Subject: [PATCH 2/6] fix --- .../src/server/future/route-modules/app-route/module.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/next/src/server/future/route-modules/app-route/module.ts b/packages/next/src/server/future/route-modules/app-route/module.ts index ceee44ea9923e..10671285fa829 100644 --- a/packages/next/src/server/future/route-modules/app-route/module.ts +++ b/packages/next/src/server/future/route-modules/app-route/module.ts @@ -395,10 +395,14 @@ export class AppRouteRouteModule extends RouteModule< context.renderOpts.fetchMetrics = staticGenerationStore.fetchMetrics - context.renderOpts.waitUntil = + context.renderOpts.waitUntil = Promise.all([ staticGenerationStore.incrementalCache?.revalidateTag( staticGenerationStore.revalidatedTags || [] - ) + ), + ...Object.values( + staticGenerationStore.pendingRevalidates || {} + ), + ]) addImplicitTags(staticGenerationStore) ;(context.renderOpts as any).fetchTags = From 4a9d585f630cd34be91c59bdb903f18fe3e3c0e2 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 2 May 2024 15:48:30 -0700 Subject: [PATCH 3/6] update fs cache as well --- .../lib/incremental-cache/file-system-cache.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts index 9098413e0a8a3..79c41dab2b1b7 100644 --- a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts +++ b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts @@ -105,9 +105,15 @@ export default class FileSystemCache implements CacheHandler { if (this.debug) console.log('loadTagsManifest', tagsManifest) } - public async revalidateTag(tag: string) { + public async revalidateTag(tags: string | string[]) { + tags = typeof tags === 'string' ? [tags] : tags + if (this.debug) { - console.log('revalidateTag', tag) + console.log('revalidateTag', tags) + } + + if (tags.length === 0) { + return } // we need to ensure the tagsManifest is refreshed @@ -118,9 +124,11 @@ export default class FileSystemCache implements CacheHandler { return } - const data = tagsManifest.items[tag] || {} - data.revalidatedAt = Date.now() - tagsManifest.items[tag] = data + for (const tag of tags) { + const data = tagsManifest.items[tag] || {} + data.revalidatedAt = Date.now() + tagsManifest.items[tag] = data + } try { await this.fs.mkdir(path.dirname(this.tagsManifestPath)) From b18ea15c4d1f9f683df8e4f806463b347a928212 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 2 May 2024 15:58:26 -0700 Subject: [PATCH 4/6] make type safer --- .../next/src/server/lib/incremental-cache/fetch-cache.ts | 5 ++++- .../src/server/lib/incremental-cache/file-system-cache.ts | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/next/src/server/lib/incremental-cache/fetch-cache.ts b/packages/next/src/server/lib/incremental-cache/fetch-cache.ts index 14126b0d7e9d9..09ffa43102acc 100644 --- a/packages/next/src/server/lib/incremental-cache/fetch-cache.ts +++ b/packages/next/src/server/lib/incremental-cache/fetch-cache.ts @@ -126,7 +126,10 @@ export default class FetchCache implements CacheHandler { memoryCache?.reset() } - public async revalidateTag(tags: string | string[]) { + public async revalidateTag( + ...args: Parameters + ) { + let [tags] = args tags = typeof tags === 'string' ? [tags] : tags if (this.debug) { console.log('revalidateTag', tags) diff --git a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts index 79c41dab2b1b7..7601fae609830 100644 --- a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts +++ b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts @@ -29,7 +29,7 @@ type TagsManifest = { let memoryCache: LRUCache | undefined let tagsManifest: TagsManifest | undefined -export default class FileSystemCache implements CacheHandler { +export default class FileSystemCache { private fs: FileSystemCacheContext['fs'] private flushToDisk?: FileSystemCacheContext['flushToDisk'] private serverDistDir: FileSystemCacheContext['serverDistDir'] @@ -105,7 +105,10 @@ export default class FileSystemCache implements CacheHandler { if (this.debug) console.log('loadTagsManifest', tagsManifest) } - public async revalidateTag(tags: string | string[]) { + public async revalidateTag( + ...args: Parameters + ) { + let [tags] = args tags = typeof tags === 'string' ? [tags] : tags if (this.debug) { From 5d80967f1ef5f7b54b42a00be5113c5b24dd2eb6 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 2 May 2024 15:59:55 -0700 Subject: [PATCH 5/6] revert extra change --- .../next/src/server/lib/incremental-cache/file-system-cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts index 7601fae609830..c1a7c3ef5bf20 100644 --- a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts +++ b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts @@ -29,7 +29,7 @@ type TagsManifest = { let memoryCache: LRUCache | undefined let tagsManifest: TagsManifest | undefined -export default class FileSystemCache { +export default class FileSystemCache implements CacheHandler { private fs: FileSystemCacheContext['fs'] private flushToDisk?: FileSystemCacheContext['flushToDisk'] private serverDistDir: FileSystemCacheContext['serverDistDir'] From b53763f841fee6e7f33b4f9c78e757cb087a9da6 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 2 May 2024 16:11:00 -0700 Subject: [PATCH 6/6] update type more --- packages/next/src/server/lib/incremental-cache/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/next/src/server/lib/incremental-cache/index.ts b/packages/next/src/server/lib/incremental-cache/index.ts index 1b81fb8992b9c..021ec29344d33 100644 --- a/packages/next/src/server/lib/incremental-cache/index.ts +++ b/packages/next/src/server/lib/incremental-cache/index.ts @@ -58,7 +58,9 @@ export class CacheHandler { ..._args: Parameters ): Promise {} - public async revalidateTag(_tag: string | string[]): Promise {} + public async revalidateTag( + ..._args: Parameters + ): Promise {} public resetRequestCache(): void {} } @@ -280,7 +282,7 @@ export class IncrementalCache implements IncrementalCacheType { return unlockNext } - async revalidateTag(tags: string | string[]) { + async revalidateTag(tags: string | string[]): Promise { if ( process.env.__NEXT_INCREMENTAL_CACHE_IPC_PORT && process.env.__NEXT_INCREMENTAL_CACHE_IPC_KEY &&