New Static Blog (2025 Edition)

Sigh. Here we are again. Over a year with no new content and yet another blog rebuild. Why? Managing a VPS running Ghost made little sense for something I rarely update, especially when I wanted this project to be hands-off. So instead of managing an entire server, I’m returning to a static site.

So what does this version look like? It’s powered by Hugo. All posts are written in Markdown, and the site builds using GitHub Actions whenever I push to GitHub. It’s hosted on AWS S3 with CloudFront as the CDN. Like my previous static site implementation, I’m using a CloudFront viewer request function that automatically appends /index.html to clean URLs. This allows the site to serve clean URLs while maintaining the underlying static file structure.

Here’s the GitHub Actions workflow I use to deploy the site:

name: Static Site Deployment
on:
  workflow_dispatch: # Allow manual trigger
  push:
    branches: [main] # Auto-deploy on push to main
jobs:
  deploy:
    name: Build and Deploy
    runs-on: ubuntu-latest
    permissions:
      id-token: write # Required for AWS OIDC authentication
      contents: read
    environment:
      name: ${{ github.ref_name }}
    steps:
      - name: 🛒 Checkout
        uses: actions/checkout@v5
      - name: ✨ Setup Hugo
        env:
          HUGO_VERSION: 0.148.2
        run: |
          # Download and install Hugo binary
          mkdir ~/hugo
          cd ~/hugo
          curl -L "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz" --output hugo.tar.gz
          tar -xvzf hugo.tar.gz
          sudo mv hugo /usr/local/bin
      - uses: aws-actions/configure-aws-credentials@v4.3.1
        with:
          # Authenticate with AWS using OIDC (no long-lived credentials needed)
          aws-region: ${{ vars.AWS_REGION }}
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
      - name: 🛠️ Build
        run: |
          # Generate the static site to public/ directory
          hugo
      - name: 📦 Upload to S3
        run: |
          # Sync the built site to S3, deleting removed files
          aws s3 sync public/ s3://${{ vars.STATIC_SITE_BUCKET_NAME }}${{ vars.STATIC_SITE_KEY_PREFIX }} --delete

New content typically goes live within 30 seconds of pushing to GitHub. Simple, fast, and maintenance-free, exactly what I wanted.