Back to Articles
#astro#cloudflare#architecture#webdev#devops

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.

8 min read
1,779 words

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 executes npm 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:

  1. 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"
  2. Draft & Preview Locally: Run Astro’s development server with hot-module replacement:

    npm run dev
  3. Verify Types and Build: Validate types and test the static build:

    npx astro check
    npm run build
  4. 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.