All writing
August 24, 20264 min read

Next.js Caching Mistakes That Never Look Like Errors

Every caching bug I have shipped rendered a perfectly valid page. The data was just old, or belonged to someone else, or the page did not exist yet.

Next.js caching is the part of the framework I have broken most often, and almost never in a way that showed up as an error. The page loads. The data is just old, or belongs to someone else, or the page does not exist yet according to a build that finished last Tuesday.

Everything below is a failure I have shipped, not a summary of the docs.

There is more than one cache, and clearing one does not clear the others

This is the root of most of it. A request can be served from a cached fetch, a cached rendered route, or a full route segment that was prerendered at build. They expire independently and they are invalidated by different calls.

So revalidateTag refreshing your data does not mean the page changes. If the rendered output is still cached, the fresh data has nowhere to go.

The habit I have now: when something does not update, ask which cache is holding it before changing any code. Half the time the code was already correct.

A 404 is a cached response like any other

This one cost me an afternoon recently.

I added pages to a CMS. The route uses generateStaticParams, so the deployed build knew a fixed list of slugs and the new ones were not on it. I purged the data cache, confirmed the CMS returned the new pages, and the URLs still returned 404.

The data was fine. The 404 rendered for a path the build had never generated was itself cached, and clearing the query cache did nothing to it. Nothing in the response tells you this: a cached 404 and a real 404 look identical.

If you add pages dynamically, purge the path as well as the tag. revalidateTag for the data, revalidatePath for the route.

A hardcoded date is a lie you tell crawlers

My sitemap carried lastModified: new Date("2026-08-20"), written once and forgotten. Every page claimed it had not changed since the day that line was typed.

Worse, the sitemap had no revalidate at all, so it was generated at deploy and frozen. Seven pages were added and announced nowhere. Both bugs were silent because a sitemap has no visible symptom: it does not error, it just quietly stops being true.

Read yours occasionally. Not the code, the actual XML.

The cache will happily serve one user's page to another

The frightening one, because it is silent and it is a data leak rather than a stale render.

A page rendered for a signed in user is, to the cache, just a page. Read a cookie in a Server Component and Next opts that route out of static rendering, which is what you want. Fetch user data through a path that never touches cookies and you can end up with one account's response served to another.

Test it rather than reasoning about it. Two browsers, two accounts, same route. Two minutes.

Pick the window deliberately

Most revalidate numbers I see were chosen once and never revisited. Mine were too: an hour on content that changes weekly, sixty seconds on a homepage that barely changes.

The question is not how fresh you want it. It is what the cost of being stale actually is. A blog index an hour behind is fine. A sitemap an hour behind delays discovery of a new page by an hour. A pricing page an hour behind is a support conversation.

Where staleness genuinely costs something, add a purge endpoint and call it when you publish, rather than shortening the window for every visitor.

The rule underneath all of it

Caching bugs do not announce themselves. Every failure here rendered a perfectly valid page, and the only reason I found them was going and looking at output I assumed was correct.

So look at the actual sitemap. Load the page in a second account. Check a URL you just added. The framework will not tell you, because from its point of view nothing went wrong.

Frequently asked questions

Why does my page still show old data after revalidateTag?

Because there is more than one cache. revalidateTag refreshes the data, but if the rendered route is still cached the fresh data has nowhere to go. Ask which cache is holding it before changing code.

Why do newly added CMS pages return 404?

The 404 is cached too. With generateStaticParams the deployed build knows a fixed list of slugs and the 404 rendered for a path it never generated is a cached response like any other. Purge the path with revalidatePath as well as the tag.

Can Next.js cache leak one user's page to another?

Yes, if user data is fetched through a path that never touches cookies. Reading a cookie in a Server Component opts the route out of static rendering, which is the behaviour you want. Test it with two accounts in two browsers rather than reasoning about it.