Skip to content

Revalidate Path — Revalidating Specific Paths on Demand

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Revalidate Path. We cover key concepts, practical examples, and best practices to help you master this topic.

Revalidate path triggers ISR for specific URLs, allowing precise control over which individual pages regenerate on content changes.

What You'll Learn

By the end of this tutorial, you'll understand how path-based revalidation works, how to revalidate single and multiple paths, how to handle dynamic parameters, and how to build revalidation chains.

Why It Matters

Not all content changes require wide revalidation. When a single blog post is edited, only that post page and its listing need updating. Path-based revalidation targets exactly the changed pages, minimizing server load.

Real-World Use

A product manager updates the description of one product. The CMS sends a Webhook with the product slug. The revalidation API calls res.revalidate() for exactly that product page and the product listing page. No other pages are affected.

Path Revalidation Flow

graph TD
    A[Content Update] --> B[Get affected
page paths] B --> C[Product page
/products/phone-x] B --> D[Listing page
/products] B --> E[Category page
/categories/electronics] C --> F[res.revalidate
for each path] D --> F E --> F F --> G[ISR re-renders
each path] G --> H[Static HTML
updated on CDN] style B fill:#4a90d9,color:#fff style F fill:#e67e22,color:#fff style H fill:#27ae60,color:#fff

Single Path Revalidation

// pages/api/revalidate.js — Single path revalidation
export default async function handler(req, res) {
    if (req.method !== 'POST') {
        return res.status(405).json({ message: 'Method not allowed' });
    }

    const { secret, path } = req.body;

    if (secret !== process.env.REVALIDATION_TOKEN) {
        return res.status(401).json({ message: 'Invalid secret' });
    }

    if (!path) {
        return res.status(400).json({ message: 'Path is required' });
    }

    try {
        await res.revalidate(path);
        console.log(`Revalidated: ${path}`);
        return res.json({ revalidated: true, path });
    } catch (err) {
        console.error(`Failed to revalidate ${path}:`, err);
        return res.status(500).json({
            message: 'Failed to revalidate',
            path,
            error: err.message
        });
    }
}

Dynamic Path Revalidation

// pages/api/revalidate-dynamic.js — Dynamic path revalidation
export default async function handler(req, res) {
    const { secret, type, slug } = req.body;

    if (secret !== process.env.REVALIDATION_TOKEN) {
        return res.status(401).json({ message: 'Invalid secret' });
    }

    const pathsToRevalidate = [];

    // Calculate paths based on content type
    switch (type) {
        case 'post':
            pathsToRevalidate.push(
                `/blog/${slug}`,     // The post itself
                '/blog',             // Blog listing
                '/'                  // Homepage (may feature recent posts)
            );
            break;

        case 'product':
            pathsToRevalidate.push(
                `/products/${slug}`,  // Product page
                '/products',          // Product listing
                '/'                   // Homepage
            );
            break;

        case 'category':
            pathsToRevalidate.push(
                `/categories/${slug}`,      // Category page
                '/products',                // Product listing
            );
            // Also revalidate products in this category
            // This would need a database lookup
            break;

        case 'page':
            pathsToRevalidate.push(
                `/${slug}`  // Static page
            );
            break;

        default:
            return res.status(400).json({ message: 'Unknown type' });
    }

    // Revalidate all paths
    const results = await Promise.allSettled(
        pathsToRevalidate.map(path =>
            res.revalidate(path)
                .then(() => ({ path, status: 'success' }))
                .catch(err => ({ path, status: 'failed', error: err.message }))
        )
    );

    const successCount = results.filter(r => r.value?.status === 'success').length;
    const failCount = results.filter(r => r.value?.status === 'failed').length;

    res.json({
        revalidated: true,
        type,
        slug,
        paths: pathsToRevalidate,
        successCount,
        failCount,
        details: results.map(r => r.value)
    });
}

Batch Path Revalidation

// pages/api/revalidate-batch.js — Batch revalidate
export default async function handler(req, res) {
    const { secret, paths } = req.body;

    if (secret !== process.env.REVALIDATION_TOKEN) {
        return res.status(401).json({ message: 'Invalid token' });
    }

    if (!Array.isArray(paths) || paths.length === 0) {
        return res.status(400).json({ message: 'Paths array required' });
    }

    // Rate limit: max 50 paths per request
    if (paths.length > 50) {
        return res.status(400).json({
            message: 'Maximum 50 paths per request',
            pathsProvided: paths.length
        });
    }

    const results = [];
    const BATCH_SIZE = 10;

    // Process in batches to avoid overwhelming the server
    for (let i = 0; i < paths.length; i += BATCH_SIZE) {
        const batch = paths.slice(i, i + BATCH_SIZE);

        const batchResults = await Promise.allSettled(
            batch.map(async (path) => {
                const start = Date.now();
                await res.revalidate(path);
                return {
                    path,
                    duration: Date.now() - start,
                    status: 'success'
                };
            })
        );

        results.push(...batchResults.map(r => r.value || {
            path: r.reason?.path || 'unknown',
            status: 'failed',
            error: r.reason?.message
        }));
    }

    const summary = {
        total: paths.length,
        succeeded: results.filter(r => r.status === 'success').length,
        failed: results.filter(r => r.status === 'failed').length,
        totalDuration: results.reduce((sum, r) => sum + (r.duration || 0), 0)
    };

    res.json({
        revalidated: summary.failed === 0,
        summary,
        details: results
    });
}

