revalidatePath Might Not Be Invalidating What You Think It Is

1 13
calendar_today agoschedule3 min read

If you've ever called revalidatePath after a mutation, watched the data still show up stale on the page, and fixed it by just also calling revalidatePath on a few more related paths until it finally worked, you were probably fighting the actual behavior of the function rather than a bug in your own code. This one has a genuinely confusing default that catches a lot of people, myself included the first time I ran into it.

The Setup That Looks Like It Should Just Work

// actions/posts.ts
'use server';
import { revalidatePath } from 'next/cache';

export async function createPost(formData: FormData) {
  await Post.create({ title: formData.get('title') });
  revalidatePath('/blog');
}

A new post gets created, revalidatePath('/blog') runs, and yet the blog index sometimes still shows the old, stale list on the very next visit. Nothing throws an error. It just quietly doesn't update the way you'd expect, and the natural instinct is to assume the mutation itself failed, when the actual data was written correctly the whole time.

What's Actually Happening

revalidatePath takes an optional second argument, 'page' or 'layout', and it changes what actually gets invalidated:

revalidatePath('/blog');              // defaults to invalidating just this specific path
revalidatePath('/blog', 'page');      // same as above, explicit
revalidatePath('/blog', 'layout');    // invalidates this path AND all paths sharing its layout

Without the second argument, revalidatePath('/blog') only invalidates that exact path. If your blog index is actually rendered through a layout that also affects nested routes, /blog/[slug], and you're expecting a change on the index to also refresh those nested pages, the default page-scoped call doesn't do that. You need the 'layout' variant explicitly if the invalidation is meant to cascade.

An Even More Common Gotcha: Dynamic Segments

// This does NOT invalidate /blog/my-first-post or any other specific slug
revalidatePath('/blog/[slug]');

// This DOES invalidate the specific rendered path
revalidatePath('/blog/my-first-post');

Passing the literal route pattern, [slug] included as text, does not magically invalidate every dynamically rendered version of that route. revalidatePath needs the actual, real path that was rendered, not the file-system pattern that generated it. If you're revalidating a specific post after an edit, you need the specific slug, not the dynamic segment's placeholder syntax.

Why This Is Genuinely Confusing to Debug

The failure mode here is not an error, it's stale data that sometimes seems to resolve itself anyway, since a full page reload, a different route triggering revalidation elsewhere, or the cache eventually expiring on its own can mask the actual bug intermittently. This makes it feel inconsistent and hard to reproduce reliably, when the actual rule underneath it is completely deterministic, it's just not the rule most people assume by default.

When to Reach for revalidateTag Instead

For anything genuinely complex, several routes that share underlying data but don't share a clean path or layout relationship, revalidateTag is usually the more precise, more maintainable tool, tagging the actual data dependency rather than trying to map out every path relationship by hand.

// lib/queries/posts.ts
export const getPosts = unstable_cache(
  async () => Post.find().sort({ createdAt: -1 }).lean(),
  ['posts-list'],
  { tags: ['posts'] }
);

export const getPost = unstable_cache(
  async (slug: string) => Post.findOne({ slug }).lean(),
  ['post-detail'],
  { tags: ['posts'] }
);
// actions/posts.ts
'use server';
export async function createPost(formData: FormData) {
  await Post.create({ title: formData.get('title') });
  revalidateTag('posts'); // invalidates every cached query tagged 'posts', regardless of path
}

This sidesteps the entire path-matching question, since it invalidates based on what data was actually affected, not which specific URL path happens to render that data.

The Actual Checklist

If you need a change to cascade to nested routes under a shared layout, pass 'layout' explicitly, the default won't do this for you.

Never pass a route pattern with the literal [param] syntax expecting it to match every rendered instance. Pass the actual, specific path that was rendered.

For data shared across multiple, not-cleanly-related paths, reach for revalidateTag instead of trying to enumerate every affected path by hand with revalidatePath.

If a page still seems stale after revalidation, check whether the actual issue is a missing 'layout' argument or a route pattern being passed literally, before assuming the underlying mutation itself failed.


If you've hit stale data after a revalidatePath call and eventually "fixed" it by adding a few more calls until it worked, genuinely curious whether it turned out to be one of these two issues, or something else entirely. Drop what actually fixed it in the comments.

Get the templates: https://pixelanas.gumroad.com


Anas, full-stack Next.js developer building SaaS products and premium templates. X: @ASheikh69751

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelski - Apr 23

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelski - Mar 19

Your Backup Data Knows More Than You Think. HYCU aiR Is Finally Asking It the Right Questions.

Tom Smithverified - May 14

Cisco's Amy Chang: A Model's "Passport" Doesn't Tell You Where It Actually Came From

Tom Smithverified - Aug 27

Merancang Backend Bisnis ISP: API Pelanggan, Paket Internet, Invoice, dan Tiket Support

Masbadar - Mar 13
chevron_left
360 Points14 Badges
11Posts
2Comments
1Connections
Full-stack developer building premium Next.js templates & web apps. Specializing in GSAP animations,... Show more

Related Jobs

View all jobs →

Commenters (This Week)

2 comments
2 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!