What is static generation in React?
By FrontendPro Editorial Team Updated 9/26/2026
Answer
Static generation renders your components to HTML once, at build time, and serves the same files to every visitor. No server work per request — just a file from a CDN.
// Next.js App Router: this route is prerendered at build
export const revalidate = 3600; // and rebuilt at most hourly
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map(p => ({ slug: p.slug })); // one HTML file each
}
Why it wins where it fits
- The fastest possible response. TTFB is CDN latency; there is no render, no database, nothing to cold-start.
- Cost and scale. A traffic spike costs bandwidth, not CPU. Ten thousand concurrent readers hit a cache.
- Nothing to fall over. No runtime means no runtime errors and no database connection limits at 3am.
- Same SEO benefit as SSR — the crawler gets real HTML.
The constraint
The HTML is only as fresh as the last build, and the build has to render every page. A thousand pages is fine; a hundred thousand makes builds slow, and content that changes by the minute is simply wrong by the time it is served.
Two escape hatches handle most of that:
- Revalidation (ISR). The page is served from cache, and after the interval the next request triggers a rebuild in the background. The stale copy goes out first, so nobody waits.
- On-demand revalidation. Your CMS calls a webhook; you invalidate that one path or tag. This is what you want when an editor expects to see their change immediately.
And anything per-user simply does not belong in the static HTML. Render the shell statically and fetch the personalised part in a client component or a dynamic segment — that way the page is instant and the greeting fills in.
Choosing between the three
| Content | Approach |
|---|---|
| marketing page, docs, blog | static |
| product list changing hourly | static + revalidate |
| search results, dashboard | SSR per request |
| personal settings panel | client fetch |
The trap worth naming
A statically generated page that reads cookies(), headers(), or searchParams cannot stay static — the framework silently opts it into dynamic rendering. If a route you expected to be prerendered is showing up as dynamic in the build output, one of those calls is the reason, often inside a component several levels down.
