CPCODELAB
Next.js

Static vs Dynamic Rendering

13 min read

How Next.js Decides When to Render

Next.js automatically decides at build time whether a route should be rendered statically (once, at build) or dynamically (on every request). You rarely need to configure this explicitly — the framework infers it from what your route does.

Static Rendering (the default)

A route is statically rendered when it has no dynamic dependencies: no cookies(), no headers(), no searchParams, and all fetch calls are cached. The HTML is generated once at build time and served from a CDN on every request.

Dynamic Rendering

A route becomes dynamically rendered the moment it calls cookies(), headers(), reads searchParams, or uses cache: "no-store". The HTML is then generated fresh on every incoming request.

tsx
import { cookies } from "next/headers";

// This page is DYNAMIC — it reads cookies at request time
export default async function ProfilePage() {
  const cookieStore = await cookies();
  const userId = cookieStore.get("userId")?.value;

  if (!userId) return <p>Not logged in.</p>;

  const user = await fetchUser(userId);
  return <h1>Hello, {user.name}</h1>;
}

async function fetchUser(id: string) {
  const res = await fetch(`https://api.example.com/users/${id}`, {
    cache: "no-store",
  });
  return res.json();
}

generateStaticParams for Dynamic Routes

For dynamic routes (e.g. /blog/[slug]), export generateStaticParams to pre-render a known list of slugs at build time. Any slug not in the list is rendered dynamically on demand.

tsx
// src/app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await fetch("https://api.example.com/posts").then((r) =>
    r.json()
  );
  return posts.map((post: { slug: string }) => ({ slug: post.slug }));
}

export default async function PostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await fetchPostBySlug(slug);
  return <article><h1>{post.title}</h1></article>;
}

async function fetchPostBySlug(slug: string) {
  const res = await fetch(`https://api.example.com/posts/${slug}`);
  return res.json();
}

Prefer static rendering wherever possible — it is faster and cheaper. Only opt into dynamic rendering when you genuinely need per-request data (authentication, personalisation, live prices).

Ready to test yourself?
Practice Next.js— quiz & coding exercises