My Personal Website Architecture: Why I Chose Astro, GitHub, and Cloudflare Pages
Why I built my personal website as a zero-cost static site using Astro, GitHub, and Cloudflare Pages—prioritizing longevity, sub-millisecond edge delivery, and zero maintenance over complex dynamic stacks.
As software engineers, our natural instinct when starting any personal project is often to overengineer it. We think about microservices, container orchestration, edge databases, server-side rendering frameworks, and complex caching strategies.
When I sat down to design the architecture for my personal website and technical blog, I decided to do the exact opposite.
I wanted an architecture governed by a single guiding principle: radical simplicity and durability. A technical website should not require a maintenance subscription on my personal time. It should survive for years without manual intervention, cost virtually nothing to run, deliver sub-millisecond page loads anywhere in the world, and—above all—allow me to focus entirely on writing and engineering rather than babysitting infrastructure.
Here is why I chose the combination of Astro, GitHub, and Cloudflare Pages, how the continuous deployment pipeline works, and why I chose Astro even as an avid fan of Hugo.
The Problem: The “Maintenance Tax” of Traditional Web Stacks
Before choosing a static architecture, it is worth examining the hidden maintenance tax imposed by traditional dynamic stacks.
1. The Dynamic CMS (WordPress, Drupal, Ghost)
For years, WordPress was the default choice for blogging. But a dynamic CMS brings significant operational luggage:
- Relational Database Dependency: You need MySQL/PostgreSQL running 24/7. Databases can crash, suffer corrupted tables, exhaust disk space, or require schema migrations and backups.
- Constant Security Patches & CVEs: Because PHP and WordPress power over 40% of the web, they are primary targets for automated vulnerability scanners. An unpatched plugin or theme can turn your personal blog into a crypto-miner or spam relay overnight.
- Database Caching Layers: To achieve acceptable response times, you end up bolting on reverse proxies (Varnish), Redis/Memcached object caches, and CDN edge rules—simply to serve content that changes once every few weeks.
2. The Self-Hosted VPS (Docker, Nginx, Linux)
Running your own small virtual private server (VPS) on DigitalOcean, Linode, or Hetzner gives you ultimate control, but turns you into an unpaid sysadmin:
- Routine OS updates (
apt update && apt upgrade) and kernel reboots. - Managing SSH keys, firewall rules (
ufw), and fail2ban logs. - Renewing Let’s Encrypt TLS certificates when automated cron jobs fail.
- Memory leaks or rogue processes triggering the Linux Out-Of-Memory (OOM) killer.
3. The Heavyweight Full-Stack Framework (Next.js, Remix, Nuxt)
While modern full-stack JavaScript frameworks are phenomenal for interactive SaaS applications, deploying dynamic Node.js or edge serverless runtimes for a personal blog introduces unnecessary complexity:
- Cold starts on serverless functions.
- Complex runtime environments and vendor lock-in.
- Variable hosting bills if a post goes viral on Hacker News or Reddit.
When your primary workload is serving read-only articles, every server process running between your visitor and your content is a liability.
The Architecture: Static Generation + Edge Delivery
To eliminate every point of operational friction, I settled on a modern static site architecture composed of three decoupled pillars:
flowchart LR
Dev["<b>1. Local Dev</b><br/>Astro + Jujutsu"]
Git["<b>2. GitHub</b><br/>Source of Truth"]
CI["<b>3. Cloudflare Pages</b><br/>CI/CD Build"]
Edge["<b>4. Edge CDN</b><br/>300+ Cities"]
Users(["<b>Readers</b><br/>Worldwide"]):::users
Dev -->|push| Git
Git -->|webhook| CI
CI -->|deploy| Edge
Edge -->|HTTP/3| Users
classDef users fill:#0284c7,stroke:#38bdf8,color:#ffffff,font-weight:600
1. GitHub as the Single Source of Truth
All content, components, and configurations live in a Git repository hosted on GitHub.
- Content Ownership: Posts are stored as standard Markdown (
.md) and MDX (.mdx) files with structured YAML frontmatter. There is no proprietary database lock-in. If every cloud provider vanished tomorrow, my articles remain human-readable plain text files on my local drive. - Versioned History: Every revision, typo fix, and draft is tracked with full commit history using modern VCS workflows (like Jujutsu /
jj).
2. Cloudflare Pages for Zero-Cost, Infinite-Scale Edge Hosting
Cloudflare Pages acts as both the CI/CD pipeline and the hosting provider.
- Native CI/CD Compilation: When a commit lands on
main, Cloudflare Pages automatically detects the update, spins up an isolated build container, installs dependencies, and executesnpm run build(astro build). - Global Edge Anycast Network: Cloudflare distributes the compiled static artifacts directly to data centers in over 300 cities worldwide.
- Zero Cold Starts, Zero Server Overhead: Because the output is pure HTML, CSS, and optimized static assets, there are no containers to wake up, no serverless execution limits, and no database connections to pool.
- Security by Design: There is no database to inject, no server to breach, and no backend daemon running code. The attack surface is effectively zero.
- Predictable Cost: $0.00 / month: Cloudflare Pages offers unlimited bandwidth and 500 builds per month on its free tier, making the entire infrastructure completely cost-free with zero risk of surprise bills.
Choosing Astro: Why Astro Over Hugo?
When it came to selecting the static site generator (SSG), my decision came down to two prime contenders: Hugo and Astro.
The Case for Hugo
I have long been a passionate fan of Hugo. Written in Go, Hugo is celebrated across the industry for its blistering compilation speeds. Hugo can build thousands of pages in fractions of a second, ships as a single zero-dependency binary, and has proven its reliability for over a decade.
If your site contains 50,000 pages of text, Hugo remains practically unbeatable in raw build throughput.
Why I Chose Astro for My Personal Site
Despite my admiration for Hugo, I chose Astro because its developer ergonomics, component model, and type safety provide significant advantages for modern web authoring:
1. Component Model: TypeScript & JSX vs. Go Templates
Hugo relies on Go’s html/template engine. While functional, Go templates can quickly become unwieldy when composing modular, reusable UI patterns:
- Navigating nested contexts (
$,.,range,with) in Go templates can be cryptic. - Passing structured props, formatting dates, or conditional class styling requires bespoke template functions.
Astro uses the .astro component format, which combines standard HTML with TypeScript and JSX-like expressions:
---
// src/components/HeaderLink.astro
import type { HTMLAttributes } from 'astro/types';
type Props = HTMLAttributes<'a'>;
const { href, class: className, ...props } = Astro.props;
const { pathname } = Astro.url;
const isActive = href === pathname || href === pathname.replace(/\/$/, '');
---
<a href={href} class:list={[className, { active: isActive }]} {...props}>
<slot />
</a>
This syntax feels immediately intuitive, fully typed, and effortless to refactor.
2. Content Collections & Strict Schema Validation
In Hugo, frontmatter fields are largely unconstrained. A misspelled tag or invalid date format can silently pass through build time, only to cause visual anomalies or missing links in production.
Astro introduced Content Collections, backed by Zod schemas. In src/content.config.ts, every piece of frontmatter is strictly typed:
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
id: z.string(),
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(true),
}),
});
export const collections = { blog };
Running npx astro check verifies all Markdown frontmatter at compile time. If an article is missing an id or has a malformed pubDate, the build immediately fails before reaching production.
3. Zero JavaScript by Default with the “Islands” Escape Hatch
A common problem with modern JavaScript frameworks (like Next.js or Gatsby) is shipping massive bundles of client-side JavaScript just to render static text.
Astro adheres to an Islands Architecture that outputs zero client-side JavaScript by default. If a page contains only text and styling, Astro renders clean, semantic HTML and CSS.
Crucially, if I ever want to build an interactive component—such as an interactive query benchmark, an eBPF trace visualizer, or an interactive data structure explorer—I can embed a component written in React, Svelte, Vue, or Solid, and hydrate only that specific island using directives like client:visible or client:idle.
4. Ecosystem Integration (MDX, Shiki, Rehype)
Astro provides first-class support for:
- MDX: Using Astro or UI components directly inside Markdown posts.
- Built-in Shiki: Accurate code highlighting that runs entirely at build time without requiring heavy client-side highlighting libraries like Prism.js.
- Official Integrations: Easy plugins for XML sitemaps (
@astrojs/sitemap), RSS feeds (@astrojs/rss), and Tailwind CSS.
Architectural Comparison Matrix
| Attribute | Dynamic Stack (WordPress/Ghost) | Self-Hosted VPS (Docker/Nginx) | Hugo + GitHub Pages | Astro + Cloudflare Pages (My Stack) |
|---|---|---|---|---|
| Hosting Cost | $5 – $30+ / month | $5 – $20 / month | $0 / month | $0.00 / month |
| Database | MySQL / PostgreSQL | SQLite / MariaDB | None (Static) | None (Pure Static) |
| Security Surface | High (PHP/plugins/DB) | Moderate (OS/ports) | Minimal | Zero attack surface |
| Maintenance Work | Updates, backups, spam | OS patching, SSL, OOM | Low | Zero (Push to deploy) |
| Component Model | PHP Hooks / Handlebars | Custom templates | Go html/template |
TypeScript / .astro components |
| Content Safety | Proprietary DB rows | DB dumps / files | Markdown | Strictly validated Markdown / Zod |
| Client JavaScript | Heavy | Moderate | Zero | Zero by default (Islands on demand) |
| Global CDN Delivery | Requires add-on CDN | Manual reverse proxy | GitHub CDN | Cloudflare Edge Network (300+ PoPs) |
The Authoring Lifecycle: From Thought to Production
With this architecture, authoring a new article is straightforward and distraction-free:
-
Scaffold the Post: A lightweight Node.js script generates a collision-free 6-character hex ID, formats the frontmatter, and creates the Markdown file:
npm run new-post "Post Title" "custom-slug" "tag1,tag2" -
Draft & Preview Locally: Run Astro’s development server with hot-module replacement:
npm run dev -
Verify Types and Build: Validate types and test the static build:
npx astro check npm run build -
Push to GitHub: Commit changes using Jujutsu (
jj) or Git:jj commit -m "content(blog): add post title" jj git push
Within 30 seconds, Cloudflare Pages clones the repository, runs Astro, caches the compiled assets across hundreds of edge nodes, and serves the article with sub-second Time-To-First-Byte (TTFB) worldwide.
Final Thoughts: Technology That Gets Out of the Way
The best infrastructure is the kind you never have to think about.
By offloading compilation to Cloudflare Pages CI/CD, keeping source files in GitHub, and generating pure static HTML with Astro, I gained an architecture that requires zero maintenance, incurs zero financial cost, and provides peak security and performance.
Most importantly, it removed all the operational friction between having an idea and publishing it. Instead of triaging server logs or updating database drivers, I can sit down, open an editor, and write.