Lesson 12 of 81
A cache is only as good as your ability to say 'this is wrong now, fix it' the moment data changes. Next.js gives you three different ways to say that, and picking the wrong one either makes users wait or shows them stale data they just personally changed.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A Server Action calls `revalidateTag('posts', 'max')` right after creating a new post. A different visitor is currently looking at the cached blog listing page, which uses the 'posts' tag. What happens to their view?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Cached data eventually goes stale — someone edits a post, deletes a comment, updates a price. On-demand revalidation is how you tell Next.js "this specific cached thing is wrong now" the instant it happens, instead of waiting for a timer to expire. There are three tools, each with a different tradeoff between speed and freshness:
revalidateTag(tag, profile) — marks every cache entry with that tag stale. With profile: 'max' (the recommended default), it uses stale-while-revalidate semantics: readers keep getting the cached version while a fresh one is fetched in the background, and the swap happens the next time a tagged page is visited, not instantly everywhere at once.revalidatePath(path) — invalidates one specific page or layout path directly, by its route file location, not by tag.updateTag(tag) — expires a tag's cache immediately. The very next request for that tag blocks until fresh data is fetched. This is the tool for read-your-own-writes: the person who just made the change needs to see it, right now, not a beat later.All three only run in Server Functions (Server Actions) and Route Handlers — never in Client Components, and never in proxy.ts, since they depend on server-only cache internals.
// app/posts/[id]/actions.ts
"use server";
import { revalidateTag, updateTag } from "next/cache";
export async function publishComment(postId: string, formData: FormData) {
const content = formData.get("content") as string;
const comment = await db.comment.create({ data: { postId, content } });
// Every other visitor's cached comment count can catch up on their next visit
revalidateTag("comment-counts", "max");
// The commenter needs their own comment to show up right now
updateTag(`comments-${postId}`);
return comment;
}
revalidateTag("comment-counts", "max") is for content where a slight delay is genuinely fine — a sitewide comment-count badge doesn't need to update mid-request for a stranger browsing a different page. updateTag(\comments-$`)` is for the one thing that can't wait: the person who just wrote the comment reloading the thread and seeing it missing would look broken, even though technically "the cache just hasn't caught up yet."
revalidateTag(tag, 'max') doesn't refetch everywhere the instant you call it. It marks the tag stale; the actual background refresh happens the next time a page using that tag is visited. Calling it doesn't trigger a burst of refetches across the whole site.updateTag only works inside Server Actions — not Route Handlers. A webhook or third-party callback that needs immediate expiry has to use revalidateTag(tag, { expire: 0 }) instead; calling updateTag from a Route Handler throws.revalidatePath and revalidateTag solve different scopes. Revalidating /blog doesn't revalidate a dashboard page that happens to fetch the same tagged data — the dashboard's own tag has to be invalidated separately, or it keeps serving stale content.proxy.ts can't call any of the three — they only run inside Server Functions and Route Handlers, full stop.fetch needs next: { tags: ['posts'] }, or a 'use cache' function needs cacheTag('posts') called inside it — revalidating a tag nothing was ever tagged with is a silent no-op."Your app has both a 'my own edit should appear instantly' screen and a 'this can be a little stale' public listing — how would you invalidate each?" tests whether a candidate reaches for the same revalidation call everywhere, or actually reasons about the read-your-own-writes vs. eventual-consistency tradeoff each screen needs.