DEV Community

Cover image for Static Rendering (SSG) in React: From Classic Builds to Data‑Driven Pages
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Static Rendering (SSG) in React: From Classic Builds to Data‑Driven Pages

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:

  1. Classic SSG (purely static)
  2. SSG with build‑time data
  3. SSG with individual detail pages

Finally, we’ll cover key considerations and wrap up with a quick checklist.


1. How Static Rendering Works

  1. Build step – Your React components are rendered to HTML strings.
  2. Asset emission – The build outputs plain .html, .css, and .js files.
  3. CDN deployment – The files are uploaded to edge nodes worldwide.
  4. 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");
})();
Enter fullscreen mode Exit fullscreen mode

Run:

node build.js   # generates /dist/index.html
npx serve dist  # serves the folder locally
Enter fullscreen mode Exit fullscreen mode

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
  };
}
Enter fullscreen mode Exit fullscreen mode

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 } };
}
Enter fullscreen mode Exit fullscreen mode
  • getStaticPaths enumerates which IDs to pre‑render.
  • fallback controls 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)