Skip to content

Next.js revalidate — Time-Based Revalidation with the revalidate Property

DodaTech Updated 2026-06-28 5 min read

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

Next.js revalidate property controls time-based ISR, specifying seconds after which a page can be regenerated in the background.

What You'll Learn

By the end of this tutorial, you'll understand how the revalidate property works, how to choose appropriate revalidation intervals, how to handle dynamic revalidation values, and common configuration patterns.

Why It Matters

The revalidate value determines the freshness-performance tradeoff. Too low, and your server works harder than necessary. Too high, and users see stale content. Choosing the right value requires understanding your content's update frequency.

Real-World Use

A blog with daily articles uses revalidate: 300 (5 minutes). When a new article is published, on-demand revalidation immediately triggers a refresh. The time-based revalidation catches any edits made in the CMS within 5 minutes.

Revalidate Flow

graph TD
    A[Page requested] --> B{Revalidate
window passed?} B -->|No| C[Serve cached
static HTML] B -->|Yes| D[Serve stale page
immediately] D --> E[Trigger background
re-render] E --> F{Fetch new data
successful?} F -->|Yes| G[Save new HTML
to cache] F -->|No| H[Keep stale page
retry next request] G --> I[Next visitor
gets fresh page] H --> J[Stale page
continues serving] style C fill:#27ae60,color:#fff style D fill:#f39c12,color:#fff style G fill:#27ae60,color:#fff style H fill:#e74c3c,color:#fff

Basic Revalidate Configuration

// pages/blog.js — Simple revalidation
export async function getStaticProps() {
    const res = await fetch('https://api.example.com/posts');
    const posts = await res.json();

    return {
        props: { posts },
        // Revalidate at most every 5 minutes
        revalidate: 300
    };
}

// pages/blog/[slug].js — Per-page revalidation
export async function getStaticProps({ params }) {
    const res = await fetch(`https://api.example.com/posts/${params.slug}`);
    const post = await res.json();

    return {
        props: { post },
        // Revalidate more frequently for popular content
        revalidate: post.isPopular ? 30 : 300
    };
}

Dynamic Revalidate Values

// pages/posts/[slug].js — Dynamic revalidate based on content
export async function getStaticProps({ params }) {
    const res = await fetch(`https://api.example.com/posts/${params.slug}`);
    const post = await res.json();

    // Calculate revalidate based on content age and type
    let revalidate = 300; // Default: 5 minutes

    if (post.type === 'news') {
        revalidate = 60; // News: 1 minute
    } else if (post.type === 'evergreen') {
        revalidate = 86400; // Evergreen: 24 hours
    } else if (post.type === 'product' && post.inventoryChanged) {
        revalidate = 30; // Product with changes: 30 seconds
    }

    // Set longer revalidate for older content
    const daysSincePublish = (Date.now() - new Date(post.publishedAt).getTime())
        / (1000 * 60 * 60 * 24);
    if (daysSincePublish > 30) {
        revalidate = Math.max(revalidate, 3600); // At least 1 hour
    }

    return {
        props: {
            post,
            revalidateInterval: revalidate,
            generatedAt: Date.now()
        },
        revalidate
    };
}

Revalidate with Fallback Data

// pages/products/[id].js — Graceful ISR
export async function getStaticProps({ params }) {
    try {
        const res = await fetch(
            `https://api.example.com/products/${params.id}`
        );

        if (!res.ok) {
            // If API fails during revalidation, keep stale data
            throw new Error(`API error: ${res.status}`);
        }

        const product = await res.json();

        return {
            props: { product, generatedAt: Date.now() },
            revalidate: 60
        };
    } catch (error) {
        console.error(`ISR failed for product ${params.id}:`, error.message);

        // Return fallback props — page keeps serving stale content
        return {
            props: {
                product: null,
                error: error.message,
                generatedAt: Date.now()
            },
            // Try again sooner if revalidation failed
            revalidate: 10
        };
    }
}

// Component handles null product gracefully
export default function ProductPage({ product, error, generatedAt }) {
    return (
        <div>
            {product ? (
                <>
                    <h1>{product.name}</h1>
                    <p>${product.price}</p>
                </>
            ) : (
                <div className="error-banner">
                    <p>Could not load latest data</p>
                    <small>Showing cached version</small>
                </div>
            )}
            <small>Generated: {new Date(generatedAt).toLocaleString()}</small>
        </div>
    );
}

Revalidate Best Practices

// Recommended revalidate values by content type
const revalidateGuidelines = {
    'Live blog / news feed': {
        value: 30,
        reason: 'Content updates frequently, freshness critical'
    },
    'E-commerce product': {
        value: 60,
        reason: 'Price and stock changes, but not every second'
    },
    'Blog post': {
        value: 300,
        reason: 'Rarely changes after publish, acceptable delay'
    },
    'Documentation page': {
        value: 3600,
        reason: 'Changes only with releases, hourly is fine'
    },
    'Static about page': {
        value: 86400,
        reason: 'Changes rarely, daily revalidation is sufficient'
    },
    'SEO-optimized landing page': {
        value: 604800,
        reason: 'Changes weekly, revalidate weekly'
    }
};

// Implementation helper
function getRevalidateByType(type) {
    const map = {
        news: 30,
        product: 60,
        blog: 300,
        docs: 3600,
        static: 86400,
        landing: 604800
    };
    return map[type] || 300;
}

Common Mistakes

  1. Setting revalidate: 1 for all pages. Revalidating every second means every Visitor triggers a re-render. This is essentially SSR with extra latency from serving stale content first.
  2. Not considering the revalidate cost. Each revalidation is a full page render + external API call. 1000 pages revalidating every 30 seconds is 2000 renders per minute.
  3. Using revalidate with static export. The output: 'export' configuration doesn't support ISR. Revalidate is ignored when using static export.
  4. Ignoring the revalidate cooldown. Multiple requests during the revalidate window only trigger one re-render. The first triggers it, subsequent requests serve stale content until complete.
  5. Not monitoring revalidate failures. Silent revalidation failures mean stale content indefinitely. Add logging and alerting for revalidation errors.

Practice Questions

  1. What unit does the revalidate property use?
  2. How does Next.js determine whether to serve cached or stale content?
  3. Can you set different revalidate values for different pages?
  4. What happens if the re-render fails during revalidation?
  5. How does revalidate interact with fallback: 'blocking'?

Challenge: Create a site with multiple content types (news, blog, products) each using different revalidate values. Add a dashboard that displays the last revalidation time for each page type and tracks how many background re-renders occur.

FAQ

What is the minimum revalidate value?

Technically 1 second. In practice, use 30+ seconds. Lower values increase server load without benefit since ISR still serves cached content during revalidation.

Can I disable ISR after enabling it?

Yes. Remove the revalidate property from getStaticProps to revert to pure SSG. Existing cached pages continue to serve until the next build.

Does revalidate apply to all deployment environments?

Revalidate works in production. In development (next dev), pages are rendered on every request, so ISR behavior differs.

How do I test ISR locally?

Build the site (next build) and start the production server (next start). ISR behaves the same way locally as in production.

Can revalidate be set to 0 for immediate updates?

No. Use on-demand revalidation via res.revalidate() instead. Setting revalidate: 0 causes immediate re-renders on every request.

Mini Project

Build a content site with tiered revalidation: create 4 page types (news: 30s, blog: 300s, docs: 3600s, evergreen: 86400s), display the generation timestamp and revalidate interval on each page, and verify that each type refreshes at its configured rate.

What's Next

You've mastered time-based revalidation. Now learn On-Demand ISR to trigger revalidation instantly from CMS Webhooks and API routes.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro