Time-Based Revalidation: Control Cache Duration in Next.js
Time-based revalidation controls how long Next.js caches data before regenerating a page or fetch. The revalidate property, set in seconds, tells Next.js when the cache expires and triggers automatic regeneration. Choosing the right revalidation time is a tradeoff: shorter times keep data fresh but regenerate more often (higher server load); longer times reduce load but risk stale data. This article teaches you how to set revalidation times strategically per content type and page.
The Revalidate Property: Where and How to Use It
You can set revalidation in two places: page-level (for the entire page) or fetch-level (for individual fetches).
Page-level revalidation applies to the whole page. All fetches on that page respect this setting unless overridden:
// app/products/page.js
export const revalidate = 3600; // Page regenerates every hour
export default async function ProductsPage() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }
}).then(r => r.json());
return (
<div>
{products.map(p => <div key={p.id}>{p.name}</div>)}
</div>
);
}
Fetch-level revalidation controls individual fetch caches. Use this when a page fetches data from multiple sources with different freshness requirements:
// app/products/page.js
export const revalidate = 3600; // Page default
export default async function ProductsPage() {
// Static: never changes
const categories = await fetch('https://api.example.com/categories', {
next: { revalidate: false }
}).then(r => r.json());
// Semi-static: refresh daily
const featured = await fetch('https://api.example.com/products/featured', {
next: { revalidate: 86400 }
}).then(r => r.json());
// Dynamic: refresh every 5 minutes
const trending = await fetch('https://api.example.com/products/trending', {
next: { revalidate: 300 }
}).then(r => r.json());
return (
<div>
<section>
<h2>Categories</h2>
{categories.map(c => <div key={c.id}>{c.name}</div>)}
</section>
<section>
<h2>Featured</h2>
{featured.map(p => <div key={p.id}>{p.name}</div>)}
</section>
<section>
<h2>Trending</h2>
{trending.map(p => <div key={p.id}>{p.name}</div>)}
</section>
</div>
);
}
In this example, categories never change (revalidate: false), featured products change daily, and trending products update every 5 minutes. The page's overall revalidation time becomes the shortest of all fetches: every 300 seconds, the entire page regenerates because at least one fetch is stale.
Choosing the Right Revalidation Time
The best revalidation time depends on how quickly your data changes and how much stale data you can tolerate:
| Content Type | Change Frequency | Suggested revalidate | Why |
|---|---|---|---|
| Company name, logo, policies | Weeks to months | false (static forever) | Never changes; no need to regenerate |
| Blog posts, articles | Days to weeks | 86400 (daily) | Minor edits don't need instant refresh |
| Product catalog | Hours | 3600 (hourly) | Prices and inventory change daily |
| Top trending list | Minutes | 300 (5 min) | Must reflect current trends |
| Live inventory | Seconds to minutes | 30 (30 sec) | Must be near-real-time |
| User-specific data | Per-request | 0 (dynamic, no cache) | Changes per user, no point caching |
Static content (revalidate: false):
- Site name, company info
- Policies, legal pages
- Design system docs
- Published blog posts (if not edited after publishing)
Semi-static (revalidate: 3600 to 86400):
- Product catalogs
- Blog post listings
- Search result snippets
- Articles with occasional edits
Dynamic (revalidate: 0):
- User dashboards
- Personalized recommendations
- Real-time data (stocks, weather)
- Content behind authentication
Cost and Performance Implications
Shorter revalidation times increase server load and infrastructure costs. A page with revalidate: 60 regenerates 60 times per hour for each unique user; with revalidate: 3600, it regenerates once per hour. At scale, this matters:
Example: 1M requests/hour to a single page:
revalidate: 3600→ Page regenerates ~1 time/hour = minimal server costrevalidate: 300→ Regenerates ~12 times/hour = small increaserevalidate: 0→ Regenerates ~1M times/hour = server and database overwhelmed
Vercel's pricing (2025) charges per execution time. A single page rebuild costs ~$0.00001 (roughly). Multiply by regeneration frequency, and costs add up. Always use the longest revalidation time acceptable for your data freshness.
Conversely, too-long revalidation times risk user frustration. A price catalog regenerating only once a week will show outdated prices that upset customers. Balance is key.
Handling Revalidation Errors
When revalidation is scheduled but fails (API timeout, database down), Next.js keeps serving the stale cached version. This is a feature—graceful degradation. However, monitor revalidation failures:
// app/products/page.js
export const revalidate = 3600;
export default async function ProductsPage() {
try {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 },
signal: AbortSignal.timeout(5000) // 5 second timeout
}).then(r => r.json());
return (
<div>
{products.map(p => <div key={p.id}>{p.name}</div>)}
</div>
);
} catch (error) {
// Log error for monitoring
console.error('Failed to fetch products:', error);
// Return an error message or fallback UI
return <div className="error">Failed to load products</div>;
}
}
Set a fetch timeout to prevent revalidations hanging indefinitely. Log errors to Sentry or your monitoring service (Sentry docs, 2025) so you're alerted to persistent failures.
Stale-While-Revalidate Pattern
The SWR (Stale-While-Revalidate) pattern, defined in HTTP RFC 5861 (2010), serves stale content while revalidating in the background. Next.js implements this naturally with ISR:
User request arrives at T=3600s (cache expired)
├─ Server serves cached page immediately (stale but fast)
└─ Server background regenerates
└─ Next request (at T=3600+Δ) gets fresh data
This is optimal for user experience: nobody waits for regeneration. The trade-off is that some users see stale data for a few seconds. For non-critical content (product listings, blog posts), this is acceptable. For critical data (account balance, medical info), use dynamic rendering instead.
Combining Revalidation Strategies
Professional apps combine multiple strategies on the same page:
// app/news/page.js (news aggregator)
export const revalidate = 3600; // Page default
async function LatestNews() {
// Revalidate every hour
const news = await fetch('https://api.example.com/news', {
next: { revalidate: 3600 }
}).then(r => r.json());
return (
<section>
<h2>Latest News</h2>
{news.map(n => <article key={n.id}>{n.title}</article>)}
</section>
);
}
async function TrendingTopics() {
// Revalidate more frequently (trending data changes fast)
const topics = await fetch('https://api.example.com/trending', {
next: { revalidate: 300 }
}).then(r => r.json());
return (
<section>
<h2>Trending</h2>
{topics.map(t => <div key={t.id}>{t.name}</div>)}
</section>
);
}
export default async function NewsPage() {
return (
<div>
<LatestNews />
<TrendingTopics />
</div>
);
}
LatestNews regenerates hourly; TrendingTopics every 5 minutes. The page regenerates at the fastest interval (5 min) to keep trending data fresh. This is efficient: you're not regenerating the entire page more than necessary, but trending sections update frequently.
Debugging Revalidation Times
Next.js doesn't expose revalidation timing in the browser directly, but you can infer behavior:
- Check
x-nextjs-cacheheader (Vercel deploys):HITmeans served from cache,STALEmeans revalidating. - Measure response time: Cached pages <50ms; regenerating pages 100–500ms.
- Inspect build logs: Deployment logs show page generation times.
- Use
console.login components: Log when a fetch executes. On revalidation, you'll see logs.
export default async function Page() {
console.log('Page rendering at', new Date().toISOString());
const data = await fetch('...');
return <div>{data}</div>;
}
Each revalidation logs a new timestamp, confirming when regeneration occurred.
Key Takeaways
- Use
revalidate: <seconds>to set cache TTL per page or per fetch. - Shorter revalidation (300–3600 sec) keeps data fresh but increases server load.
- Longer revalidation (86400 sec or
false) reduces load but risks stale data. - Use
revalidate: falsefor truly static content;revalidate: 0for dynamic, per-request rendering. - The shortest revalidation time across all fetches on a page determines the page's overall cache duration.
Frequently Asked Questions
What's the difference between revalidate: 0 and not setting revalidate?
revalidate: 0 explicitly disables caching (always fresh, always recomputed). Not setting revalidate defaults to revalidate: false (cache indefinitely). You should always be explicit to avoid confusion.
Can I change revalidate after deployment?
No. Revalidation settings are compiled into your page. To change them, modify the code and redeploy. Next.js won't retroactively apply new revalidation times to cached data.
Does revalidate apply to database queries or only HTTP fetches?
By default, only HTTP fetches via the enhanced fetch function. For database queries, you must manually implement caching or use your ORM's cache layer. Some ORMs (Prisma) support next.js revalidate indirectly via extensions.
How do I revalidate faster than every 60 seconds in production?
Use on-demand revalidation via revalidatePath() or revalidateTag() instead of time-based schedules. This regenerates immediately when content changes, rather than waiting for a timer.
What happens if my API is down during revalidation?
The revalidation fails silently, and the cached version remains in use. If the API stays down, users see stale data. This is graceful degradation, but monitor for persistent API failures.