Repository: vercel/next.js
Pull request: Remove obsolete update of implicit tags expiration after server action

With the change in #76885, we don't need to update the expiration date of implicit tags anymore at the end of a request to ensure no stale data is used after `revalidatePath` was called in a server action.

This PR is covered by the existing tests, e.g. `use-cache should revalidate caches after redirect`.

Changed files:
filename: packages/next/src/server/app-render/action-handler.ts
patch: @@ -517,9 +517,6 @@ export async function handleAction({
 
   requestStore.phase = 'action'
 
-  const resolvePendingRevalidations = async () =>
-    workUnitAsyncStorage.run(requestStore, () => executeRevalidates(workStore))
-
   // When running actions the default is no-store, you can still `cache: 'force-cache'`
   workStore.fetchCache = 'default-no-store'
 
@@ -571,7 +568,7 @@ export async function handleAction({
 
       if (isFetchAction) {
         res.statusCode = 500
-        await resolvePendingRevalidations()
+        await executeRevalidates(workStore)
 
         const promise = Promise.reject(error)
         try {
@@ -926,7 +923,7 @@ export async function handleAction({
 
       // For form actions, we need to continue rendering the page.
       if (isFetchAction) {
-        await resolvePendingRevalidations()
+        await executeRevalidates(workStore)
         addRevalidationHeader(res, { workStore, requestStore })
 
         actionResult = await finalizeAndGenerateFlight(req, ctx, requestStore, {
@@ -948,7 +945,7 @@ export async function handleAction({
       const redirectUrl = getURLFromRedirectError(err)
       const redirectType = getRedirectTypeFromError(err)
 
-      await resolvePendingRevalidations()
+      await executeRevalidates(workStore)
       addRevalidationHeader(res, { workStore, requestStore })
 
       // if it's a fetch action, we'll set the status code for logging/debugging purposes
@@ -978,7 +975,7 @@ export async function handleAction({
     } else if (isHTTPAccessFallbackError(err)) {
       res.statusCode = getAccessFallbackHTTPStatus(err)
 
-      await resolvePendingRevalidations()
+      await executeRevalidates(workStore)
       addRevalidationHeader(res, { workStore, requestStore })
 
       if (isFetchAction) {
@@ -1008,7 +1005,7 @@ export async function handleAction({
 
     if (isFetchAction) {
       res.statusCode = 500
-      await resolvePendingRevalidations()
+      await executeRevalidates(workStore)
       const promise = Promise.reject(err)
       try {
         // we need to await the promise to trigger the rejection early

filename: packages/next/src/server/lib/implicit-tags.ts
patch: @@ -13,7 +13,7 @@ export interface ImplicitTags {
    * implicit tags' expiration is stored in the work unit store, and used to
    * compare with a cache entry's timestamp.
    */
-  expiration: number
+  readonly expiration: number
 }
 
 const getDerivedTags = (pathname: string): string[] => {
@@ -71,16 +71,6 @@ async function getImplicitTagsExpiration(tags: string[]): Promise<number> {
   return expiration
 }
 
-/**
- * Fetches a new expiration value for the given `implicitTags`, and mutates its
- * `expiration` property.
- */
-export async function updateImplicitTagsExpiration(
-  implicitTags: ImplicitTags
-): Promise<void> {
-  implicitTags.expiration = await getImplicitTagsExpiration(implicitTags.tags)
-}
-
 export async function getImplicitTags(
   page: string,
   url: {

filename: packages/next/src/server/revalidation-utils.ts
patch: @@ -1,6 +1,4 @@
 import type { WorkStore } from './app-render/work-async-storage.external'
-import { workUnitAsyncStorage } from './app-render/work-unit-async-storage.external'
-import { updateImplicitTagsExpiration } from './lib/implicit-tags'
 import type { IncrementalCache } from './lib/incremental-cache'
 import { getCacheHandlers } from './use-cache/handlers'
 
@@ -89,16 +87,6 @@ async function revalidateTags(
   }
 
   await Promise.all(promises)
-
-  const workUnitStore = workUnitAsyncStorage.getStore()
-
-  if (workUnitStore?.implicitTags) {
-    const tagsSet = new Set(tags)
-
-    if (workUnitStore.implicitTags.tags.some((tag) => tagsSet.has(tag))) {
-      await updateImplicitTagsExpiration(workUnitStore.implicitTags)
-    }
-  }
 }
 
 export async function executeRevalidates(

filename: test/e2e/app-dir/use-cache-custom-handler/use-cache-custom-handler.test.ts
patch: @@ -147,7 +147,7 @@ describe('use-cache-custom-handler', () => {
     })
   })
 
-  it('should not call getExpiration again after an action if only explicit tags were revalidated', async () => {
+  it('should not call getExpiration again after an action', async () => {
     const browser = await next.browser(`/`)
 
     await retry(async () => {
@@ -171,29 +171,4 @@ describe('use-cache-custom-handler', () => {
       )
     })
   })
-
-  it('should call getExpiration again after an action if implicit tags were revalidated', async () => {
-    const browser = await next.browser(`/`)
-
-    await retry(async () => {
-      const cliOutput = next.cliOutput.slice(outputIndex)
-      expect(cliOutput).toInclude('ModernCustomCacheHandler::getExpiration')
-    })
-
-    outputIndex = next.cliOutput.length
-
-    await browser.elementById('revalidate-path').click()
-
-    await retry(async () => {
-      const cliOutput = next.cliOutput.slice(outputIndex)
-      expect(cliOutput).toIncludeRepeated(
-        'ModernCustomCacheHandler::expireTags',
-        1
-      )
-      expect(cliOutput).toIncludeRepeated(
-        'ModernCustomCacheHandler::getExpiration',
-        2
-      )
-    })
-  })
 })