Context
Next.js supports multiple rendering strategies, each with different trade-offs:
- SSG (Static Site Generation): Pre-render pages at build time, serve static HTML from CDN
- SSR (Server-Side Rendering): Render pages on-demand per request, requires a Node.js server
- ISR (Incremental Static Regeneration): Hybrid approach, static pages with background revalidation
- CSR (Client-Side Rendering): Ship minimal HTML, render everything in the browser
For a personal portfolio and blog, I need to choose a rendering strategy that balances:
- Performance: Fast page loads globally, minimal Time to First Byte (TTFB)
- Cost: Predictable, ideally free for a side project
- SEO: Fully formed HTML for search engines and social media previews
- Simplicity: Minimal infrastructure to maintain, following "Choose Boring Technologies"
- Developer Velocity: Fast iteration, no complex deployment pipelines
I also need a strategy that acts as a forcing function to keep the architecture simple. Constraints breed creativity—if I can't reach for a database or server-side processing by default, I'm forced to build leaner, faster, client-side solutions.
Decision
I will use Static Site Generation (SSG) as the rendering strategy for this site.
All pages are pre-rendered at build time and served as static HTML from Cloudflare Pages' global CDN. Content lives as MDX files in the git repository, making the entire site "content as code."
This is an intentionally temporary decision. SSG is the right choice for now while the site is simple and content-driven.
I foresee two distinct migration paths depending on how the project solves:
- To ISR: If content volume grows to thousands of pages and build times become the primary bottleneck.
- To SSR: If dynamic user features (personalization, auth-gated routes) become a requirement.
Starting with SSG forces me to exhaust simple solutions before accepting the complexity of either path.
Build-Time Rendering
Next.js generates static HTML for all routes during the build process:
- Blog posts, project pages, and ADRs are rendered from MDX files
- All routes are discoverable at build time (no dynamic segments requiring runtime rendering)
- The output is pure HTML, CSS, and JavaScript—no Node.js runtime required
SSG as a Forcing Function
By committing to SSG, I'm deliberately constraining the solution space:
- No database? Content lives in git, versioned and reviewed like code
- No server-side search? Build a client-side search experience with tools like fuse.js that's instant and works offline
- No server-side filtering? Client-side filtering is faster anyway—no round trip, no loading states
- No authentication? Public-first content by default, rethink what actually needs to be private
- No server-side analytics? Client-side or privacy-focused edge analytics
These constraints don't limit capability—they push toward better solutions. Client-side search is often faster than server-side because there's no network latency. Static content served from a CDN 5ms from the user beats dynamic rendering from a distant server.
Alternatives Considered
SSR (Server-Side Rendering):
- Pros: Dynamic per-request rendering, personalized content, real-time data, auth-gated pages
- Cons: Requires Node.js server runtime, higher hosting costs, slower TTFB (even with edge), introduces server failure modes, more complex deployments
- Why not now: No current need for personalization or dynamic data. SSG's simplicity and performance win for a content site. Will revisit when adding user features or real-time data.
ISR (Incremental Static Regeneration):
- Pros: SSG-like first load performance (cached static HTML) with background revalidation, no full site rebuilds when updating a single page, better UX (instant cached content while fresh version generates)
- Cons: More complex mental model (stale-while-revalidate semantics), requires edge compute or Vercel, harder to reason about cache states, adds complexity for minimal gain at current scale
- Why not now: With a small content corpus, full rebuilds are fast (~2 minutes) and happen automatically via git push. ISR's benefit—avoiding full rebuilds—doesn't matter when rebuilds are quick. Will consider when the site has hundreds of pages and rebuild times become painful (10+ minutes), or when I need to update content without git commits (e.g., pulling from a CMS).
CSR (Client-Side Rendering / SPA):
- Pros: Maximum interactivity, no server dependencies, works offline as PWA
- Cons: Poor initial page load performance, terrible SEO (requires JS execution), loading spinners everywhere, inaccessible until JavaScript loads
- Why not: SEO and performance are non-negotiable for a public portfolio/blog. Need instant paint and fully formed HTML for social media previews.
Pure Static HTML (no React/Next.js):
- Pros: Ultimate simplicity, minimal bundle size, Hugo/Jekyll style, no JavaScript required
- Cons: No component reusability, no rich interactions, harder to build interactive demos or data visualizations, limited ecosystem
- Why not: Want the ability to build rich interactive content (diagrams, visualizations, code playgrounds) without leaving the framework. React components provide this escape hatch while keeping most content static.
Consequences
Positive:
- Performance: Sub-50ms TTFB globally. Pre-rendered HTML served from Cloudflare's edge network in 275+ cities. No cold starts, no server latency.
- Cost: Zero runtime costs. Cloudflare Pages free tier includes unlimited bandwidth for static assets. No server bills, no database costs.
- Reliability: If the CDN works, the site works. No backend to crash, no database to fail, no rate limits to hit. Static files are the most reliable deployment artifact in computing.
- SEO: Fully formed HTML at request time. Search engines and social media bots see complete content immediately, no JavaScript execution required.
- Security: No server attack surface. No SQL injection, no RCE vulnerabilities, no runtime exploits. Static files can't be compromised at runtime.
- Developer Experience: Content as code. Blog posts and documentation are MDX files in git, reviewed via pull requests, versioned alongside code. Content changes are code changes.
- AI-Native Workflow: Claude Code has direct access to all content in the git repository. No need to query databases or fetch from APIs—the AI can read, search, and modify blog posts, project documentation, and ADRs as easily as application code. Content living in MDX files makes the entire site LLM-readable and editable.
- Simple Deployments: Git push triggers build and deploy. No server provisioning, no container orchestration, no database migrations. Build succeeds or fails atomically.
- Forcing Function for Simplicity: The constraint of no backend forces creative, client-side solutions that often result in better UX. No lazy reaching for a database when localStorage or static JSON suffices. No server-side search when client-side is faster. The limitation breeds innovation.
- Offline Capable: Static sites can be cached aggressively. With service workers, the entire site can work offline. No server means no network dependency after first load.
- Boring Technology: Static files served from a CDN is the oldest, most proven pattern in web infrastructure. Nothing is more boring, predictable, or well-understood.
Negative:
- Build Time Scales with Content: Every content change triggers a full rebuild. As the blog grows (hundreds of posts), build times will increase. Cloudflare Pages currently builds in ~2 minutes. At scale, this could become 10+ minutes. Mitigation: ISR or partial builds if this becomes painful.
- No Dynamic Content: Can't show user-specific data, personalized recommendations, or real-time information without client-side JavaScript. Everything must be pre-rendered or fetched client-side.
- Content Staleness: Content updates require a full rebuild and deploy. Can't update a single blog post instantly—must wait for the full build pipeline. Acceptable for blog cadence (weekly/monthly posts) but problematic for high-frequency content.
- No Auth-Gated Content: Can't have private pages or member-only content without client-side workarounds. If this site ever needs authentication, SSR becomes necessary, though Cloudflare Pages Middleware (Edge Functions) offers a middle-ground solution for basic gating without a full origin server.
- Future Migration Effort: Choosing SSG now means eventual migration work when requirements demand SSR/ISR. Routes will need refactoring, data fetching patterns will change, hosting setup may need to evolve (e.g., moving from Cloudflare Pages to Vercel or self-hosted). This is accepted technical debt—the velocity gains from simplicity now outweigh the future migration cost.
- Edge Case Limitations: Some Next.js features (middleware, API routes, dynamic OG image generation) require runtime environments. Using these breaks the pure-static constraint and forces hybrid deployment models or workarounds.
- Manual Image Optimization: Using
output: 'export'disables Next.js' default on-demand image optimization API. Images must be optimized at build time or handled by an external service (like Cloudflare Images) to avoid shipping uncompressed assets. This adds infra complexity (scripts, accounts) to replace what was "free" in SSR.