You changed a row in the database, reloaded the page, and it still shows the old value. The server is running the newest code. You even restarted it. The page has not moved.
If the route exports revalidate, nothing is broken. You are looking at Incremental Static Regeneration doing exactly what it was told.
What `revalidate` actually does
export const revalidate = 300;This says: render this route, keep the rendered output, and serve that copy to everyone for the next 300 seconds without running the page function again.
The important word is output. Next.js is not caching your database query, your fetch, or anything inside the component. It is caching the finished HTML and the RSC payload, on disk, under .next. Your page function does not run, so your query does not run, so a change in the database is invisible until the window expires.
This is the whole point of ISR — it is why the page is fast — but it means "the database is the source of truth" stops being true for the length of the window.
Why restarting the server does not fix it
This is the part that wastes an afternoon.
Restarting the Node process feels like it should clear a cache, and for an in-memory cache it would. The ISR cache is not in memory. It is written under .next, and it survives the process that wrote it. Restarting gives you a new process reading the same stale files.
A rebuild does appear to fix it, which sends people down the wrong path. It works for an indirect reason: a build produces a new BUILD_ID, and the cache is keyed per build, so a new build starts with an empty namespace. You did not clear the cache, you moved to a different one.
So "restart it" is a non-fix, and "rebuild it" is a fix for the wrong reason — a full production build to publish a typo is an expensive way to delete a file.
The three things that do work
Wait for the window. The next request after the window expires triggers regeneration. Note the stale-while-revalidate behaviour: that first request is usually still served the old page while the new one renders in the background. So you can request the page, see stale content, and conclude the cache is stuck when it has already refreshed. Request twice before you believe it.
Revalidate on demand. If the change comes from your own admin UI or API route, this is the right answer:
import { revalidatePath } from "next/cache";
await prisma.article.update({ where: { id }, data });
revalidatePath("/blog");
revalidatePath(`/blog/${slug}`);The edit invalidates exactly the routes it affects, immediately, with no build and no waiting. revalidateTag does the same where you have tagged the underlying fetches.
Shorten or remove the window. If a route genuinely must be live, revalidate = 0 (or dynamic = "force-dynamic") opts it out. Be honest about the cost: every request now runs your queries.
Choosing a window
The question is not "how fresh do I want this" — everyone wants everything live. It is "how stale can this be before someone is misled".
A marketing page that changes monthly does not need a 60-second window. A listing that an editor publishes to and immediately checks probably wants on-demand revalidation rather than a short window, because a short window is still a coin flip on whether they see their own change.
The pattern worth reaching for: a longer window as the safety net, plus on-demand revalidation on the paths your own writes touch. You get static performance and edits that appear when they are made.
Diagnosing it quickly
When a page looks stale, answer these in order:
1. Does the route export `revalidate`? If not, ISR is not your problem — look at your data layer, a CDN, or the browser.
2. Does the served HTML actually contain the old value? Fetch it with curl rather than a browser. Browsers and service workers have their own caches and will happily confuse the diagnosis.
3. Has the window passed? If yes, request once more — the first request after expiry may still serve stale.
4. Is there a CDN in front? Then you have two caches, and the second one has its own rules regardless of what Next.js decided.
Step two catches the most embarrassing version of this bug, where the server is serving fresh content and you are looking at a page your browser cached.
The short version
revalidate caches rendered output on disk, keyed per build. A database write does not invalidate it, restarting the process does not clear it, and a rebuild only "works" because it changes the key. Use revalidatePath from the code that performs the write, keep a longer window as the backstop, and remember that the first request after expiry can still hand you the old page.