Every Next.js caching complaint I have ever debugged came down to not knowing which cache was involved. There are four. Each answers exactly one question.
The four layers
| Cache | Question it answers | Lives | Cleared by |
|---|---|---|---|
| Request memoization | "Did we already fetch this in this render?" | Server, per request | End of request |
| Data cache | "Did we already fetch this ever?" | Server, persistent | revalidateTag, time |
| Full route cache | "Did we already render this page?" | Server, persistent | Redeploy, revalidation |
| Router cache | "Did the browser already visit this?" | Client, in memory | Navigation, refresh |
Tag everything
Time-based revalidation is a guess. Tag-based revalidation is a fact:
export async function getPosts() {
const res = await fetch("https://api.example.com/posts", {
next: { tags: ["posts"] },
});
return res.json();
}"use server";
import { revalidateTag } from "next/cache";
export async function publishPost(data: FormData) {
await savePost(data);
revalidateTag("posts", "max");
}In Next.js 16,
revalidateTagtakes a cache profile as the second argument to enable stale-while-revalidate behavior."max"is the right default for content that changes rarely.
Static first, dynamic when forced
For a markdown blog like this one, the answer is simple: everything is known at build time, so everything should be in the full route cache.
export async function generateStaticParams() {
return getAllPosts().map((post) => ({ slug: post.slug }));
}No runtime rendering. No database. No cold starts on the read path. The fastest request is the one your server never sees.
Reading
cookies(),headers(), orsearchParamsopts a route out of static rendering. Audit your layouts — one carelesscookies()call in a shared layout makes every child route dynamic.
Debugging checklist
- Check the build output:
○means static,ƒmeans dynamic - A route unexpectedly dynamic? Search the tree for
cookies()/headers() - Stale data after mutation? You forgot
revalidateTag - Stale data after navigation? That is the client router cache — use
router.refresh()
Four caches, four questions. Once you name the layer, the fix is usually one line.
