Architecture of a Modern Static Blog
A deep dive into how asimon.blog is built: Next.js, AWS, and the philosophy behind static-first engineering
title: "Architecture of a Modern Static Blog" summary: "A deep dive into how asimon.blog is built: Next.js, AWS, and the philosophy behind static-first engineering" date: "2025-12-28" tags: ["architecture", "nextjs", "aws", "infrastructure"] topics: ["infrastructure", "static-sites", "cost-optimization"] prerequisites: [] related: ["2025-12-30-building-an-interactive-terminal-shell"] author: "asimon" published: true
Architecture of a Modern Static Blog
When I set out to build this blog, I had a simple goal: create a fast, maintainable site that costs almost nothing to run. The result is a static-first architecture that combines modern tooling with cloud infrastructure designed for simplicity.
This post walks through the key architectural decisions and why they matter.
Why Static?
Before diving into the stack, it's worth explaining why I chose static site generation over a traditional server-rendered approach.
The Static Advantage
Static sites have zero runtime. No servers to manage, no cold starts, no scaling concerns. Your CDN serves pre-built HTML files directly to users.
Three reasons to go static:
- Speed - Pre-rendered HTML loads instantly. No database queries, no server-side rendering delays.
- Cost - Hosting static files on S3 + CloudFront costs pennies per month, even with decent traffic.
- Simplicity - No runtime means no runtime bugs. The site either builds correctly or it doesn't.
The trade-off is that dynamic content requires workarounds. But for a blog, that trade-off is easy to accept.
The Stack
Here's what powers asimon.blog:
| Layer | Technology | Purpose | |-------|------------|---------| | Framework | Next.js 16 (App Router) | React-based static site generation | | Content | MDX | Markdown with embedded React components | | Styling | Tailwind CSS | Utility-first CSS framework | | Hosting | AWS S3 + CloudFront | Static file storage and CDN | | CI/CD | GitHub Actions | Automated builds and deployments | | Analytics | Google Analytics 4 | View counts and traffic data |
Next.js with Static Export
Next.js supports a static export mode that generates plain HTML files at build time. No Node.js server required in production.
The App Router gives us file-based routing with React Server Components, but in static mode, everything is pre-rendered. The result is a folder of HTML, CSS, and JavaScript files that any static host can serve.
MDX for Content
Blog posts are written in MDX - Markdown that supports React components. This lets me embed interactive elements directly in posts without building separate pages.
Each post lives in src/content/ as an .mdx file with YAML frontmatter:
---
title: "Post Title"
date: "2025-12-28"
summary: "A brief description"
tags: ["tag1", "tag2"]
---
At build time, a script reads all MDX files and generates a metadata index that powers the post listings.
Content Pipeline
Here's how content flows from MDX files to the production site:
src/content/*.mdx
↓
Build Script
↓
posts-metadata.json ← GA4 View Counts
↓
Next.js Build
↓
Static HTML
↓
S3 Bucket
↓
CloudFront CDN
↓
Users
View Counts at Build Time
One interesting challenge: displaying view counts on a static site. The typical approach is a client-side API call, but that adds complexity and latency.
My solution: fetch view counts from Google Analytics during the build process, then embed them in the static HTML.
Build-Time Analytics
A Node.js script queries the GA4 Data API before each build, writes the results to a JSON file, and Next.js includes that data in the static pages. No client-side API calls needed.
This means view counts are "stale" between builds, but for a blog, that's perfectly acceptable. The site rebuilds on every push to main, so counts stay reasonably current.
Infrastructure
The AWS infrastructure is intentionally minimal:
S3 + CloudFront
- S3 Bucket: Stores the static files (HTML, CSS, JS, images)
- CloudFront Distribution: Global CDN that serves content from edge locations
- Origin Access Control: Ensures S3 is only accessible via CloudFront
CloudFront handles HTTPS termination with an ACM certificate and applies security headers (HSTS, CSP, X-Frame-Options).
Edge Functions for URL Rewriting
One challenge with static exports: Next.js generates files like /about/index.html, but users expect clean URLs like /about.
CloudFront Functions handle this at the edge:
Request: /about
↓
CloudFront Function
↓
Rewrite: /about/index.html
↓
S3 Response
This runs at CloudFront edge locations with sub-millisecond latency, no Lambda cold starts.
Deployment
Every push to the main branch triggers a GitHub Actions workflow:
- Install dependencies (pnpm with caching)
- Fetch GA4 view counts (via Google Analytics Data API)
- Build static site (
next build) - Upload to S3 (
aws s3 sync) - Invalidate CloudFront cache (so changes appear immediately)
- Run smoke tests (Playwright E2E tests against production)
Infrastructure Drift Detection
Before deploying, the workflow checks that the CloudFormation template matches what's deployed. This catches accidental infrastructure changes before they cause problems.
The entire workflow runs in about 3-4 minutes, including the E2E tests.
Design Philosophy
Beyond the technical stack, a few principles guide the site's design:
Terminal Aesthetic
The site leans into a terminal/directory aesthetic - JetBrains Mono font, directory-style post listings, and an interactive terminal shell you can open with the backtick key.
This isn't just for looks. The terminal metaphor creates a consistent mental model for navigation and exploration.
Progressive Enhancement
The core reading experience works without JavaScript. Posts render as static HTML with CSS styling. JavaScript enhances the experience (theme toggle, terminal shell) but isn't required.
Accessibility First
Keyboard navigation, proper ARIA attributes, reduced motion support, and WCAG AA color contrast. These aren't afterthoughts - they're built into the component library from the start.
Cost
One of the best parts of this architecture: it's cheap to run.
| Service | Monthly Cost | |---------|--------------| | S3 Storage | ~$0.02 | | CloudFront | ~$0.10 | | Route 53 | $0.50 | | Total | ~$0.62/month |
That's for a fully-featured blog with global CDN distribution, HTTPS, and automated deployments. The cost of a fancy coffee, once a month.
Trade-offs
No architecture is perfect. Here's what I gave up:
- Real-time data: View counts and other dynamic data are only updated at build time
- Comments/interaction: Would require a third-party service or separate backend
- Search: Client-side search only, no server-side full-text search
For a personal engineering blog, these trade-offs make sense. For a different use case, they might not.
Wrapping Up
Static-first doesn't mean simple or limited. With the right tooling, you can build sophisticated sites that are fast, cheap, and easy to maintain.
The source code for this blog is available on GitHub if you want to dig into the implementation details. And if you want to experience the terminal aesthetic firsthand, press the backtick key (`) anywhere on the site.
Next up: Building the Interactive Terminal Shell - a deep dive into the terminal overlay feature.