Web Development

Next.js Revalidate Not Working? How to Debug and Fix

Learn why your Next.js cache isn't updating and how to correctly use revalidatePath and revalidateTag to trigger fresh data.

By · · 6 min read

Short answer

Next.js revalidation often fails because it invalidates the cache for the next request rather than purging it instantly. Ensure your fetch calls use explicit tags, confirm you are calling revalidation inside a Server Action or Route Handler, and verify that your cache keys match exactly what was defined in the fetch request.

Key takeaways

  • Revalidation is lazy; the cache only updates when a new request actually triggers a rebuild.
  • revalidateTag requires tags to be explicitly attached to fetch calls or unstable_cache functions.
  • Client components cannot revalidate directly; you must call a Server Action or Route Handler.
  • Always check your headers in the Network tab to confirm cache state before debugging code logic.

Why does revalidate not update immediately?

The most common reason developers think revalidation is broken is a misunderstanding of how Next.js handles cache invalidation. When you call revalidatePath or revalidateTag, Next.js does not immediately purge the cache and re-render the page. This design choice prioritizes performance by ensuring the user experience is never blocked by a slow re-rendering process.

Instead, it marks the cached data as stale. The next time a user visits that path, Next.js will serve the stale data one last time while simultaneously triggering a background re-render to update the cache. If you are refreshing the page immediately after triggering the function, you might still see the old version because the re-render has not yet completed or because the first request only serves the stale version to ensure quick page load times.

Understanding this 'stale-while-revalidate' behavior is critical. You are essentially telling the system that the current data is expired, but the system decides when to generate the new data based on incoming traffic. If you do not visit the page a second time, the cache remains stale indefinitely in the background.

Are your fetch calls correctly tagged?

If you are using revalidateTag, the cache invalidation will only work if your data sources are explicitly tagged. By default, Next.js does not attach tags to fetch requests. Without these identifiers, the system has no way to know which specific data chunks are associated with the tag you provided to the revalidation function.

You must include the next property with a tags array in your fetch options. If the tags do not match exactly—including case sensitivity—the revalidation function will run without error but will have no effect on your data. This is a common source of confusion; ensure your tags in the fetch request are strings that match the exact identifier used in revalidateTag.

Additionally, keep in mind that fetch requests are memoized within the lifecycle of a single server request. If you perform the same fetch multiple times in one render cycle, only the first one hits the network. When you trigger revalidation, ensure the request that follows actually invokes the fetch again by navigating or refreshing the page.

const data = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
});

Are you calling revalidate in the correct environment?

Next.js revalidation functions are Server-side only. You cannot call them from a Client Component (a file with 'use client' at the top). If you attempt to do this, the code will fail silently or throw an error depending on your setup because the Node.js runtime environment required for caching operations is not available in the browser context.

You must move your revalidation logic into a Server Action or a Route Handler. Server Actions are generally preferred for handling UI-triggered revalidation, such as submitting a form or clicking a button that updates content. When a button is clicked in a client component, it calls the exported function from the 'use server' file, which executes on the server and performs the revalidation safely.

If you try to call revalidatePath inside a standard API route or a utility function that is not marked as a server action, ensure that you are within the App Router's scope. In pages router setups or legacy code, these functions behave differently. Always check that your import statement originates from 'next/cache'.

// app/actions.ts
'use server';

import { revalidateTag } from 'next/cache';

export async function refreshPosts() {
  revalidateTag('posts');
}

When should you use revalidatePath vs revalidateTag?

Choosing between these two depends on the scope of the data you want to clear. revalidatePath is useful when you want to clear the cache for a specific URL or a set of URLs, while revalidateTag is for clearing data across multiple pages. Using tags is typically more scalable for complex applications where one data source feeds into several different pages. If you update a product, revalidating the tag ensures the product page, category page, and search results all update simultaneously without needing to manually list every path.

revalidatePath, conversely, acts with more surgical precision. You can provide a specific URL like '/dashboard/settings', and it will purge the cache specifically for that page. This is safer if you only want to affect a single route without worrying about side effects on other components that might share a data tag. If you need to target a whole hierarchy, you can pass the 'layout' type to force a revalidation of all child segments of a path.

For most data-driven applications, tags provide a cleaner interface because they abstract the URL structure away from the data management layer. When the underlying database changes, you simply revalidate the tag associated with the entity, regardless of where that entity is displayed on the site.

MethodScopeUse Case
revalidatePathURL specificWhen you want to refresh a specific page content
revalidateTagData specificWhen one API call is used across multiple pages

Debugging cache issues on Vercel

If you are deployed on Vercel, the cache headers can provide insights into what is happening. Use the Network tab in your browser's Developer Tools to inspect the x-vercel-cache header on your responses. This header informs you whether the request was handled by the edge or the origin.

A HIT means the data was served from the cache, while a MISS means it was fetched from your origin. If you see HIT after calling revalidate, it confirms that your revalidation command likely failed to clear the target or was called in an environment where it had no effect. Sometimes, you may see STALE, which indicates the system is serving the outdated content while fetching the update in the background.

Additionally, check the x-nextjs-cache header. It usually indicates whether the response came from the Data Cache or the Full Route Cache. By cross-referencing these values, you can determine if your revalidation succeeded but the route itself is still cached at the page level. Always clear your browser cache or use an incognito window to verify that you are not seeing a local browser cache hit instead of a server cache hit.

Common gotchas and trade-offs

One major gotcha is the cache: 'no-store' option in fetch. If you set this, Next.js will ignore your cache tags entirely because it never caches the request. This is useful for real-time data but often confuses developers who try to revalidate it. If your fetch call is set to 'no-store', there is no cache to invalidate, so revalidateTag will do nothing. Ensure your cache strategy is set to 'force-cache' or uses a default time if you intend to use revalidation.

Another issue arises with middleware. If your middleware is modifying requests or blocking paths, it might interfere with how the cache is identified. Always ensure your path strings passed to revalidatePath match the actual route exactly, including trailing slashes if your configuration demands them. If you define a route like '/blog/[slug]', you must pass that exact string to revalidatePath, not the resolved URL like '/blog/my-post'.

Finally, ensure that your revalidation logic is not accidentally wrapped in a condition that never executes. It is common to have conditional logic in server actions that guard against unauthorized users; if the user is not authenticated, the revalidate function never runs, leaving the cache in a stale state for everyone.

Frequently asked questions

Why does my data stay stale after calling revalidate?

This happens because revalidation is lazy. It invalidates the cache, but the actual update occurs only when a subsequent request is made. If you check immediately, you might still see the old cached version.

Can I revalidate from a client component?

No. revalidatePath and revalidateTag must be executed in a server environment. You should implement a Server Action and call that action from your Client Component.

Does revalidatePath affect dynamic routes?

Yes. You can pass the specific path string for a dynamic route, such as /posts/[id], to revalidate that specific page. You can also use the type: 'layout' option if you need to revalidate all pages under a specific path segment.

Sources & further reading

About the author

Tushar Gautam is a full-stack & ai product engineer in India. He builds AI SaaS products such as an AI video ad generator, a RAG chatbot platform and a Razorpay-powered report service, mostly with Next.js, Flask and the Gemini API. See the projects, GitHub or LinkedIn.

This article was drafted with the help of AI (Google Gemini) from official documentation and published by Tushar Gautam. Spotted an error? Let me know.