Skip to main content

Static Generation and Incremental Static Regeneration

12/28
Chapter 3 Data Fetching and Server Components

Static Generation and Incremental Static Regeneration

20 min read Lesson 12 / 28

Rendering Modes: Static, Dynamic, and ISR

Next.js 15 supports multiple rendering strategies. Choosing the right one for each route determines both performance and data freshness.

Fully Static Routes

When a page has no dynamic data, Next.js renders it at build time:

// app/about/page.tsx — no async data = fully static
export default function AboutPage() {
    return <h1>About Us</h1>;
}

generateStaticParams for Dynamic Static Routes

Pre-render all known slugs at build time:

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
    const posts = await db.post.findMany({
        select: { slug: true },
        where: { published: true },
    });

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

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

    if (!post) notFound();

    return <article>{post.content}</article>;
}

Incremental Static Regeneration (ISR)

Re-generate static pages in the background after they are visited:

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

// Revalidate this page every 60 seconds after first request
export const revalidate = 60;

// On-demand: regenerate when new posts are published
// Call revalidatePath('/blog/new-post-slug') from a Server Action

The Rendering Decision Tree

Is there dynamic data (cookies, headers, searchParams)?
  → YES: Dynamic rendering (per request)
  → NO: Does the route use revalidate?
        → YES: ISR (stale-while-revalidate)
        → NO: Fully static (at build time)

Combining Strategies with Partial Prerendering

// app/product/[id]/page.tsx
export const experimental_ppr = true;

export default async function ProductPage({
    params,
}: {
    params: Promise<{ id: string }>;
}) {
    const { id } = await params;
    const product = await getProduct(id); // Static shell

    return (
        <div>
            <ProductDetails product={product} />  {/* Static */}
            <Suspense fallback={<ReviewsSkeleton />}>
                <ProductReviews productId={id} />  {/* Dynamic stream */}
            </Suspense>
            <Suspense fallback={<RecommendationsSkeleton />}>
                <Recommendations productId={id} />  {/* Dynamic stream */}
            </Suspense>
        </div>
    );
}

PPR delivers the static product details instantly while the personalized sections stream in — best of both worlds.