Caching makes Next.js fast, but a cache you cannot clear is a bug factory: an editor publishes a post and the site keeps showing the old list. This lesson covers the tools that keep cached data correct. After it you will be able to expire cached data on a timer or on demand with revalidatePath and revalidateTag, and cache any async function with the "use cache" directive.
| Cache | Lives | Stores | Cleared by |
|---|---|---|---|
| Request memoization | One render pass | Identical fetch calls | Automatically after the render |
| Data Cache | Server, across requests | fetch results and "use cache" output | revalidatePath, revalidateTag, time |
| Full Route Cache | Server, across requests | Static HTML and RSC payload | Same as Data Cache |
| Router Cache | Browser | Visited route payloads | router.refresh(), navigation, actions |
Time-based expiry (revalidate) was covered earlier; here we focus on on-demand revalidation and the newer caching model.
revalidatePath marks a route's cached data and HTML as stale. The next visit regenerates it. It is typically called from a Server Action or a Route Handler after a write:
// app/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db";
export async function addComment(postId: string, text: string) {
await db.comment.create({ data: { postId, text } });
revalidatePath(`/posts/${postId}`); // this page only
}Pass "page" (the default) to target one route or "layout" to include every child route.
Paths are a blunt instrument when the same data appears in ten places. Tags let you name data and purge every consumer at once. Attach tags to fetch calls:
const res = await fetch("https://api.example.com/posts", {
next: { tags: ["posts"] },
});Then, wherever the data changes:
import { revalidateTag } from "next/cache";
revalidateTag("posts", "max");The second argument, introduced in Next.js 16, is a cache-life profile that controls how the stale entry is served while fresh data loads in the background; "max" is the usual choice. Inside a Server Action, updateTag("posts") expires the entry immediately so the same request can read its own write.
fetch options only cover HTTP calls. Database queries, file reads and expensive computations need something else. Enable Cache Components in next.config.ts:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;Now any async function, component or whole file can opt into caching with a directive, as shown in the sample at the top of this lesson. Two helpers refine it:
cacheLife("seconds" | "minutes" | "hours" | "days" | "weeks" | "max") sets how long the result is reused; a custom object with stale, revalidate and expire also works.cacheTag("name") connects the entry to revalidateTag.The directive replaces the older unstable_cache helper. With Cache Components enabled, Next.js also pre-renders a static shell for every route and streams only the parts that read request data, so any component that touches cookies(), headers() or uncached data must sit inside a <Suspense> boundary; the build reports an error if it does not.
revalidatePath during render instead of inside an action or handler; it only takes effect after a mutation."use cache" functions receive serializable arguments only; the arguments form the cache key.revalidateTag to refresh the browser: the Router Cache updates after navigation, router.refresh() or a completed Server Action.Which call purges every cached `fetch` result tagged `"products"`?
revalidatePath purges a route (or a whole layout subtree); revalidateTag purges named data anywhere it is used.fetch calls with next: { tags } and cached functions with cacheTag()."use cache" with cacheLife caches any async work once cacheComponents is enabled in next.config.ts.<Suspense> so a static shell can be pre-rendered.Next lesson: Streaming and Suspense ā send the fast parts of a page first and stream the slow parts as they resolve.