Static Site Generation in Next.js turns React components into fast, pre-rendered HTML at build time, and this guide shows you exactly how to use it well.
Static Site Generation in Next.js is the default rendering strategy in the App Router, and it's the right choice for most pages on a content-driven site. But it's not a silver bullet — you need to know when it helps, when it hurts, and how to avoid the common pitfalls that trip up full-stack developers.
Why Static Site Generation in Next.js Matters (and When to Skip It)
Here's my take: if a page can be rendered at build time, it should be. Static pages load faster, cost less to serve, and survive traffic spikes without breaking a sweat. You're shipping HTML files to a CDN instead of running serverless functions on every request.
But I've seen teams force SSG where it doesn't belong. If your page depends on user-specific data, real-time updates, or cookies, you're fighting the paradigm. Use server-side rendering or client-side fetching for those cases. Don't contort your architecture to fit a buzzword.
Getting Started with Static Site Generation in Next.js
The App Router makes SSG the default — you don't need to opt in. A page component without dynamic functions is automatically static. Here's the minimal setup:
// app/blog/page.tsx
export default async function BlogPage() {
const posts = await getPosts();
return (
<div>
{posts.map((post) => (
<article key={post.slug}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
async function getPosts() {
// This runs at build time, not on every request
const res = await fetch("https://api.example.com/posts");
return res.json();
}
That's it. No getStaticProps, no config. Next.js renders this at build time and serves the static HTML. If you're on the Pages Router, the equivalent looks like this:
// pages/blog.tsx (Pages Router)
import { GetStaticProps } from "next";
export const getStaticProps: GetStaticProps = async () => {
const res = await fetch("https://api.example.com/posts");
const posts = await res.json();
return {
props: { posts },
revalidate: 3600, // ISR: rebuild every hour
};
};
Core Static Site Generation in Next.js Concepts Every Developer Should Know
1. Build-time data fetching
The fetch inside your server components runs once during next build. The result gets baked into the HTML. This is why static pages are so fast — there's zero data fetching at request time.
// app/products/[slug]/page.tsx
export async function generateStaticParams() {
const products = await fetch("https://api.example.com/products").then((r) => r.json());
return products.map((product: { slug: string }) => ({
slug: product.slug,
}));
}
2. Incremental Static Regeneration (ISR)
ISR is the sweet spot between static and dynamic. You serve static pages but revalidate them in the background after a set interval. It's perfect for content that changes occasionally.
// app/pricing/page.tsx
export const revalidate = 3600; // rebuild at most every hour
export default async function PricingPage() {
const pricing = await fetch("https://api.example.com/pricing").then((r) => r.json());
return <PricingTable data={pricing} />;
}
3. Dynamic rendering with cookies() and headers()
The moment you call cookies() or headers() in a server component, Next.js switches to dynamic rendering. This is a common source of confusion — developers think their page is static but it's not.
// app/dashboard/page.tsx
import { cookies } from "next/headers";
export default async function DashboardPage() {
const session = cookies().get("session");
// This page is now dynamic — it renders on every request
}
Common Static Site Generation in Next.js Mistakes and How to Fix Them
Mistake 1: Fetching user-specific data at build time. If you're calling getServerSession() inside a static page, you're mixing concerns. Fix: split the page — static shell for the public content, client component for the authenticated part.
Mistake 2: Ignoring generateStaticParams for dynamic routes. Without it, dynamic routes like app/blog/[slug]/page.tsx won't be pre-rendered. They'll render on-demand and might be dynamic. Fix: explicitly list the paths you want static.
Mistake 3: Overusing revalidate = 0. This disables ISR and makes every page dynamic. If you're doing this everywhere, you probably shouldn't be using SSG at all.
When Should You Use Static Site Generation in Next.js?
Use Static Site Generation in Next.js when your content is public, rarely changes, and doesn't depend on the request. Think marketing pages, blog posts, documentation, product listings. If you can answer "will this page look the same for every visitor?" with yes, go static.
Skip it for authenticated dashboards, real-time data, or pages that personalize content per user. You'll end up fighting the framework and paying for serverless compute anyway.
Static Site Generation in Next.js in Production
1. Use next build to verify your pages are static. Run the build and check the output — Next.js prints a table showing which routes are static, dynamic, or ISR. Make it part of your CI check.
2. Cache your external API calls. A build-time fetch that hits a slow API will slow down every deployment. Add a caching layer or use a static JSON file for data that doesn't change often.
3. Consider on-demand revalidation for critical content. Instead of time-based ISR, use revalidateTag() to rebuild pages when content actually changes. Your CMS webhook can trigger this.
// app/actions/revalidate.ts
"use server";
import { revalidateTag } from "next/cache";
export async function revalidateContent() {
revalidateTag("blog-posts");
}
If you're looking for more patterns on data fetching and rendering strategies, I've written about this extensively on suhailroushan.com — check the blog section for deep dives.
Start with static by default, add ISR where content changes, and reach for dynamic rendering only when you absolutely need it. Your users will thank you for the speed.