Skip to content

Revalidate Tag — Tag-Based Revalidation for Grouped Content

DodaTech Updated 2026-06-28 6 min read

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

  1. Using too many unique tags. Tags like product:abc123 (one per product) defeat the purpose. Tags should group multiple pages.
  2. 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.
  3. Overlapping tag scopes. If every page has the tag "all", revalidating that tag rebuilds the entire site. Design tag hierarchies carefully.
  4. 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.
  5. 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

  1. How do revalidation tags differ from path-based revalidation?
  2. How do you design a tag hierarchy for a product catalog?
  3. How do you revalidate all pages with a specific tag?
  4. What happens when a tag matches hundreds of pages?
  5. 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

Is tag-based revalidation available in Next.js?

Yes, via the tags property in getStaticProps and res.revalidate({ type: 'tag', tag }). Available in Next.js 14+.

Can I combine path and tag revalidation?

Yes. You can call res.revalidate('/path') and res.revalidate({ type: 'tag', tag }) in the same handler. This gives you precise control.

How many tags should I use per page?

3-8 tags is ideal. Each tag should represent a meaningful grouping. Too few tags misses grouping opportunities. Too many creates maintenance overhead.

What happens if I revalidate a tag that doesn't exist?

Nothing happens. No pages are revalidated. The API returns successfully but no work is done.

Can tags be nested or hierarchical?

Not natively. Use naming conventions like 'category:electronics' and 'category:clothing'. Tag matching is exact string comparison.

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