React 110 ⚛️ Static Site Generation with React-based Frameworks
Static Site Generation produces the HTML for every route at build time. Each URL maps to a file on disk that a CDN can serve from cache in tens of milliseconds, regardless of where the user is or how many other requests are in flight. The server does no work per request, so Time to First Byte is bounded only by network latency. For React developers, the choice of SSG framework determines not just the rendering strategy but the data layer, the build pipeline, and the interactivity model.
This chapter covers the three viable React-compatible SSG frameworks—Next.js, Astro, and Gatsby—the different rendering models each provides, the build-scale ceilings that determine which framework survives a large route space, and the JavaScript footprint that separates an application framework from a content-first tool. It examines why React Static is not a candidate and why Gatsby’s maintenance status changes the calculation.
Key point: The choice between these frameworks is a choice about how much JavaScript the site ships and how the build handles scale. Next.js is the hybrid rendering platform with the largest ecosystem. Astro is the content-first framework that ships zero JavaScript by default and hydrates only the interactive islands. Gatsby is the build-time GraphQL data pipeline, now in maintenance rather than active development.
Why three frameworks remain
The application problem. A React team that needs one framework for marketing pages, a documentation section, and an authenticated dashboard cannot choose a content-first tool. Next.js is the only option that covers all three in a single application. The App Router statically renders Server Components by default, handles dynamic routes with generateStaticParams, and switches to server or client rendering per route . The cost is the JavaScript bundle: the React runtime ships to every page, even the static ones.
The content problem. A marketing site or a documentation site is mostly static text with a few interactive widgets. The application framework ships 100–200 kB of JavaScript that the content pages never use. Astro exists to eliminate that cost. A React component in an Astro page renders to static HTML with zero client-side JavaScript unless it is explicitly marked with a client:* directive . The result is a page that is static by default and interactive only where the developer chose to make it interactive.
The data pipeline problem. A site that aggregates content from a CMS, a database, and a filesystem needs a data layer that normalizes all of those sources before rendering. Gatsby’s build-time GraphQL pipeline solves that problem with source plugins and a unified schema. The trade-off is that the plugin-driven coupling can slow builds, and the framework is no longer actively developed . The data layer is still functional; the future is uncertain.
a. Next.js: The Hybrid Rendering Platform
Next.js is the dominant React framework by every market metric. It has approximately 127,000 GitHub stars and 6 million weekly npm downloads, more than all the other React SSG options combined . The framework’s SSG model has evolved significantly with the App Router, which replaced the getStaticProps/getStaticPaths API with Server Components and generateStaticParams.
In the App Router, a Server Component without any dynamic data sources is statically rendered automatically at build time. There is no getStaticProps to call; the component itself runs at build time, and the rendered HTML goes to the CDN. For dynamic routes, generateStaticParams returns the list of parameter sets to pre-render. Each returned set produces one HTML file.
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export default async function Post({ params }) {
const { slug } = await params
const post = await getPost(slug)
return <article dangerouslySetInnerHTML={{ __html: post.html }} />
}
The constraint that surfaces at scale is the materialized array. generateStaticParams must return a complete array of all parameter sets before rendering begins. A benchmark comparing React SSG frameworks found that at 100,000 pages this is manageable, but above approximately 150,000 entries the build crashes with a RangeError: Maximum call stack size exceeded during page-data collection . For sites with very large dynamic route spaces, this is a hard ceiling that Astro does not share.
Next.js also provides the most mature hybrid rendering options. Incremental Static Regeneration allows a statically generated page to be revalidated on a time interval or on-demand via a webhook, without rebuilding the entire site . The App Router’s "use cache" directive and tag-based revalidation replace the older revalidate export.
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
export async function POST(request) {
const tag = request.nextUrl.searchParams.get('tag')
revalidateTag(tag)
return Response.json({ revalidated: true })
}
b. Astro: Content-First with Islands Architecture
Astro is not a React framework in the same sense as Next.js. It is a content-first framework that supports React (along with Preact, Svelte, Vue, and Solid) as one of several UI component options. The architecture is built around islands: the page is static HTML by default, and interactive components are isolated islands that hydrate independently.
By default, a React component in an Astro page renders to static HTML with zero client-side JavaScript. The component becomes interactive only when marked with a client:* directive: client:load hydrates immediately, client:idle waits for browser idle, client:visible waits for the component to enter the viewport, and client:only skips server rendering entirely .
---
import Header from '../components/Header.jsx'
import Carousel from '../components/Carousel.jsx'
---
<Header client:load />
<p>Static content, no JavaScript</p>
<Carousel client:visible />
The benefit is the smallest possible JavaScript footprint. A content site with a few interactive widgets ships only the JavaScript for those widgets, not the React runtime for the entire page. The trade-off is that state sharing across islands requires explicit patterns, and the team must avoid over-hydrating—marking too many components as interactive defeats the purpose.
Astro’s build model uses getStaticPaths() for dynamic routes, which returns a materialized array like Next.js’s generateStaticParams. The benchmark notes that Astro is the only framework in the comparison that does not run React in its page-render path, making it the SSG-throughput ceiling rather than a like-for-like React tool . For a React team, the decision to use Astro is a decision to treat React as a component library rather than the application framework.
c. Gatsby and the Abandoned Options
Gatsby’s defining feature is its build-time GraphQL data layer. Source plugins pull data from any number of sources—filesystems, CMSs, APIs, databases—and normalize it into a unified GraphQL schema. Pages query that schema with a graphql tag, and the result is available as props to the page component. The data fetching happens once at build time, producing static HTML.
The plugin ecosystem is the reason to choose Gatsby. A project that needs to pull from Strapi, MongoDB, a filesystem, and a dozen other sources can do so with existing plugins, and the GraphQL layer unifies the data access . The trade-off is that the plugin-driven coupling can slow builds, and large datasets require careful caching and query discipline.
The critical caveat is that Gatsby is no longer actively developed. The SSW rules page states this explicitly, and the framework’s GitHub stars and npm downloads are an order of magnitude below Next.js . A new project choosing Gatsby is choosing a framework with an uncertain maintenance future. The Strapi integration guide and the MongoDB guide are still functional, but they describe a tool that is not receiving new features.
React Static, the fourth option that once appeared in comparison tables, is abandoned entirely. Its last release was in 2018, it uses Webpack 3 and Babel 6, and it cannot run on Node 22 without deprecated workarounds. It should not be considered for any new project.
Complete Example Session
// ============================================
// PART 1: NEXT.JS — STATIC SERVER COMPONENT
// ============================================
// app/pricing/page.tsx
export default function Pricing() {
// No dynamic data sources → statically rendered at build time
return <div>Three tiers, no surprises.</div>
}
// ============================================
// PART 2: NEXT.JS — DYNAMIC ROUTE
// ============================================
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export default async function Post({ params }) {
const { slug } = await params
const post = await getPost(slug)
return <article dangerouslySetInnerHTML={{ __html: post.html }} />
}
// ============================================
// PART 3: NEXT.JS — ON-DEMAND REVALIDATION
// ============================================
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
export async function POST(request) {
const tag = request.nextUrl.searchParams.get('tag')
revalidateTag(tag)
return Response.json({ revalidated: true })
}
// ============================================
// PART 4: ASTRO — STATIC PAGE WITH REACT ISLAND
// ============================================
// src/pages/index.astro
---
import Header from '../components/Header.jsx'
import Carousel from '../components/Carousel.jsx'
---
<Header client:load />
<p>Static content, no JavaScript</p>
<Carousel client:visible />
The four parts show the Next.js static component, the dynamic route with generateStaticParams, the revalidation endpoint, and the Astro island with different hydration directives.
Quick Reference
Framework Comparison
| Aspect | Next.js | Astro | Gatsby |
|---|---|---|---|
| React-first | Yes | No (multi-framework) | Yes |
| Default JS shipped | React runtime | Zero | React runtime |
| Data layer | Server Components / fetch | Any (in frontmatter) | GraphQL |
| Dynamic routes | generateStaticParams | getStaticPaths | createPages |
| Incremental regeneration | ISR | No | No |
| Build scale ceiling | ~150k pages | Higher (no React in render) | Linear memory |
| Active development | Yes | Yes | No |
When to Choose Each
| Need | Framework |
|---|---|
| One framework for static + authenticated pages | Next.js |
| Smallest JS footprint for content site | Astro |
| Strong build-time GraphQL data pipeline | Gatsby (with caution) |
| Large dynamic route space (>150k pages) | Astro or custom streaming |
Astro Hydration Directives
| Directive | When It Hydrates |
|---|---|
client:load | Immediately |
client:idle | When browser is idle |
client:visible | When component enters viewport |
client:only | Skips server render, client only |
Best Practices
✅ Do This:
// Next.js: use Server Components for static pages
export default function Pricing() { return <div>...</div> } // ✅
<!-- Astro: use client:visible for below-the-fold components -->
<Carousel client:visible /> // ✅
// Next.js: use revalidateTag for on-demand ISR
revalidateTag('posts') // ✅
❌ Don’t Do This:
// Next.js: materialize 200k params in generateStaticParams
return allPages.map(p => ({ slug: p.slug })) // crashes above ~150k // ❌
<!-- Astro: mark every component as client:load -->
<Header client:load /><Footer client:load /> // over-hydration // ❌
// Choose Gatsby for a new project without acknowledging maintenance status
// No new features are being added // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Next.js build crashes at scale | generateStaticParams materializes array | Stream or use Astro for >150k routes |
| Astro site ships too much JS | Over-hydrating with client:load | Use client:visible / client:idle |
| Gatsby plugin breaks | Plugin not maintained | Check plugin activity; consider alternative |
| Next.js page becomes dynamic | Using cookies() or headers() in a page | Move to a route handler or use connection() |
Real-World Examples
1. Next.js Static Page
export default function About() { return <div>About</div> }
2. Next.js Dynamic Route
export async function generateStaticParams() { return posts.map(p => ({ slug: p.slug })) }
3. Next.js Revalidation
revalidateTag('products')
4. Astro Island
<Carousel client:visible />
5. Astro Static Content
<p>No JS shipped</p>
6. Gatsby GraphQL Query
export const query = graphql`query { allMarkdownRemark { nodes { frontmatter { title } } } }`
7. Astro client:only
<Chart client:only="react" />
8. Next.js "use cache"
"use cache"
export default async function Page() { /* cached at build */ }
9. Astro Multi-Framework
<ReactComp client:load />
<VueComp client:load />
10. Framework Migration Check
Gatsby → Next.js: check plugin replacements
Visual
The JavaScript Footprint
┌──────────────────────────────────────────────────────────────┐
│ NEXT.JS ASTRO │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ React runtime │ │ Zero JS default │ │
│ │ for every page │ │ │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ All components │ │ Only client:* │ │
│ │ hydrated │ │ components │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ Content site: Content site: │
│ ~100-200 kB JS ~10-30 kB JS │
│ │
└──────────────────────────────────────────────────────────────┘
Build Scale
┌──────────────────────────────────────────────────────────────┐
│ PAGES NEXT.JS ASTRO │
│ │
│ 10,000 ✅ ✅ │
│ 50,000 ✅ ✅ │
│ 100,000 ✅ ✅ │
│ 150,000 ⚠️ crash ✅ │
│ 200,000+ ❌ ✅ │
│ │
│ Next.js materializes the params array before rendering. │
│ Astro's render path does not include React. │
│ │
└──────────────────────────────────────────────────────────────┘
The Three Rendering Models
┌──────────────────────────────────────────────────────────────┐
│ NEXT.JS ASTRO │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Server │ │ Static HTML │ │
│ │ Components │ │ + islands │ │
│ │ (static or │ │ │ │
│ │ dynamic) │ │ client:* │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ GATSBY │
│ ┌─────────────────┐ │
│ │ GraphQL │ │
│ │ build-time │ │
│ │ data pipeline │ │
│ └─────────────────┘ │
│ │
│ Each model optimizes for a different constraint. │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Next.js SSG | Static Server Components + generateStaticParams |
| Next.js hybrid | ISR, revalidateTag |
| Astro model | Static HTML by default, islands hydrate with client:* |
| Astro directives | client:load, client:idle, client:visible, client:only |
| Gatsby model | Build-time GraphQL, source plugins |
| Gatsby status | No longer actively developed |
| React Static | Abandoned (2018) |
| Build scale | Next.js ~150k page ceiling |
Key takeaways:
- Next.js is the default choice for React teams that need one framework for both static and dynamic pages. The App Router statically renders Server Components by default,
generateStaticParamshandles dynamic routes, and ISR provides revalidation without full rebuilds. The constraint is the materialized params array, which crashes above approximately 150,000 pages. - Astro is the choice when the site is content-first and JavaScript is the enemy. React components render to static HTML by default and become interactive only with a
client:*directive. The result is the smallest possible JavaScript footprint, but React is a component library in this model rather than the application framework. - Gatsby’s build-time GraphQL pipeline is still functional but the framework is in maintenance. The plugin ecosystem is the reason to choose it, but new projects should weigh the lack of active development against the data-layer convenience.
- React Static should not be considered. It was abandoned in 2018, uses Webpack 3 and Babel 6, and cannot run on modern Node.js without deprecated workarounds.
- The framework choice is a choice about the JavaScript model. Next.js ships the React runtime and hydrates the tree. Astro ships no JavaScript by default and hydrates only the islands. The performance difference on a content site is measured in hundreds of kilobytes.
Remember: Static site generation is not a single technique; it is a family of build strategies that share the property of producing HTML at build time. The React frameworks differ in how much JavaScript they ship, how they handle data, and how they scale. Next.js is the platform with the most features and the largest ecosystem. Astro is the content-first tool that treats React as one option among several. Gatsby is the legacy data pipeline that is no longer actively developed. The correct choice depends on whether the site is an application, a content site with interactive pockets, or a data-heavy build that already uses GraphQL.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!