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:
| Resource | Purpose |
|---|---|
aws_s3_bucket | Stores the built site files |
aws_cloudfront_distribution | CDN, HTTPS, edge caching |
aws_acm_certificate | TLS cert (must be in us-east-1) |
aws_route53_record | DNS A/AAAA aliases to CloudFront |
aws_cloudfront_function | URI 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.