Repository: vercel/next.js
Pull request: Only log `pending revalidates...` debug log if applicable

When [verbose cache logging](https://nextjs.org/docs/app/guides/incremental-static-regeneration#verifying-correct-production-behavior) is enabled, we were always adding a debug log as follows after rendering a page (during prerendering or at request time):

```
pending revalidates promise finished for: {
  auth: null,
  host: null,
  hostname: null,
  pathname: '/',
  port: null,
  protocol: null,
  query: undefined,
  search: '',
  hash: '',
  href: '/',
  slashes: null
}
```

However, if there were no pending revalidates to process, this log is misleading as it suggests that there were pending revalidates that have now been processed, while in reality there were none. So with this PR, we now only log this message if there are actually any pending revalidates, i.e. some tags were revalidated, a `fetch` cache entry was revalidated, an `unstable_cache` entry was revalidated, or a `'use cache'` entry was saved to a cache handler.

In addition, the log is now shortened to only log the URL string instead of the full URL object for better readability, e.g.:

```
pending revalidates promise finished for: /
```

Changed files:
filename: packages/next/src/server/app-render/action-handler.ts
patch: @@ -1086,18 +1086,23 @@ export async function handleAction({
 
         // For form actions, we need to continue rendering the page.
         if (isFetchAction) {
+          // If we skip page rendering, we need to ensure pending revalidates
+          // are awaited before closing the response. Otherwise, this will be
+          // done after rendering the page.
+          const maybeRevalidatesPromise = skipPageRendering
+            ? executeRevalidates(workStore)
+            : false
+
           return {
             type: 'done',
             result: await generateFlight(req, ctx, requestStore, {
               actionResult: Promise.resolve(actionResult),
               skipPageRendering,
               temporaryReferences,
-              // If we skip page rendering, we need to ensure pending
-              // revalidates are awaited before closing the response. Otherwise,
-              // this will be done after rendering the page.
-              waitUntil: skipPageRendering
-                ? executeRevalidates(workStore)
-                : undefined,
+              waitUntil:
+                maybeRevalidatesPromise === false
+                  ? undefined
+                  : maybeRevalidatesPromise,
             }),
           }
         } else {

filename: packages/next/src/server/app-render/app-render.tsx
patch: @@ -2108,22 +2108,19 @@ async function renderToHTMLOrFlightImpl(
       metadata,
       contentType: HTML_CONTENT_TYPE_HEADER,
     }
+
     // If we have pending revalidates, wait until they are all resolved.
-    if (
-      workStore.pendingRevalidates ||
-      workStore.pendingRevalidateWrites ||
-      workStore.pendingRevalidatedTags
-    ) {
-      const pendingPromise = executeRevalidates(workStore).finally(() => {
+    const maybeRevalidatesPromise = executeRevalidates(workStore)
+    if (maybeRevalidatesPromise !== false) {
+      const revalidatesPromise = maybeRevalidatesPromise.finally(() => {
         if (process.env.NEXT_PRIVATE_DEBUG_CACHE) {
-          console.log('pending revalidates promise finished for:', url)
+          console.log('pending revalidates promise finished for:', url.href)
         }
       })
-
       if (renderOpts.waitUntil) {
-        renderOpts.waitUntil(pendingPromise)
+        renderOpts.waitUntil(revalidatesPromise)
       } else {
-        options.waitUntil = pendingPromise
+        options.waitUntil = revalidatesPromise
       }
     }
 
@@ -2290,21 +2287,17 @@ async function renderToHTMLOrFlightImpl(
     }
 
     // If we have pending revalidates, wait until they are all resolved.
-    if (
-      workStore.pendingRevalidates ||
-      workStore.pendingRevalidateWrites ||
-      workStore.pendingRevalidatedTags
-    ) {
-      const pendingPromise = executeRevalidates(workStore).finally(() => {
+    const maybeRevalidatesPromise = executeRevalidates(workStore)
+    if (maybeRevalidatesPromise !== false) {
+      const revalidatesPromise = maybeRevalidatesPromise.finally(() => {
         if (process.env.NEXT_PRIVATE_DEBUG_CACHE) {
-          console.log('pending revalidates promise finished for:', url)
+          console.log('pending revalidates promise finished for:', url.href)
         }
       })
-
       if (renderOpts.waitUntil) {
-        renderOpts.waitUntil(pendingPromise)
+        renderOpts.waitUntil(revalidatesPromise)
       } else {
-        options.waitUntil = pendingPromise
+        options.waitUntil = revalidatesPromise
       }
     }
 

filename: packages/next/src/server/revalidation-utils.ts
patch: @@ -183,26 +183,39 @@ async function revalidateTags(
   await Promise.all(promises)
 }
 
-export async function executeRevalidates(
+export function executeRevalidates(
   workStore: WorkStore,
   state?: RevalidationState
-) {
+): false | Promise<void> {
+  const promises: Promise<unknown>[] = []
+
   const pendingRevalidatedTags =
     state?.pendingRevalidatedTags ?? workStore.pendingRevalidatedTags ?? []
 
-  const pendingRevalidates =
+  if (pendingRevalidatedTags.length > 0) {
+    promises.push(
+      revalidateTags(
+        pendingRevalidatedTags,
+        workStore.incrementalCache,
+        workStore
+      )
+    )
+  }
+
+  const pendingRevalidates = Object.values(
     state?.pendingRevalidates ?? workStore.pendingRevalidates ?? {}
+  )
+
+  promises.push(...pendingRevalidates)
 
   const pendingRevalidateWrites =
     state?.pendingRevalidateWrites ?? workStore.pendingRevalidateWrites ?? []
 
-  return Promise.all([
-    revalidateTags(
-      pendingRevalidatedTags,
-      workStore.incrementalCache,
-      workStore
-    ),
-    ...Object.values(pendingRevalidates),
-    ...pendingRevalidateWrites,
-  ])
+  promises.push(...pendingRevalidateWrites)
+
+  if (promises.length === 0) {
+    return false
+  }
+
+  return Promise.all(promises).then(() => undefined)
 }

filename: packages/next/src/server/route-modules/app-route/module.ts
patch: @@ -331,16 +331,20 @@ export class AppRouteRouteModule extends RouteModule<
     }
 
     const resolvePendingRevalidations = () => {
-      context.renderOpts.pendingWaitUntil = executeRevalidates(
-        workStore
-      ).finally(() => {
-        if (process.env.NEXT_PRIVATE_DEBUG_CACHE) {
-          console.log(
-            'pending revalidates promise finished for:',
-            requestStore.url
-          )
-        }
-      })
+      const maybeRevalidatesPromise = executeRevalidates(workStore)
+
+      if (maybeRevalidatesPromise !== false) {
+        context.renderOpts.pendingWaitUntil = maybeRevalidatesPromise.finally(
+          () => {
+            if (process.env.NEXT_PRIVATE_DEBUG_CACHE) {
+              console.log(
+                'pending revalidates promise finished for:',
+                requestStore.url.pathname + requestStore.url.search
+              )
+            }
+          }
+        )
+      }
     }
 
     let prerenderStore: null | PrerenderStore = null