Welcome to the first post on this site. Rather than a generic introduction, let me walk through how this blog itself was built — it covers a few interesting decisions.

The Stack

The site is a fully static build using Astro. At build time, every page — including every blog post — is pre-rendered to plain HTML and pushed to S3, fronted by CloudFront. There’s no server, no database, and no runtime. Fast, cheap, and trivially scalable.

Browser → CloudFront → S3 (static HTML/CSS/JS)

Why Astro?

Astro’s content collections are a first-class way to manage a blog with Markdown/MDX. You get:

  • Zod schema validation for frontmatter
  • Built-in Shiki syntax highlighting
  • RSS feed support
  • Near-zero client JS by default

Compare that to Next.js, which ships a React runtime and hydration overhead you simply don’t need for a content site.

The Infrastructure

Everything in infra/ is plain Terraform. The main pieces:

ResourcePurpose
aws_s3_bucketStores the built site files
aws_cloudfront_distributionCDN, HTTPS, edge caching
aws_acm_certificateTLS cert (must be in us-east-1)
aws_route53_recordDNS A/AAAA aliases to CloudFront
aws_cloudfront_functionURI rewrite (/about/about/index.html)

The Subdirectory Routing Problem

This one trips people up. When you navigate to /about, CloudFront forwards that request to S3 looking for a key named about. That key doesn’t exist — the actual file is about/index.html. CloudFront’s default_root_object only applies to the root /, not subdirectories.

The fix is a CloudFront Function (viewer-request event) that rewrites any URI with no file extension:

function handler(event) {
  var request = event.request;
  var uri = request.uri;

  if (!uri.includes('.', uri.lastIndexOf('/'))) {
    request.uri = uri.replace(/\/?$/, '/index.html');
  }
  return request;
}

Tiny, runs at the edge, solves the problem cleanly.

Blog Posts

Writing a new post is just creating a new .mdx file in src/content/blog/:

touch src/content/blog/my-new-post.mdx

Commit and push — GitHub Actions builds the site and syncs it to S3. The CloudFront invalidation fires automatically so the new content is live within seconds.

What’s Next

A few things I want to add:

  • Tag pages for static SEO (currently a client-side filter)
  • Open Graph image generation per post
  • A short TIL (Today I Learned) section for quick notes

If you find any issues or want to see how something works, the source is on GitHub.