Type something to search...
Next.js updateTag

Next.js updateTag

There's a specific kind of bug that makes an app feel broken even when nothing is technically wrong: a user submits a form, the page redirects to show their new data, and the new data isn't there. They see the old list, refresh out of confusion, and only then does their change appear. Nothing crashed. The cache just didn't know to let go yet.

updateTag exists to close that gap. It's a Server Action-only function for immediately invalidating cached data tagged with a specific string, and it's built around a narrower, more demanding guarantee than Next.js's other cache-invalidation tools: the very next request after you call it will not see stale data. Not "probably won't." Won't.

The Read-Your-Own-Writes Problem

The technical term for what users expect after a form submission is read-your-own-writes consistency — if I just wrote something, the next thing I read should reflect it. It sounds like an obviously correct default, but caching systems generally don't give it to you for free, because the whole point of caching is to avoid doing work on every request. Most cache invalidation strategies optimize for eventual consistency: invalidate the cache, but let the current request (and maybe the next few) serve slightly stale data while fresh data gets fetched in the background. That's usually the right tradeoff for a homepage or a product listing. It's the wrong tradeoff for "did my post actually get created."

updateTag is Next.js's answer to that second case specifically. It doesn't try to be a general-purpose revalidation tool — for that, you already have revalidateTag. It solves one problem: make sure this Server Action's own consequences are visible immediately, to the user who triggered them.

Usage

updateTag(tag: string): void

updateTag takes a single argument — a case-sensitive string tag, capped at 256 characters — and returns nothing. Its restriction is what makes it different from everything else in the caching toolkit:

updateTag can only be called from within a Server Action. Calling it from a Route Handler, a Client Component, or anywhere else throws an error.

That's not an incidental limitation, it's the point. A Server Action has a request/response cycle: the user submits it, waits, and gets a result — usually a redirect or a re-render of the calling page. updateTag piggybacks on that waiting period to guarantee the invalidation completes before the user sees anything. A Route Handler backing a webhook has no equivalent "the user is waiting right here" moment, which is exactly why that context uses revalidateTag instead, with its softer, background-refresh semantics.

Tagging Data So There's Something to Invalidate

updateTag invalidates cache entries by tag, but tags don't exist on their own — you have to assign them when the data is first cached. There are two ways to do that, matching the two caching primitives in the App Router:

With fetch, via the next.tags option:

async function getPosts() {
  const res = await fetch("https://api.example.com/posts", {
    next: { tags: ["posts"] },
  });
  return res.json();
}

With cacheTag, inside a function or component marked 'use cache':

import { cacheTag } from "next/cache";

async function getData() {
  "use cache";
  cacheTag("posts");
  return db.post.findMany();
}

Either way, the tag is just a label. Next.js doesn't care what the string means to you — it only cares that when you call updateTag('posts'), every cache entry anywhere in the app that was tagged 'posts' gets treated as invalid, regardless of which page or component originally requested it. This is what makes tag-based invalidation more powerful than path-based invalidation (revalidatePath): one call can clear a homepage feed, a category page, and a sitemap generator simultaneously, as long as they all pulled from data tagged the same way.

A Complete Worked Example

The canonical use case is exactly what it sounds like — create something, then make sure the create is visible before the redirect lands:

// app/actions.ts
"use server";

import { updateTag } from "next/cache";
import { redirect } from "next/navigation";

export async function createPost(formData: FormData) {
  const title = formData.get("title") as string;
  const content = formData.get("content") as string;

  const post = await db.post.create({
    data: { title, content },
  });

  // Invalidate the list view...
  updateTag("posts");
  // ...and the specific post detail view, separately
  updateTag(`post-${post.id}`);

  redirect(`/posts/${post.id}`);
}

Notice the two separate tags. This is a pattern worth internalizing: a single mutation often needs to invalidate more than one "shape" of cached data — the collection view and the individual-item view are two different caches even though they're derived from the same underlying row. Forgetting the second tag is a common way updateTag "doesn't work": the list refreshes, but the detail page a user lands on via redirect() still shows a cache miss's worth of nothing, or an older version if one existed.

Because redirect() throws internally to unwind the Server Action, updateTag must run before the redirect call, not after — code placed after redirect() in the same function is unreachable, exactly as it is with notFound() or any other Next.js navigation function.

updateTag vs. revalidateTag

These two are easy to mix up because they both take a tag string and both invalidate cached data. The difference is entirely in the guarantee, not the mechanism:

updateTagrevalidateTag
Where it can runServer Actions onlyServer Actions and Route Handlers
Freshness guaranteeNext request always gets fresh dataWith a "max" profile, may serve stale data briefly while refreshing in the background
Design intentRead-your-own-writes for the acting userGeneral-purpose invalidation, including from webhooks and external triggers
Typical triggerA user's own form submissionA CMS webhook, a scheduled job, an admin action affecting other users

A useful mental shortcut: if the person who triggered the invalidation is also the person who needs to see the result on the very next screen, reach for updateTag. If the invalidation is happening on someone's behalf — a CMS editor publishing content that other visitors will eventually see — revalidateTag with a sensible cache profile is the better tool, because it avoids making every visitor's request pay the cost of a synchronous cache miss just because one editor made a change.

Calling updateTag from the wrong place surfaces immediately and loudly:

// app/api/posts/route.ts
import { updateTag } from "next/cache";

export async function POST() {
  updateTag("posts");
  // Error: updateTag can only be called from within a Server Action
}

There's no silent fallback here, which is a deliberate design choice — a caching function that quietly does the wrong thing in the wrong context would be far worse than one that fails the build or the request outright.

Common Mistakes

Tagging the write path but not the read path. updateTag('posts') does nothing if nothing was ever cached with tags: ['posts'] or cacheTag('posts') in the first place. The tag has to exist on both ends of the relationship.

Invalidating only one shape of the data. As in the example above, a single database row is often rendered through multiple differently-cached views (a list, a detail page, a sidebar widget, a sitemap). Each one needs its own tag invalidated, or its own shared tag, deliberately.

Reaching for updateTag out of habit in a Route Handler. If your invalidation logic lives behind an API route — for a webhook, an admin panel, or a cron job — that's a revalidateTag job by definition; updateTag simply isn't available there.

Assuming it revalidates a whole page. updateTag clears specific cache entries by tag, not an entire route the way revalidatePath does. If your page composes several independently cached pieces, only the ones carrying the tag you invalidated actually refresh.

Forgetting it's synchronous from the caller's perspective. Because the guarantee is "the next request gets fresh data," a Server Action that calls updateTag and then does a lot of additional slow work before redirecting is delaying the user for no caching-related reason — the invalidation itself is what needed to happen inline, not the rest of the action.

Key Takeaways

QuestionAnswer
What does updateTag do?Immediately invalidates all cache entries tagged with the given string
Where can I call it?Only inside a Server Action
What happens if I call it elsewhere?It throws an error at call time
How do things get a tag in the first place?fetch(url, { next: { tags: [...] } }) or cacheTag(...) inside a 'use cache' function
When should I use it over revalidateTag?When the acting user needs to see their own change on the very next request — read-your-own-writes
When should I use revalidateTag instead?Route Handlers, webhooks, and any invalidation on behalf of other users where stale-while-revalidate is acceptable

updateTag is a narrow tool for a narrow, very human problem: making sure the app doesn't lie to the person who just changed something in it. Everything else about cache freshness — background revalidation, time-based expiry, invalidation from the outside world — belongs to revalidateTag and its relatives. Keep that boundary clear and the two functions stop being confusing and start being complementary.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper