Journal

Why I Moved from SPAs to Static Site Generation

I spent years building SPAs with React and Vue. Client-side routing, state management libraries, loading spinners, hydration bugs. It all felt like the cost of doing business. Then I started questioning the business.

The SPA Tax

A typical SPA delivers a near-empty HTML shell, then boots a JavaScript application that renders the page. For a content site, the sequence looks like:

  1. Browser requests page → receives empty <div id="root">
  2. Downloads 200-400KB of JavaScript
  3. Parses and executes the bundle
  4. Fetches data from an API
  5. Renders the actual content

The user stares at a blank screen (or a spinner) through steps 1-4. Search engines may or may not execute your JavaScript. Social media crawlers definitely won’t.

This makes sense for Gmail. It doesn’t make sense for a blog, a documentation site, a marketing page, or a portfolio.

What Static Site Generation Gets Right

An SSG builds every page at deploy time. The output is plain HTML, CSS, and minimal JavaScript. The sequence:

  1. Browser requests page → receives complete HTML with content
  2. CSS renders the page immediately
  3. Optional: small JS enhances interactivity

First contentful paint drops from seconds to milliseconds. No API calls, no loading states, no hydration mismatch errors.

Performance

Static HTML served from a CDN is as fast as the web gets. There’s no server to be slow, no database to query, no cold start to wait for. Time to first byte (TTFB) is typically under 50ms.

For my portfolio site, moving from a Next.js SPA to Astro dropped the Lighthouse performance score from the high 70s to a consistent 98-100.

SEO

Every page is fully rendered HTML. Search engines index it immediately. OpenGraph tags work without a headless browser. RSS feeds are generated at build time. Sitemaps are automatic.

No more “but did Google render the JavaScript?” anxiety.

Security

Static files have no attack surface. No SQL injection, no XSS through server-rendered user input, no authentication bypass. Your CDN serves files. That’s it.

Hosting

Static sites can be hosted anywhere — Netlify, Vercel, Cloudflare Pages, an S3 bucket, a $5 VPS with Nginx. No runtime to manage, no server to patch.

When SSG Falls Short

Static generation isn’t universal. It struggles with:

  • User-specific content — dashboards, feeds, settings pages
  • Real-time data — stock tickers, live scores, chat
  • Frequent content changes — if you publish every 5 minutes, rebuilding the entire site each time is wasteful
  • Very large sites — 100,000+ pages can have long build times

For these cases, server-side rendering (SSR) or hybrid approaches work better.

The Hybrid Middle Ground

Modern frameworks blur the line:

Astro ships zero JavaScript by default and lets you add interactive “islands” only where needed. A static page with one interactive form widget only loads JS for that widget.

Next.js supports static generation, server rendering, and incremental static regeneration in the same project.

Nuxt offers similar flexibility in the Vue ecosystem.

The pattern I’ve settled on: static by default, server-rendered for authenticated pages, client-side for genuinely interactive widgets. This covers 95% of what I build.

The Developer Experience Shift

Moving to SSG simplified my stack:

  • No state management library — data is resolved at build time
  • No loading states — content is always there
  • No client-side routing bugs — links are just <a> tags
  • No hydration mismatches — there’s nothing to hydrate
  • No API layer for content — Markdown files in a directory

My build pipeline went from “compile TypeScript, bundle React, generate service worker, deploy to Node.js server” to “build static HTML, push to CDN.”

Fewer dependencies, fewer failure modes, fewer things to debug at 2am.

The Pragmatic Takeaway

Don’t build a SPA when a static site will do. Don’t build a static site when you need real-time interactivity. The best architecture is the simplest one that solves your actual problem.

For content-driven sites — which is most of the web — static generation with selective interactivity is the sweet spot. Ship HTML. Enhance progressively. Your users (and your Lighthouse score) will thank you.