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:

lib/posts.ts
export async function getPosts() {
  const res = await fetch("https://api.example.com/posts", {
    next: { tags: ["posts"] },
  });
  return res.json();
}
app/actions.ts
"use server";
 
import { revalidateTag } from "next/cache";
 
export async function publishPost(data: FormData) {
  await savePost(data);
  revalidateTag("posts", "max");
}

In Next.js 16, revalidateTag takes 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.

app/blog/[slug]/page.tsx
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(), or searchParams opts a route out of static rendering. Audit your layouts — one careless cookies() 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.