Revalidating Tags

Tagging and Revalidating Cached Entries

Time is a poor trigger for content that changes when somebody edits it. Tag such entries instead, give them a long lifetime, and invalidate them from the code that did the editing. cacheTag attaches labels to the entry being produced:

A tagged cached read (app/notes/data.js)JavaScript
export async function getNotes() {
  "use cache";
  cacheLife("max");
  cacheTag("notes");
  return { notes: listNotes(), at: new Date().toISOString() };
}

The Server Action that mutates the data clears the label afterwards:

Invalidating from a Server Action (app/notes/actions.js)JavaScript
"use server";
import { updateTag } from "next/cache";
import { addNote } from "./store";
export async function createNote(formData) {
  addNote(String(formData.get("text") || "Untitled"));
  updateTag("notes");
}

The page reads getNotes() and renders a form whose action is createNote. Reading twice, submitting, then reading twice more:

Output of 39
before  : last at 2026-09-22T05:57:55.295Z | Ship the release notes
before  : last at 2026-09-22T05:57:55.295Z | Ship the release notes
after   : last at 2026-09-22T05:58:19.996Z | Ship the release notes | Draft the changelog
after   : last at 2026-09-22T05:58:19.996Z | Ship the release notes | Draft the changelog

cacheLife("max") means a 30-day revalidate, so nothing but the tag could have moved that timestamp.

Three functions invalidate, and the choice matters:

Choosing an invalidation function
Function Where it runs Behavior
updateTag(tag) Server Actions only Expires immediately; the next read waits for fresh data
revalidateTag(tag, profile) Actions and Route Handlers Serves the stale entry for profile while regenerating
revalidatePath(path) Actions and Route Handlers Same, for every entry a route touches

Use updateTag after a mutation the same user is about to see: serving them a list without the note they just added would look broken. Use revalidateTag for a CMS webhook or an admin endpoint, where a few seconds of stale content costs nothing; pass 'max' as the second argument for the longest stale window. Tags are case-sensitive and capped at 256 characters. refresh() from next/cache is also Server-Action-only, but it refreshes the client router rather than expiring anything on the server.

Prefer tags over paths. A path invalidates everything a route rendered, including entries that were still correct; a tag names the data that changed, and one tag can cover several functions at once.