Static Site Generation (SSG)
Next.js Development
Static Site Generation (SSG)
Static Site Generation is a rendering strategy where HTML pages are generated at build time and served as static files from a CDN. This is the fastest possible delivery method because the server does not need to render anything per request — it simply serves pre-built HTML files. SSG is ideal for content that does not change frequently, such as blog posts, documentation, marketing pages, and product catalogs. Next.js combines SSG with Incremental Static Regeneration (ISR), which allows you to update static pages after deployment without rebuilding the entire site.
Static Pages in the App Router
In the App Router, pages are statically generated by default when they do not use dynamic functions like cookies, headers, or uncached fetch requests. Data fetching with the default cached fetch produces static pages.
// app/blog/page.tsx — statically generated at build time
interface Post {
id: number;
title: string;
excerpt: string;
slug: string;
}
async function getPosts(): Promise<Post[]> {
// Default fetch is cached → page is statically generated
const res = await fetch("https://api.example.com/posts");
return res.json();
}
export default async function BlogIndex() {
const posts = await getPosts();
return (
<div>
<h1>Blog</h1>
{posts.map((post) => (
<article key={post.id}>
<h2><a href={`/blog/${post.slug}`}>{post.title}</a></h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
Static Dynamic Routes with generateStaticParams
For dynamic routes, use generateStaticParams to tell Next.js which parameter values to pre-render at build time. This is the App Router equivalent of getStaticPaths.
// app/blog/[slug]/page.tsx
interface Post {
slug: string;
title: string;
content: string;
publishedAt: string;
}
// Generate static pages for all posts at build time
export async function generateStaticParams() {
const posts = await fetch("https://api.example.com/posts").then((r) =>
r.json()
);
return posts.map((post: Post) => ({
slug: post.slug,
}));
// Returns: [{ slug: "first-post" }, { slug: "second-post" }, ...]
}
// Generate metadata for each page
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.content.slice(0, 160),
};
}
async function getPost(slug: string): Promise<Post> {
const res = await fetch(`https://api.example.com/posts/${slug}`);
if (!res.ok) throw new Error("Post not found");
return res.json();
}
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
return (
<article>
<h1>{post.title}</h1>
<time>{new Date(post.publishedAt).toLocaleDateString()}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
Incremental Static Regeneration (ISR)
ISR allows you to update static pages after deployment without rebuilding the entire site. Pages are regenerated in the background when a request comes in after the revalidation period.
// Time-based ISR — revalidate every 60 seconds
async function getData() {
const res = await fetch("https://api.example.com/products", {
next: { revalidate: 60 },
});
return res.json();
}
// On-demand ISR — revalidate when content changes
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from "next/cache";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const { secret, path, tag } = await request.json();
// Verify webhook secret
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: "Invalid secret" }, { status: 401 });
}
// Revalidate by path or tag
if (path) {
revalidatePath(path);
}
if (tag) {
revalidateTag(tag);
}
return NextResponse.json({ revalidated: true, now: Date.now() });
}
// Call from your CMS webhook:
// POST /api/revalidate
// { "secret": "...", "path": "/blog", "tag": "posts" }
Static Export
// next.config.js — full static export (no Node.js server needed)
const nextConfig = {
output: "export",
// All pages must be statically generable
// No API routes, no server-side rendering
// Perfect for hosting on GitHub Pages, S3, or any static host
};
module.exports = nextConfig;
Tip: Use SSG for content that changes infrequently (blog posts, docs, marketing pages) and add ISR with a short revalidation period for content that updates moderately (product listings, news). Reserve SSR for content that must be fresh on every request (user dashboards, real-time data).
Key Takeaways
- SSG generates HTML at build time for the fastest possible page loads.
generateStaticParamspre-renders dynamic routes at build time.- ISR updates static pages after deployment using
next: { revalidate: seconds }. - On-demand revalidation with
revalidatePathandrevalidateTagenables instant updates from CMS webhooks. - Use
output: "export"for fully static sites that can be hosted anywhere without a server.