Skip to main content

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 TypeChange FrequencySuggested revalidateWhy
Company name, logo, policiesWeeks to monthsfalse (static forever)Never changes; no need to regenerate
Blog posts, articlesDays to weeks86400 (daily)Minor edits don't need instant refresh
Product catalogHours3600 (hourly)Prices and inventory change daily
Top trending listMinutes300 (5 min)Must reflect current trends
Live inventorySeconds to minutes30 (30 sec)Must be near-real-time
User-specific dataPer-request0 (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 cost
  • revalidate: 300 → Regenerates ~12 times/hour = small increase
  • revalidate: 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:

  1. Check x-nextjs-cache header (Vercel deploys): HIT means served from cache, STALE means revalidating.
  2. Measure response time: Cached pages <50ms; regenerating pages 100–500ms.
  3. Inspect build logs: Deployment logs show page generation times.
  4. Use console.log in 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: false for truly static content; revalidate: 0 for 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.

Further Reading