Revalidation from Client-Side

// lib/revalidate.js — Client-side revalidation trigger
export async function triggerRevalidation(paths, options = {}) {
    const {
        method = 'POST',
        baseUrl = process.env.NEXT_PUBLIC_BASE_URL || '',
        secret = process.env.NEXT_PUBLIC_REVALIDATION_TOKEN
    } = options;

    if (!secret) {
        console.warn('Revalidation secret not configured');
        return { success: false, error: 'No secret configured' };
    }

    try {
        const response = await fetch(`${baseUrl}/api/revalidate`, {
            method,
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                secret,
                paths: Array.isArray(paths) ? paths : [paths]
            })
        });

        const data = await response.json();

        if (!response.ok) {
            throw new Error(data.message || 'Revalidation failed');
        }

        console.log('Revalidation triggered:', data);
        return { success: true, data };
    } catch (error) {
        console.error('Revalidation error:', error);
        return { success: false, error: error.message };
    }
}

// Usage in admin panel
function PublishButton({ post }) {
    const [isPublishing, setIsPublishing] = useState(false);

    async function handlePublish() {
        setIsPublishing(true);

        // Revalidate the post page and listing
        const result = await triggerRevalidation([
            `/blog/${post.slug}`,
            '/blog',
            '/'
        ]);

        if (result.success) {
            alert('Post published and cache refreshed!');
        } else {
            alert('Published but cache refresh failed. Content may be stale.');
        }

        setIsPublishing(false);
    }

    return (
        <button onClick={handlePublish} disabled={isPublishing}>
            {isPublishing ? 'Refreshing cache...' : 'Publish'}
        </button>
    );
}

Common Mistakes

  1. Revalidating paths that don't exist. Calling res.revalidate('/nonexistent-page') throws an error. Always verify paths before revalidation.
  2. Not revalidating listing pages. When publishing a new post, revalidate the post page AND the blog listing page. Otherwise the listing shows stale content.
  3. Revalidating too many paths at once. Each revalidation triggers a page render. Revalidating 500 paths simultaneously can overwhelm the server. Batch by 10-20.
  4. Forgetting to handle revalidation errors. Failed revalidations don't crash the server but leave stale content. Log and monitor all revalidation attempts.
  5. Using path revalidation when tag revalidation is better. If 50 products all depend on one category, tag-based revalidation is more efficient than listing 50 paths.

Practice Questions

  1. How does res.revalidate('/path') trigger page regeneration?
  2. Why should you revalidate both the changed page and its listing?
  3. How do you handle dynamic paths like /products/[id]?
  4. What is the recommended batch size for path revalidation?
  5. How do you verify that path revalidation succeeded?

Challenge: Build an admin panel with path revalidation: create an API endpoint that accepts a content type and slug and calculates the correct paths, implement batch revalidation with error handling, add a publish button that triggers revalidation, and verify that pages update within 2 seconds.

FAQ

Can I revalidate paths in a different Next.js application?

No. res.revalidate only works within the same Next.js instance. Cross-application revalidation requires separate webhook calls.

How long does path revalidation take?

Typically 200-500ms per page. This depends on data fetching, template complexity, and server load. Batch requests may take longer.

What happens if I revalidate the same path multiple times?

Only the first revalidation within the cooldown window actually triggers. Subsequent requests are queued or ignored, depending on timing.

Can I revalidate paths during build time?

No. res.revalidate only works at runtime (in API routes). During build, pages are rendered fresh by getStaticProps.

How do I know if a path was successfully revalidated?

The res.revalidate() returns a promise that resolves on success or rejects on failure. Wrap in try-catch for error handling.

Mini Project

Build a revalidation dashboard: create an admin interface that lists all content, shows last revalidation time per page, allows manual revalidation of individual paths or batches, displays success/failure status, and logs all revalidation activity.

What's Next

You've mastered path-based revalidation. Now learn how ISR Caching works at the edge and origin server levels.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro