Back to Blog

Next.js Static Site Generation in 2026: ISR, generateStaticParams, and On-Demand Revalidation

Master Next.js static generation in 2026: generateStaticParams for dynamic routes, ISR with revalidate, on-demand revalidation via revalidatePath and revalidateTag, and PPR.

Viprasol Tech Team
February 19, 2027
13 min read

Next.js Static Site Generation in 2026: ISR, generateStaticParams, and On-Demand Revalidation

Static generation delivers pre-rendered HTML from CDN edge servers โ€” sub-100ms TTFB, no database queries on each request, and infinite scalability at low cost. The challenge is keeping that static content fresh. Next.js App Router handles this through three mechanisms: time-based ISR (revalidate), on-demand revalidation (revalidatePath/revalidateTag), and Partial Prerendering (PPR) that mixes static and dynamic in the same page.

This post covers all three patterns: when to use each, generateStaticParams for pre-rendering dynamic routes, cache tagging for granular invalidation, and the ISR stale-while-revalidate behavior that trips up most teams.


The Three Rendering Modes

// Static (SSG): generated once at build time, never refreshes
// Use for: marketing pages, docs, blog posts (manually revalidated)
export const dynamic = "force-static";

// ISR: static + auto-refresh on a schedule
// Use for: product listings, news feeds, dashboards
export const revalidate = 3600; // Regenerate at most once per hour

// Dynamic: server-rendered on every request
// Use for: user dashboards, real-time data, auth-dependent pages
export const dynamic = "force-dynamic";

generateStaticParams: Pre-Rendering Dynamic Routes

// app/blog/[slug]/page.tsx

// This tells Next.js which [slug] values to pre-render at build time
export async function generateStaticParams() {
  const posts = await db.blogPost.findMany({
    where: { status: "published" },
    select: { slug: true },
    orderBy: { publishedAt: "desc" },
    take: 200, // Pre-render latest 200 posts; others render on-demand
  });

  return posts.map((post) => ({ slug: post.slug }));
}

// ISR: pages not in generateStaticParams are rendered on first request
// and then cached according to revalidate
export const revalidate = 3600; // Cache for 1 hour

// If a slug not in generateStaticParams is requested:
// - first request: render and cache
// - subsequent requests (within 1h): serve from cache
// - after 1h: serve stale, regenerate in background
export const dynamicParams = true; // Allow on-demand rendering (default)
// Set to false to 404 for slugs not in generateStaticParams

export default async function BlogPostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await db.blogPost.findUnique({
    where: { slug, status: "published" },
  });

  if (!post) notFound();
  return <BlogPost post={post} />;
}

// OpenGraph image (also statically generated)
export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await db.blogPost.findUnique({
    where: { slug },
    select: { title: true, excerpt: true, image: true },
  });

  return {
    title: post?.title,
    description: post?.excerpt,
    openGraph: {
      title: post?.title,
      description: post?.excerpt,
      images: [{ url: post?.image ?? "/og-default.jpg" }],
    },
  };
}

๐ŸŒ Looking for a Dev Team That Actually Delivers?

Most agencies sell you a project manager and assign juniors. Viprasol is different โ€” senior engineers only, direct Slack access, and a 5.0โ˜… Upwork record across 100+ projects.

  • React, Next.js, Node.js, TypeScript โ€” production-grade stack
  • Fixed-price contracts โ€” no surprise invoices
  • Full source code ownership from day one
  • 90-day post-launch support included

ISR: Stale-While-Revalidate Behavior

// app/products/page.tsx
// ISR with 60-second revalidation

export const revalidate = 60; // Revalidate at most every 60 seconds

export default async function ProductsPage() {
  // This fetch is cached by Next.js for 60 seconds
  const products = await db.product.findMany({
    where: { active: true },
    orderBy: { sortOrder: "asc" },
  });

  return <ProductGrid products={products} />;
}

ISR timeline (important to understand):

T=0s:   First request โ†’ render & cache
T=30s:  Request โ†’ serve cached (fast, from CDN)
T=60s:  Revalidate period expires
T=61s:  Request โ†’ serve STALE (still fast) + trigger background regeneration
T=62s:  Background regeneration completes โ†’ cache updated
T=63s:  Request โ†’ serve fresh content

The key: after revalidation, the next request sees stale content while the background re-render happens. The request after that sees fresh content. This is intentional โ€” it avoids making users wait for regeneration.


Cache Tags: Granular Invalidation

Tags let you invalidate multiple pages that share the same data:

// app/products/[id]/page.tsx
import { unstable_cache } from "next/cache";

// Tag this fetch with "products" and specific product ID
const getProduct = unstable_cache(
  async (id: string) => {
    return db.product.findUnique({ where: { id } });
  },
  ["product-detail"],           // Cache key prefix
  {
    tags: ["products", `product-${id}`],  // Tags for invalidation
    revalidate: 3600,
  }
);

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await getProduct(id);
  if (!product) notFound();
  return <ProductDetail product={product} />;
}
// app/products/page.tsx (listing page โ€” also tagged)
import { unstable_cache } from "next/cache";

const getProducts = unstable_cache(
  async () => db.product.findMany({ where: { active: true } }),
  ["product-list"],
  { tags: ["products"], revalidate: 3600 }
);

export default async function ProductsPage() {
  const products = await getProducts();
  return <ProductGrid products={products} />;
}
// app/actions/products.ts โ€” on-demand revalidation after update
"use server";

import { revalidateTag, revalidatePath } from "next/cache";

export async function updateProduct(productId: string, data: ProductUpdateInput) {
  await db.product.update({ where: { id: productId }, data });

  // Invalidate all pages tagged with this product OR the products collection
  revalidateTag(`product-${productId}`); // โ†’ re-renders /products/[id] page
  revalidateTag("products");             // โ†’ re-renders /products listing

  // Alternatively, invalidate by path:
  // revalidatePath(`/products/${productId}`);
  // revalidatePath("/products", "page");
}

๐Ÿš€ Senior Engineers. No Junior Handoffs. Ever.

You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.

  • MVPs in 4โ€“8 weeks, full platforms in 3โ€“5 months
  • Lighthouse 90+ performance scores standard
  • Works across US, UK, AU timezones
  • Free 30-min architecture review, no commitment

On-Demand Revalidation API Route

For webhooks (e.g., CMS publishes content โ†’ trigger revalidation):

// app/api/revalidate/route.ts
import { NextRequest, NextResponse } from "next/server";
import { revalidatePath, revalidateTag } from "next/cache";

export async function POST(req: NextRequest) {
  // Verify the request is from your CMS/webhook source
  const secret = req.headers.get("x-revalidate-secret");
  if (secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ error: "Invalid secret" }, { status: 401 });
  }

  const body = await req.json();
  const { type, slug, tag } = body;

  try {
    if (tag) {
      // Invalidate by tag (most efficient โ€” only revalidates tagged pages)
      revalidateTag(tag);
      return NextResponse.json({ revalidated: true, tag });
    }

    if (type === "blog-post" && slug) {
      revalidatePath(`/blog/${slug}`);
      revalidatePath("/blog");       // Invalidate listing too
      return NextResponse.json({ revalidated: true, path: `/blog/${slug}` });
    }

    if (type === "all") {
      revalidatePath("/", "layout"); // Revalidate entire site
      return NextResponse.json({ revalidated: true, scope: "all" });
    }

    return NextResponse.json({ error: "Unknown type" }, { status: 400 });
  } catch (err) {
    return NextResponse.json({ error: "Revalidation failed" }, { status: 500 });
  }
}
# Trigger from CMS webhook or CI/CD:
curl -X POST https://yourdomain.com/api/revalidate \
  -H "Content-Type: application/json" \
  -H "x-revalidate-secret: your-secret" \
  -d '{"type": "blog-post", "slug": "nextjs-image-optimization"}'

Partial Prerendering (PPR)

PPR (stable in Next.js 15) renders a static shell at build time and streams dynamic parts at request time โ€” no loading.tsx flicker:

// next.config.ts
const config: NextConfig = {
  experimental: {
    ppr: true,        // Enable PPR
  },
};

// app/products/[id]/page.tsx
import { Suspense } from "react";

// The product shell (title, image, description) is static
// The "In stock" indicator and recommendations are dynamic
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  // This is fetched at build time (static part)
  const product = await db.product.findUnique({
    where: { id },
    select: { id: true, name: true, description: true, image: true, price: true },
  });

  if (!product) notFound();

  return (
    <div>
      {/* Static shell โ€” pre-rendered, served instantly */}
      <ProductHeader product={product} />
      <ProductDescription text={product.description} />

      {/* Dynamic parts โ€” streamed after static shell */}
      <Suspense fallback={<StockSkeleton />}>
        <StockIndicator productId={id} />
      </Suspense>

      <Suspense fallback={<RecommendationsSkeleton />}>
        <Recommendations productId={id} />
      </Suspense>
    </div>
  );
}

// StockIndicator reads live inventory โ€” opt into dynamic
async function StockIndicator({ productId }: { productId: string }) {
  const stock = await db.inventory.findUnique({ where: { productId } });
  return <StockBadge count={stock?.available ?? 0} />;
}

Caching Comparison Table

StrategyTTFBFreshnessUse Case
SSG (build-time only)<50msStale until redeployDocs, landing pages
ISR (time-based)<50ms (stale ok)Up to X seconds oldBlogs, product listings
On-demand revalidation<50msFresh after webhookCMS content, editorial
PPR<50ms (shell)Shell static, parts dynamicProduct pages with live stock
Dynamic (SSR)100โ€“500msAlways freshUser-specific, real-time

Cost and Timeline

ComponentTimelineCost (USD)
generateStaticParams setup0.5 day$300โ€“$500
ISR configuration + cache tag design0.5โ€“1 day$400โ€“$800
On-demand revalidation webhook0.5 day$300โ€“$500
PPR implementation1โ€“2 days$800โ€“$1,600
Full SSG/ISR/PPR strategy1โ€“2 weeks$5,000โ€“$10,000

See Also


Working With Viprasol

We architect Next.js caching strategies for high-traffic applications โ€” from simple ISR blog pages through complex PPR product pages with live inventory. Our team has shipped Next.js sites serving millions of static pages with sub-100ms TTFB globally.

What we deliver:

  • generateStaticParams configuration for all dynamic routes
  • ISR revalidate period tuning per content type
  • Cache tag taxonomy for granular on-demand invalidation
  • Revalidation webhook for CMS integration (Contentful, Sanity, Strapi)
  • PPR implementation for pages with mixed static/dynamic content

Explore our web development services or contact us to optimize your Next.js caching strategy.

Share this article:

About the Author

V

Viprasol Tech Team

Custom Software Development Specialists

The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need a Modern Web Application?

From landing pages to complex SaaS platforms โ€” we build it all with Next.js and React.

Free consultation โ€ข No commitment โ€ข Response within 24 hours

Viprasol ยท Web Development

Need a custom web application built?

We build React and Next.js web applications with Lighthouse โ‰ฅ90 scores, mobile-first design, and full source code ownership. Senior engineers only โ€” from architecture through deployment.