cacheTag()
Label a cached entry so tag-based invalidation can purge it on demand.
cacheTag(...tags: string[]): voidParameters
| Parameter | Type | Description |
|---|---|---|
$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
cacheTagand 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
defaultprofile. - 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
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.
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.
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.
'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