Static rendering—also called Static‑Site Generation (SSG)—pre‑generates HTML during your build step, so visitors receive ready‑to‑serve pages without invoking a JavaScript runtime on each request. The pattern marries the speed of a CDN with the developer experience of React. Below we’ll unpack how it works, then walk through three escalating examples:
- Classic SSG (purely static)
- SSG with build‑time data
- SSG with individual detail pages
Finally, we’ll cover key considerations and wrap up with a quick checklist.
1. How Static Rendering Works
- Build step – Your React components are rendered to HTML strings.
-
Asset emission – The build outputs plain
.html,.css, and.jsfiles. - CDN deployment – The files are uploaded to edge nodes worldwide.
- Request/response cycle – A user’s browser receives an already rendered page, massively reducing Time to First Byte (TTFB) and Largest Contentful Paint (LCP).
Because there’s no server‑side computation per request, SSG excels at:
- Performance (edge‑cached, no cold starts)
- Reliability (few moving parts)
- Cost efficiency (mostly bandwidth)
- SEO (search crawlers get full HTML)
2. Classic SSG Implementation
Below is a minimal one‑page build using Node + react-dom/server. At build time we render <App /> to HTML and write it to /dist/index.html.
// build.js
import fs from "node:fs/promises";
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import App from "./App.js";
(async () => {
const html = renderToStaticMarkup(<App />);
const doc = `<!DOCTYPE html><html><head><title>Static App</title></head><body>${html}</body></html>`;
await fs.mkdir("dist", { recursive: true });
await fs.writeFile("dist/index.html", doc);
console.log("✅ Built static page ➜ dist/index.html");
})();
Run:
node build.js # generates /dist/index.html
npx serve dist # serves the folder locally
What we achieved
- Zero runtime JavaScript (unless you hydrate later)
- Blazing‑fast first paint
- Works great for truly unchanging pages—docs, marketing landing pages, etc.
3. SSG with Build‑Time Data (Next.js)
When your page depends on HEADLINE data that changes only daily/weekly, you can fetch during the build:
// pages/index.tsx
export default function Home({ posts }) {
return (
<main>
<h1>Blog</h1>
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
</main>
);
}
export async function getStaticProps() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=10");
const posts = await res.json();
return {
props: { posts },
revalidate: false // no ISR in this example
};
}
During next build, the API is hit once, and the resulting HTML carries the fetched data. Early visitors get pre‑filled content — no spinners, no blocking XHR.
4. SSG with Individual Detail Pages
For dynamic routes like /posts/[id], pair getStaticPaths with getStaticProps:
// pages/posts/[id].tsx
export default function Post({ post }) {
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}
export async function getStaticPaths() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=10");
const posts = await res.json();
const paths = posts.map(p => ({ params: { id: p.id.toString() } }));
return { paths, fallback: false }; // pre‑generate 10 pages
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
const post = await res.json();
return { props: { post } };
}
-
getStaticPathsenumerates which IDs to pre‑render. -
fallbackcontrols behavior for non‑generated IDs ('blocking' | 'true' | 'false').
You can scale this by enabling Incremental Static Regeneration (ISR) or on‑demand revalidation so you’re not rebuilding the entire site for every content change.
5. Key Considerations
| Consideration | Why It Matters | Mitigation |
|---|---|---|
| Build Time | Thousands of pages mean longer builds. | Split builds, ISR, parallelization. |
| Data Freshness | Static pages can become stale. | Use revalidate (ISR) or webhooks to trigger rebuilds. |
| Personalization | SSG can’t easily personalize per user. | Combine with client‑side rendering or edge middleware. |
| Large Data Sets | Generating every permutation might be impractical. | Opt for partial prerender + fallback: "blocking" for rarer paths. |
| Secret Data | Anything included at build is public. | Keep private data on the server; call via API at runtime. |
6. Conclusion
Static rendering sits at the intersection of predictability and performance. Use it when:
- Content changes infrequently or on a predictable schedule
- SEO and initial‑paint speed are paramount
- You’re comfortable pushing updates via CI/CD rather than at request time
For highly dynamic, user‑specific dashboards you’ll still reach for SSR or CSR. But for landing pages, docs, blogs, e‑commerce product listings, and marketing sites, SSG remains a battle‑tested pattern—and, as Learning Patterns highlights, one of the simplest ways to deliver a snappy, resilient user experience.
Checklist
- [ ] Identify pages with stable content
- [ ] Fetch external data in
getStaticProps - [ ] Generate dynamic routes with
getStaticPaths - [ ] Decide if ISR is required for freshness
- [ ] Automate deployment to a CDN/edge platform
Happy building! 🏗️🚀
Top comments (0)