typescriptadvanced

Cache Tags and On-Demand Revalidation

Tag cached data with identifiers and revalidate specific cache entries on demand.

typescript
// Fetch with cache tags
async function getPost(id: string) {
  const res = await fetch(`https://api.example.com/posts/${id}`, {
    next: {
      tags: [`post-${id}`, 'posts'],
      revalidate: 3600,
    },
  });
  return res.json();
}

async function getComments(postId: string) {
  const res = await fetch(`https://api.example.com/posts/${postId}/comments`, {
    next: {
      tags: [`comments-${postId}`, 'comments'],
    },
  });
  return res.json();
}

// On-demand revalidation via Server Action
import { revalidateTag, revalidatePath } from 'next/cache';

export async function updatePost(id: string, data: FormData) {
  'use server';
  await fetch(`https://api.example.com/posts/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(Object.fromEntries(data)),
  });

  // Revalidate specific post cache
  revalidateTag(`post-${id}`);
  // Also revalidate its comments
  revalidateTag(`comments-${id}`);
}

export async function publishPost(id: string) {
  'use server';
  await fetch(`https://api.example.com/posts/${id}/publish`, { method: 'POST' });
  revalidateTag(`post-${id}`);
  revalidateTag('posts');  // Revalidate all post listings
  revalidatePath('/blog'); // Revalidate full page
}

// Revalidation API Route
// app/api/revalidate/route.ts
import { NextRequest } from 'next/server';

export async function POST(request: NextRequest) {
  const { tag, secret } = await request.json();
  if (secret !== process.env.REVALIDATION_SECRET) {
    return Response.json({ error: 'Invalid secret' }, { status: 401 });
  }
  revalidateTag(tag);
  return Response.json({ revalidated: true, tag });
}

Use Cases

  • CMS webhooks
  • selective cache purge
  • real-time content updates

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.