Skip to the content.

Static site generation: render once, serve a million times

Static site generation: render once, serve a million times

Static site generation renders your pages once, at build time, and produces plain HTML files. At request time the server does no rendering at all — it hands out a file, the same file, to everyone. That makes SSG the fastest and cheapest way to serve a page: a static file can sit on a CDN at the edge, respond in single-digit milliseconds, and cost almost nothing to scale, because serving the millionth copy is identical to serving the first. The catch is in the word “once”: the content is frozen at build time, so SSG fits content that is the same for every user and does not change between deploys — and stops fitting the moment either of those is false.

SSG renders at build time; requests are served as static files from a CDN At build time the generator turns content and templates into HTML files. At request time many users get the prebuilt files from a CDN edge with no server rendering. build time (once) content generate HTML files request time (a million times) CDN edge useruseruseruser
All the rendering happens once, before anyone visits. Every request after that is a static-file hand-off from the edge.

Build the routes ahead of time

An SSG framework asks you, at build time, which pages exist and what data each needs. It calls a data function per route, renders the page, and writes the file:

// build time: enumerate pages and fetch their data ONCE, then render each to HTML
export async function getStaticPaths() {
  const posts = await db.allPosts();
  return posts.map((p) => ({ params: { slug: p.slug } }));
}
export async function getStaticProps({ params }) {
  return { props: { post: await db.getPost(params.slug) } };  // baked into the file
}

The database is queried during the build, not during a request. By the time a user arrives, the answer is already sitting in a file on the CDN.

The freshness problem, and ISR

The obvious question is: what happens when the content changes? The blunt answer is you rebuild. For a blog that publishes daily that is fine; for a product catalogue that changes hourly it is not. The middle path is incremental static regeneration — serve the static file, but re-render it in the background after a set interval so it stays reasonably fresh without a full rebuild:

export async function getStaticProps() {
  return {
    props: { data: await fetchData() },
    revalidate: 60,   // serve the cached file; rebuild this page at most once a minute
  };
}

Pick SSG when the page is the same for everyone

The decision reduces to two questions. Is the page the same for every user? Personalised, authenticated content cannot be prebuilt into one shared file. And does it change rarely relative to how often it is read? A page read a million times and edited weekly is the perfect SSG candidate; a page read once and personalised per request is the worst. When both answers are “yes,” SSG gives you the best first paint, the lowest cost, and the simplest operational story — no render servers to scale, just files. When they are “no,” reach for SSR or a client fetch. The render-strategy-choice exercise is built around exactly this triage, because choosing the strategy is the real work; the frameworks make any of them easy once you have chosen.