Static Site Generation Cheat Sheet
Covers pre-building HTML at build time with SSG, Next.js static generation APIs, deployment pipelines, and incremental regeneration.
Static Generation (Next.js)
Declaring which pages to build, plus ISR revalidation.
// pages/blog/[slug].jsexport async function getStaticPaths() { const posts = await getAllPostSlugs(); // e.g. read markdown files return { paths: posts.map((slug) => ({ params: { slug } })), fallback: false, // any path not returned here => 404 };}export async function getStaticProps({ params }) { const post = await getPostBySlug(params.slug); // runs at build time return { props: { post }, revalidate: 3600, // ISR: regenerate at most once per hour };}export default function BlogPost({ post }) { return <article dangerouslySetInnerHTML={{ __html: post.html }} />;}
Build & Deploy Pipeline
Turning source content into static files, then shipping them.
# Typical SSG build pipelinenpm run build # e.g. `next build`, `astro build`, `hugo`# Output: a directory of static HTML/CSS/JS/assets, e.g. ./out or ./dist# Deploy static output to a CDN/hostnpx vercel deploy ./out --prod# oraws s3 sync ./dist s3://my-bucket --deleteaws cloudfront create-invalidation --distribution-id ABC123 --paths "/*"
Key Concepts
Terminology that comes up across every SSG tool.
- Build time vs runtime- All HTML is generated once during the build, not per visitor request
- Content source- Markdown files, a headless CMS, or a database queried only at build time
- getStaticPaths/getStaticProps- Next.js APIs that declare which pages to build and their data
- Rebuild triggers- A CMS webhook or git push triggers a new build/deploy (e.g. build hooks)
- CDN caching- Static files are cacheable at the edge, giving fast global TTFB
- Popular tools- Next.js, Astro, Hugo, Eleventy (11ty), Jekyll, Gatsby
On-Demand Revalidation (webhook-triggered)
Revalidating a specific static path immediately from a CMS webhook instead of waiting for a timed interval.
// app/api/revalidate/route.js (Next.js App Router)import { revalidatePath, revalidateTag } from 'next/cache';export async function POST(req) { const secret = req.headers.get('x-webhook-secret'); if (secret !== process.env.CMS_WEBHOOK_SECRET) { return new Response('Invalid secret', { status: 401 }); } const { slug, tag } = await req.json(); if (slug) revalidatePath(`/blog/${slug}`); // rebuild one static page now if (tag) revalidateTag(tag); // invalidate every fetch() cached under this tag return Response.json({ revalidated: true, now: Date.now() });}// CMS webhook (e.g. Contentful/Sanity 'entry.publish') calls this endpoint// so the change goes live in seconds instead of waiting for the next// scheduled `revalidate` interval or a full site rebuild.
Hybrid Rendering with fallback: 'blocking'
Pre-building the top N pages while generating long-tail pages lazily on first request.
// pages/products/[slug].jsexport async function getStaticPaths() { const topProducts = await getTopSellingProducts(500); // only pre-build the hot set return { paths: topProducts.map((p) => ({ params: { slug: p.slug } })), fallback: 'blocking', // unknown slugs SSR on first hit, then cache as static };}export async function getStaticProps({ params }) { const product = await getProductBySlug(params.slug); if (!product) return { notFound: true }; return { props: { product }, revalidate: 86400 };}// fallback: false -> unknown paths 404 immediately// fallback: true -> unknown paths render a client-side loading state first// fallback: 'blocking' -> unknown paths SSR synchronously, no loading flash, then cached
Type-Safe Content Collections (Astro)
Validating and typing Markdown/MDX content at build time so SSG builds fail fast on bad data.
// src/content/config.tsimport { defineCollection, z } from 'astro:content';const blog = defineCollection({ type: 'content', schema: z.object({ title: z.string(), publishDate: z.date(), tags: z.array(z.string()).default([]), draft: z.boolean().default(false), }),});export const collections = { blog };// src/pages/blog/[...slug].astro---import { getCollection } from 'astro:content';export async function getStaticPaths() { const posts = await getCollection('blog', ({ data }) => !data.draft); return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }));}const { post } = Astro.props;const { Content } = await post.render();---<Content />
Advanced SSG Concepts
Terms that matter once a static site grows past a handful of pages.
- Incremental builds- Only rebuild pages whose source content changed since the last build, instead of regenerating every page
- Content graph invalidation- A single changed entry (e.g. an author) must invalidate every page that references it, not just its own page
- Build-time data fan-out- Fetching hundreds/thousands of pages of paginated CMS data in parallel batches to keep build time bounded
- Atomic deploys- The new static output directory is swapped in as one unit so visitors never see a half-deployed site
- Prerendering budget- A hard cap on build time/page count that forces a hybrid (SSG + on-demand ISR) strategy for very large catalogs
- Stale-while-revalidate at the CDN- Edge serves the last-known-good static asset instantly while regenerating in the background
- Draft/preview mode- A signed cookie bypasses the static cache so editors can view unpublished content through the same route
Draft Preview Mode (bypassing the static cache)
Letting editors view unpublished CMS content through the normal route without breaking the public cache.
// app/api/preview/route.jsimport { draftMode } from 'next/headers';import { redirect } from 'next/navigation';export async function GET(req) { const { searchParams } = new URL(req.url); const secret = searchParams.get('secret'); const slug = searchParams.get('slug'); if (secret !== process.env.PREVIEW_SECRET) { return new Response('Invalid token', { status: 401 }); } (await draftMode()).enable(); // sets a bypass cookie for this session only redirect(`/blog/${slug}`);}// Inside the page, check draftMode().isEnabled to fetch the unpublished// draft from the CMS instead of the cached published version — the static// cache served to anonymous visitors is completely unaffected.
For sites with thousands of pages, avoid full rebuilds on every content change — use Incremental Static Regeneration or on-demand revalidation so only the changed pages rebuild instead of the entire site.