Revalidate Tag — Tag-Based Revalidation for Grouped Content
In this tutorial, you will learn about Revalidate Tag. We cover key concepts, practical examples, and best practices to help you master this topic.
Revalidate tags let you group related pages together, so updating one CMS entry triggers revalidation of all pages sharing that tag.
What You'll Learn
By the end of this tutorial, you'll understand how tag-based revalidation works in Next.js, how to assign tags to pages, how to revalidate by tag, and how to design tag hierarchies for efficient content updates.
Why It Matters
Path-based revalidation requires knowing every URL that needs updating. When a category name changes, you need to update every product page in that category. Tag-based revalidation handles this automatically by grouping related pages.
Real-World Use
A CMS editor updates the featured products collection. Instead of listing every product URL to revalidate, the editor triggers revalidation for the tag "featured". All 50 product pages and the homepage featuring them regenerate automatically.
Tag-Based Revalidation Flow
graph TD
A[Content Change] --> B[API Route receives
revalidation request]
B --> C{Revalidate by
tag?}
C -->|Yes| D[Look up all pages
with this tag]
C -->|No| E[Revalidate
specific paths]
D --> F[Revalidate
page 1]
D --> G[Revalidate
page 2]
D --> H[Revalidate
page N]
F --> I[All tagged pages
regenerated]
G --> I
H --> I
E --> I
I --> J[CDN cache updated]
style B fill:#4a90d9,color:#fff
style D fill:#e67e22,color:#fff
style I fill:#27ae60,color:#fff
Next.js Tag-Based Revalidation
// pages/products/[id].js — Tag-based ISR
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/products/${params.id}`);
const product = await res.json();
return {
props: { product },
revalidate: 60,
// Tags that group related pages
tags: [
'products',
`category:${product.category}`,
`brand:${product.brand}`,
product.featured ? 'featured' : null
].filter(Boolean)
};
}
// pages/api/revalidate-by-tag.js — Revalidate by tag
export default async function handler(req, res) {
const { secret, tag } = req.body;
if (secret !== process.env.REVALIDATION_TOKEN) {
return res.status(401).json({ message: 'Invalid token' });
}
try {
// Revalidate all pages with the specified tag
await res.revalidate({
type: 'tag',
tag
});
console.log(`Revalidated all pages tagged: ${tag}`);
res.json({ revalidated: true, tag });
} catch (err) {
console.error(`Tag revalidation failed: ${tag}`, err);
res.status(500).json({ error: err.message });
}
}
Tag Hierarchy Design
// tag-hierarchy.js — Structured tag system
const tagHierarchy = {
// Content type tags
types: {
post: 'type:post',
product: 'type:product',
page: 'type:page'
},
// Section tags
sections: {
blog: 'section:blog',
shop: 'section:shop',
docs: 'section:docs'
},
// Category tags
categories: {
electronics: 'category:electronics',
clothing: 'category:clothing',
food: 'category:food'
},
// Feature tags
features: {
featured: 'feature:featured',
sale: 'feature:sale',
new: 'feature:new'
}
};
// Helper to generate tags for a product page
function getProductTags(product) {
return [
'type:product',
'section:shop',
`category:${product.category}`,
`brand:${product.brand}`,
product.isFeatured && 'feature:featured',
product.isOnSale && 'feature:sale',
product.isNew && 'feature:new'
].filter(Boolean);
}
// Helper to generate tags for a blog post
function getBlogTags(post) {
return [
'type:post',
'section:blog',
...post.categories.map(c => `category:${c}`),
...post.tags.map(t => `tag:${t}`)
];
}
Multi-Tag Revalidation
// pages/api/revalidate-multi.js — Revalidate multiple tags
export default async function handler(req, res) {
const { secret, tags, source } = req.body;
if (secret !== process.env.REVALIDATION_TOKEN) {
return res.status(401).json({ message: 'Invalid token' });
}
const results = [];
for (const tag of tags) {
try {
await res.revalidate({ type: 'tag', tag });
results.push({ tag, status: 'success' });
console.log(`Tag revalidated: ${tag}`);
} catch (err) {
results.push({ tag, status: 'failed', error: err.message });
console.error(`Tag revalidation failed: ${tag}`, err);
}
}
res.json({
revalidated: true,
source: source || 'unknown',
results,
time: Date.now()
});
}
// Webhook handler for CMS
export async function cmsWebhookHandler(req, res) {
const payload = req.body;
// Determine which tags to revalidate based on the change
const tagsToRevalidate = [];
if (payload.contentType === 'product') {
tagsToRevalidate.push(
'type:product',
`category:${payload.fields.category}`,
'section:shop'
);
if (payload.fields.isFeatured) {
tagsToRevalidate.push('feature:featured');
}
}
if (payload.contentType === 'category') {
// Category change affects all products in that category
tagsToRevalidate.push(
'type:product',
`category:${payload.fields.slug}`,
'section:shop'
);
}
// Forward to revalidation endpoint
const response = await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/revalidate-multi`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
secret: process.env.REVALIDATION_TOKEN,
tags: tagsToRevalidate,
source: 'cms-webhook'
})
}
);
const result = await response.json();
res.json(result);
}
Tag Registry
// lib/tag-registry.js — Track which pages have which tags
// This can be stored in Redis, a database, or a JSON file
class TagRegistry {
constructor() {
this.tagToPaths = new Map();
this.pathToTags = new Map();
}
register(slug, tags) {
// Store tags for this path
this.pathToTags.set(slug, tags);
// Store path for each tag
for (const tag of tags) {
if (!this.tagToPaths.has(tag)) {
this.tagToPaths.set(tag, new Set());
}
this.tagToPaths.get(tag).add(slug);
}
}
getPathsByTag(tag) {
return this.tagToPaths.get(tag) || new Set();
}
getTagsByPath(slug) {
return this.pathToTags.get(slug) || [];
}
getAllPaths() {
const allPaths = new Set();
for (const paths of this.tagToPaths.values()) {
for (const path of paths) {
allPaths.add(path);
}
}
return allPaths;
}
remove(slug) {
const tags = this.pathToTags.get(slug);
if (tags) {
for (const tag of tags) {
this.tagToPaths.get(tag)?.delete(slug);
}
this.pathToTags.delete(slug);
}
}
getStats() {
const tagCounts = {};
for (const [tag, paths] of this.tagToPaths.entries()) {
tagCounts[tag] = paths.size;
}
return {
totalTags: this.tagToPaths.size,
totalPaths: this.pathToTags.size,
tagCounts
};
}
}
export const registry = new TagRegistry();
Common Mistakes
- Using too many unique tags. Tags like
product:abc123(one per product) defeat the purpose. Tags should group multiple pages. - Not cleaning up stale tags. When a page is deleted, its tags should be removed from the registry. Otherwise, tag-based revalidation attempts to revalidate deleted pages.
- Overlapping tag scopes. If every page has the tag "all", revalidating that tag rebuilds the entire site. Design tag hierarchies carefully.
- Mixing content tags with metadata tags. Tags for revalidation should be separate from content tags for display. Don't use blog post tags as revalidation tags.
- Not documenting the tag system. Team members need to know which tags to use. Document the tag hierarchy and when each tag should be applied.
Practice Questions
- How do revalidation tags differ from path-based revalidation?
- How do you design a tag hierarchy for a product catalog?
- How do you revalidate all pages with a specific tag?
- What happens when a tag matches hundreds of pages?
- How do you handle tag cleanup when content is deleted?
Challenge: Build a product catalog with tag-based revalidation: create 3 categories (electronics, clothing, food) and 5 products each, assign tags for category and featured status, implement tag-based revalidation, and verify that updating one category triggers all its products to regenerate.
FAQ
Mini Project
Create a tag-based revalidation system for an e-commerce site: define a tag hierarchy (types, sections, categories, features), assign tags to 50+ product pages, implement a tag revalidation API, build a tag management dashboard, and test revalidation by triggering tag updates.
What's Next
You've mastered tag-based revalidation. Now explore Revalidate Path for precise path-based revalidation of specific pages.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro