Revalidate Path — Revalidating Specific Paths on Demand
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
- Revalidating paths that don't exist. Calling res.revalidate('/nonexistent-page') throws an error. Always verify paths before revalidation.
- Not revalidating listing pages. When publishing a new post, revalidate the post page AND the blog listing page. Otherwise the listing shows stale content.
- Revalidating too many paths at once. Each revalidation triggers a page render. Revalidating 500 paths simultaneously can overwhelm the server. Batch by 10-20.
- Forgetting to handle revalidation errors. Failed revalidations don't crash the server but leave stale content. Log and monitor all revalidation attempts.
- 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
- How does
res.revalidate('/path')trigger page regeneration? - Why should you revalidate both the changed page and its listing?
- How do you handle dynamic paths like
/products/[id]? - What is the recommended batch size for path revalidation?
- 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
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