Tag, then invalidate

updateTag for your own write, revalidateTag in the background.

From next-cache-components, “Cache Invalidation”.

Save, then refresh

Change the price, save, then refresh each side.

Database: $20
revalidateTagBad here
Desk lamp/products/7
$20HIT
    stale after save
    updateTagGood
    Desk lamp/products/7
    $20HIT
      fresh after save
      // lib/products.ts
      import { cacheTag } from 'next/cache'
      
      export async function getProduct(id: string) {
        'use cache'
        cacheTag('products', `product-${id}`)
        return db.products.findUnique({ where: { id } })
      }
      
      // app/products/[id]/actions.ts
      'use server'
      import { updateTag } from 'next/cache'
      import { redirect } from 'next/navigation'
      
      export async function updateProduct(id: string, data: FormData) {
        await db.products.update({ where: { id }, data })
        updateTag(`product-${id}`)
        redirect(`/products/${id}`)
      }

      Blast radius of one edit

      Hover to preview; tap or click to add $5.

      One broad tagBad
      updateTag('products')
      Broad + per-item tagsGood
      updateTag(`product-${id}`)
      async function getProducts() {
        'use cache'
        const items = await db.products.findMany()
        // The list shows every item, so it carries every item's tag too.
        cacheTag('products', ...items.map((p) => `product-${p.id}`))
        return items
      }
      
      async function getProduct(id: string) {
        'use cache'
        cacheTag('products', `product-${id}`)
        return db.products.findUnique({ where: { id } })
      }

      Type the invalidation call

      Type a call or pick an example.

      'use server'
      import { revalidateTag, updateTag } from 'next/cache'
      
      export async function createPost(data: FormData) {
        await db.posts.create({ data })
        revalidateTag('posts', 'max')
      }
      
      export async function updateProduct(id: string, data: FormData) {
        await db.products.update({ where: { id }, data })
        updateTag(`product-${id}`)
      }

      Why it matters, pros and cons

      Why it matters

      A tag names data, not routes, so one write reaches every cache that read it.

      Pros

      • Per-item tags evict one entry
      • updateTag shows your own write
      • revalidateTag never blocks a request

      Cons

      • updateTag only works inside a Server Action
      • Lists go stale unless they carry item tags
      • A missed tag fails silently