@@ -214,10 +214,30 @@ export function markTagsAsStaleAndPurgeEdgeCache(
214214) {
215215 const tags = getCacheTagsFromTagOrTags ( tagOrTags )
216216
217+ // Next.js is calling classic CacheHandler.revalidateTag and 'use cache' CacheHandler expireTags/updateTags separately
218+ // this results in duplicate work being done (it doesn't cause problems, but it is inefficient)
219+ // See https://github.com/vercel/next.js/blob/8cab15c0c947a71eb8606ba29da719a2e121fc88/packages/next/src/server/revalidation-utils.ts#L170-L180
220+ // Deduping those within context of a single request might catch unrelated invalidations, so instead of using just request context
221+ // we will check if they happened in same event loop tick as well.
222+ const revalidationKey = JSON . stringify ( { tags, durations } )
223+ const requestContext = getRequestContext ( )
224+
225+ if ( requestContext ) {
226+ const ongoingRevalidation = requestContext . ongoingRevalidations ?. get ( revalidationKey )
227+ if ( ongoingRevalidation ) {
228+ // If we already have an ongoing revalidation for this key, we can use it
229+ return ongoingRevalidation
230+ }
231+ }
232+
217233 const revalidateTagPromise = doRevalidateTagAndPurgeEdgeCache ( tags , durations )
218234
219- const requestContext = getRequestContext ( )
220235 if ( requestContext ) {
236+ requestContext . ongoingRevalidations ??= new Map ( )
237+ requestContext . ongoingRevalidations . set ( revalidationKey , revalidateTagPromise )
238+ process . nextTick ( ( ) => {
239+ requestContext . ongoingRevalidations ?. delete ( revalidationKey )
240+ } )
221241 requestContext . trackBackgroundWork ( revalidateTagPromise )
222242 }
223243
0 commit comments