cacheTag()

Label a cached entry so tag-based invalidation can purge it on demand.

Signature
cacheTag(...tags: string[]): void

Parameters

ParameterTypeDescription
$tags...string[]One or more tag strings to attach to the current cache entry. A single call accepts up to 128 tags, each at most 256 characters; over-long tags are skipped and tags past the 128th in one call are dropped, both with a console warning. Applying the same tag more than once has no additional effect.

Return value

voidNothing. The tags are attached to the enclosing cache entry.

Errors

Throws when called outside a cache directive scope; requires cacheComponents to be enabled in next.config.

cacheTag is the 'use cache' equivalent of fetch's next.tags: it labels the entry the current scope produces so updateTag or revalidateTag can purge it later without knowing which routes read it. Requires cacheComponents in next.config.

How to name tags

Two tiers cover almost everything: a collection tag such as posts for anything that lists, and an entity tag such as post-42 for the detail view. A mutation then expires both and every page agrees. Because the call sits inside the cached function, tags can be derived from the data the function just fetched — useful when the identifier only exists after the response comes back.

What trips people up

  • Tags are strings matched exactly and are case-sensitive. A mismatch between cacheTag and the invalidating call fails silently — nothing errors, the data just never refreshes.
  • Tagging alone caches nothing. Without a directive there is no entry to tag, and without cacheLife the entry quietly uses the default profile.
  • A tag over 256 characters is skipped rather than truncated, so a tag built from a long URL or serialized object may never be attached at all.

Example

Example · typescript
import { cacheTag, cacheLife } from 'next/cache'

export async function getData() {
  'use cache'
  cacheTag('my-data')
  cacheLife('max')

  const res = await fetch('https://api.example.com/data')
  const data = await res.json()
  return data
}

When to use it

  • Tag a cached product query with both 'products' and 'product-{id}' so a write can purge either scope.
  • Derive a tag from the response body when the identifier is only known after fetching.
  • Give a cached component a tag so a CMS webhook can refresh every page that renders it.

Frequently asked questions

What is the difference between cacheTag and fetch's next.tags?

They attach the same kind of cache tag to different things: next.tags tags the entry created by one fetch call, while cacheTag tags the entry created by the enclosing 'use cache' scope, whatever it computed. Both are invalidated by revalidateTag and updateTag.

How many tags can one cache entry have?

A single cacheTag() call accepts up to 128 tags, each at most 256 characters. Tags longer than 256 characters are skipped, and tags past the 128th in one call are dropped; both cases log a console warning rather than throwing.

Why does revalidateTag do nothing for my cached function?

Usually the tag string does not match exactly — tags are case-sensitive — or the entry was never tagged because the cacheTag call sits outside the cached scope. A tag over 256 characters is also never attached, so revalidating it has no effect.

More examples

Collection and entity tags

Lives at lib/products.ts. A price change to one item expires product-42 and leaves the rest of the catalog cached; a bulk import expires products and clears them all. Passing both tags in one call is equivalent to two calls.

Example · typescript
import { cacheTag, cacheLife } from 'next/cache'

export async function getProduct(id: string) {
  'use cache'
  // One call, two tags: broad and specific
  cacheTag('products', `product-${id}`)
  cacheLife('days')

  return db.products.find({ where: { id } })
}

Tagging with data from the response

Lives at app/components/bookings.tsx. The tag is applied after the await, using an id that did not exist before the request. This is why cacheTag is a call inside the scope rather than an option on the directive.

Example · typescript
import { cacheTag } from 'next/cache'

export async function Bookings({ type = 'haircut' }: { type: string }) {
  async function getBookingsData() {
    'use cache'
    const response = await fetch(
      `https://api.example.com/bookings?type=${encodeURIComponent(type)}`
    )
    const data = await response.json()
    cacheTag('bookings-data', data.id)
    return data
  }

  const bookings = await getBookingsData()
  return <div>{bookings.id}</div>
}

Invalidating the tags

Lives at app/actions.ts. The same tags feed both invalidation styles. The choice is about who is waiting: updateTag blocks the next read so the user sees their own write, revalidateTag keeps serving the cached copy in the background.

Example · typescript
'use server'

import { updateTag, revalidateTag } from 'next/cache'

export async function saveProduct(id: string) {
  await db.products.update({ where: { id }, data: { updated: true } })

  // A user is waiting on this write: expire and refresh now
  updateTag(`product-${id}`)
}

export async function nightlyImport() {
  await importCatalog()

  // Background job: serve stale while the new copy is built
  revalidateTag('products', 'max')
}

Discussion

  • Be the first to comment on this lesson.