Repository: vercel/next.js
Pull request: Ignore pending revalidations during prerendering

When reading a cache entry, we usually check its tags against the recently revalidated tags, and dismiss it, if it has any of those tags. However, this is only needed during dynamic requests, specifically during the rendering that follows a revalidating server action.

During the prerender validation in dev mode, we should not discard the cache entries based on the pending revalidation. The concurrently running dynamic rendering will handle discarding and recreating those cache entries. During build-time prerendering, there will never be any pending revalidated tags.

This fixes a bug where a different value was rendered when revalidating with a server action and then reloading the page afterwards (or triggering another unrelated revalidating server action).

Changed files:
filename: packages/next/src/server/use-cache/use-cache-wrapper.ts
patch: @@ -1004,6 +1004,7 @@ export function cache(
             shouldDiscardCacheEntry(
               entry,
               workStore,
+              workUnitStore,
               implicitTags,
               implicitTagsExpiration
             )
@@ -1284,17 +1285,12 @@ function shouldForceRevalidate(
 function shouldDiscardCacheEntry(
   entry: CacheEntry,
   workStore: WorkStore,
+  workUnitStore: WorkUnitStore | undefined,
   implicitTags: string[],
   implicitTagsExpiration: number
 ): boolean {
-  // If the cache entry contains revalidated tags that the cache handler might
-  // not know about yet, we need to discard it.
-  if (entry.tags.some((tag) => isRecentlyRevalidatedTag(tag, workStore))) {
-    return true
-  }
-
   // If the cache entry was created before any of the implicit tags were
-  // revalidated last, we also need to discard it.
+  // revalidated last, we need to discard it.
   if (entry.timestamp <= implicitTagsExpiration) {
     debug?.(
       'entry was created at',
@@ -1306,6 +1302,33 @@ function shouldDiscardCacheEntry(
     return true
   }
 
+  // During prerendering, we ignore recently revalidated tags. In dev mode, we
+  // can assume that the dynamic dev rendering will have discarded and recreated
+  // the affected cache entries, and we don't want to discard those again during
+  // the prerender validation. During build-time prerendering, there will never
+  // be any pending revalidated tags.
+  if (workUnitStore) {
+    switch (workUnitStore.type) {
+      case 'prerender':
+        return false
+      case 'prerender-client':
+      case 'prerender-ppr':
+      case 'prerender-legacy':
+      case 'request':
+      case 'cache':
+      case 'unstable-cache':
+        break
+      default:
+        workUnitStore satisfies never
+    }
+  }
+
+  // If the cache entry contains revalidated tags that the cache handler might
+  // not know about yet, we need to discard it.
+  if (entry.tags.some((tag) => isRecentlyRevalidatedTag(tag, workStore))) {
+    return true
+  }
+
   // Finally, if any of the implicit tags have been revalidated recently, we
   // also need to discard the cache entry.
   if (implicitTags.some((tag) => isRecentlyRevalidatedTag(tag, workStore))) {
@@ -1326,8 +1349,9 @@ function isRecentlyRevalidatedTag(tag: string, workStore: WorkStore): boolean {
   }
 
   // It could also have been revalidated by the currently running server action.
-  // In this case the revalidation might not have been propagated to the cache
-  // handler yet, so we read it from the pending tags in the work store.
+  // In this case the revalidation might not have been fully propagated by a
+  // remote cache handler yet, so we read it from the pending tags in the work
+  // store.
   if (pendingRevalidatedTags?.includes(tag)) {
     debug?.('tag', tag, 'was just revalidated')
 

filename: test/cache-components-tests-manifest.json
patch: @@ -27,9 +27,6 @@
         "searchparams-reuse-loading should re-use loading from \"full\" prefetch for param-less URL when navigating to param-full route"
       ]
     },
-    "test/e2e/app-dir/use-cache/use-cache.test.ts": {
-      "failed": ["use-cache should update after unstable_expireTag correctly"]
-    },
     "test/production/app-dir/browser-chunks/browser-chunks.test.ts": {
       "failed": [
         "browser-chunks must not bundle any server modules into browser chunks"