Next.js revalidate and revalidatePath Not Working? Tested
To see exactly when Next.js serves fresh data, I pointed some pages at a tiny API that returns a number that goes up by one on every request. If a page shows the same number twice, it was served from the cache. I tested on Next.js 16.4.4 with next build and next start, read the x-nextjs-cache response header, and then repeated it in next dev.
The pages
// r-default: no options
const { n } = await (await fetch("http://localhost:1470/")).json();
// r-revalidate: page-level
export const revalidate = 5;
// r-fetchrev: fetch-level
await fetch("http://localhost:1470/", { next: { revalidate: 5 } });
// r-posts/[slug]: generateStaticParams returns only "a"next build listed all of them as static (○ or ●), with "5s" in the Revalidate column for the two that set it. In my build all four prerendered pages showed n=1: the same URL was fetched once and the result reused.
1. revalidate serves the old page once, then updates
after more than 5 seconds:
r-revalidate n=2 x-nextjs-cache: STALE
r-revalidate n=5 x-nextjs-cache: HITThis is the most common "revalidate isn't working" report, and it's working as designed. After the 5 seconds, the next visitor still gets the old page (STALE) while Next.js rebuilds it in the background. The visitor after that gets the new one. If you test by refreshing once, you'll always see the old data. fetch with next: { revalidate: 5 } behaved the same way.
revalidate is also a minimum, not a timer. Nothing happens until a request arrives after it expires. A page nobody visits for a week keeps its old data for a week, and the first visitor after that sees it.
2. A page with no revalidate never updates
r-default had no options. It was built once, as a static page, and kept serving n=1 with x-nextjs-cache: HIT for as long as the server ran. In Next.js 15 and 16, fetch isn't cached by default, but that doesn't make the page dynamic: if nothing on the page is dynamic, the whole page is rendered at build time and that HTML is reused. Add revalidate, call revalidatePath when the data changes, or make the page dynamic.
3. revalidatePath with a dynamic route needs "page"
revalidatePath("/r-posts/[slug]") → /r-posts/a and /r-posts/b still HIT, old data
revalidatePath("/r-posts/[slug]", "page") → both MISS, fresh data
revalidatePath("/r-posts/a") → /r-posts/a fresh, /r-posts/b untouchedPassing the route pattern without the type did nothing at all, and there was no error or warning. With "page" as the second argument it cleared every page of that route. To clear one post, pass its real URL. The type is also what makes revalidatePath("/", "layout") work: that cleared every page in my app at once.
import { revalidatePath } from "next/cache";
revalidatePath("/blog/my-post"); // one page
revalidatePath("/blog/[slug]", "page"); // every post
revalidatePath("/", "layout"); // everythingA trailing slash didn't matter: revalidatePath("/r-default/") refreshed /r-default.
4. Pages that weren't prebuilt are cached on first visit
/r-posts/b wasn't in generateStaticParams. The first request rendered it (MISS, a new number), and every request after that was a HIT with the same number. On-demand pages are cached just like prebuilt ones, so they need revalidation too.
5. next dev hides all of this
next dev:
r-default n=16, then n=17
r-revalidate n=18, then n=19
r-fetchrev n=20, then n=20In development every page rendered again on every request, so the page-level settings looked fresh and caching bugs didn't show. The one exception was fetch with next.revalidate, which was cached in dev too. Test caching with next build && next start, never with next dev.
Checklist
- Refresh twice after the revalidate time: the first response is the stale one by design.
- No revalidate and no dynamic APIs means a static page that never changes.
- revalidatePath on a dynamic route: pass "page", or pass the real URL.
- Call revalidatePath from a server action or route handler that runs after the data changes, not from the page itself.
- Check x-nextjs-cache (HIT, STALE, MISS) in the response headers to see what happened.
- Test caching in a production build, not in next dev.
Related
- Next.js Dynamic server usage error, tested
- Next.js environment variables undefined
- generateMetadata and page fetching twice
- Next.js development