Skip to content

Static Rendering

Static rendering pre-renders a route's HTML at build time, so every visitor gets the same, instantly-served page with no per-request server work. This lesson explains how Next.js decides when a route qualifies for static rendering.

How Next.js Decides to Render Statically

In the App Router, static rendering is the default behavior for any route that doesn't use dynamic, request-specific data. If a route's Server Components only call fetch() with cacheable options (or use generateStaticParams for dynamic segments), Next.js renders the route's HTML once at build time and serves that same HTML to every visitor.

The moment a route reads something inherently request-specific — like cookies, headers, or search params in certain contexts, or uses cache: "no-store" — Next.js automatically switches that route to dynamic rendering instead, since the output can no longer be the same for every visitor.

// app/about/page.tsx — a good candidate for static rendering
export default function AboutPage() {
  return <h1>About Our Company</h1>;
}

// This entire page can be rendered once, at build time,
// and served identically to every visitor.

No dynamic data, no per-request logic — Next.js renders this once and reuses the HTML for every request.

Signals That Keep a Route Static

// Static-friendly:
const res = await fetch(url); // default/cached fetch
const res2 = await fetch(url, { next: { revalidate: 3600 } });

// Forces dynamic rendering:
cookies().get("session");
headers().get("user-agent");
fetch(url, { cache: "no-store" });
  • Cached fetch() calls are compatible with static rendering.
  • Reading cookies or headers makes a route dynamic, since that data varies per request.
  • cache: "no-store" explicitly opts a specific fetch (and often the route) out of static rendering.
  • You can force a route to be static or dynamic explicitly with a route segment config export.

Static Rendering Cheat Sheet

What keeps a route static vs. what forces it dynamic.

Behavior Static Rendering
Rendered Once, at build time
Served to every visitor Same, pre-rendered HTML
Compatible with cookies()/headers() No — forces dynamic rendering
Compatible with cached fetch() Yes
Explicit opt-in export const dynamic = "force-static";

Static Rendering for Dynamic Routes

Dynamic routes (like [slug]) can still be statically rendered — for every specific slug returned by generateStaticParams, Next.js generates a separate static HTML file at build time, even though the route itself uses a dynamic segment.

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPostSlugs();
  return posts.map((slug: string) => ({ slug }));
}

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

Why Static Rendering Is Valuable

Statically rendered pages can be served directly from a CDN edge location, without invoking a server function at all, resulting in extremely fast responses and lower infrastructure cost at scale — which is why it's the preferred strategy whenever content doesn't need to be personalized per request.

Common Mistakes

  • Reading cookies or headers in a route that was intended to stay fully static, unintentionally making it dynamic.
  • Assuming a dynamic route ([slug]) can never be statically rendered.
  • Forgetting to export generateStaticParams, leaving dynamic routes rendered on-demand instead of pre-built.
  • Not distinguishing static rendering (this lesson) from Incremental Static Regeneration (which adds periodic revalidation).

Key Takeaways

  • Static rendering pre-renders a route's HTML once, at build time, for every visitor.
  • It's the default for routes without request-specific data or uncached fetches.
  • Reading cookies()/headers(), or using cache: "no-store", switches a route to dynamic rendering.
  • Dynamic routes can still be statically rendered per parameter value via generateStaticParams.
  • Static rendering offers the best performance and lowest cost for content that's the same for every visitor.

Pro Tip

Run next build and check its output summary — Next.js explicitly labels each route as Static, Dynamic, or ISR in the build log, which is the fastest way to confirm a route is being rendered the way you expect.