The idea of writing isn’t new. It has been in my head for years, with always an excuse not to. One of the bigger ones was that the site never quite felt right. Bollocks, of course. It is the fear of starting, to be honest. My work gives me enough topics to share: problems, opinions, examples, and plenty of things worth writing down.
A couple of years ago I started looking for something easy to maintain. Until then I had hosted my photography on WordPress, but it required more infrastructure than I wanted to deal with. Through my own work in Azure I came across Static Web Apps, and that was exactly the level of infrastructure I was looking for. Stack decided, on to the technology.
This post covers what I ended up with, why, and a few things worth knowing if you’re considering something similar.
The stack#
- Hugo: static site generator
- Blowfish: Hugo theme
- Azure Static Web Apps: hosting
- Azure Blob Storage: media (gallery photos)
- Terraform with AVM: infrastructure as code
- GitHub Actions: CI/CD
Hugo#
So the search for the right technology began. Static site generators was the first term on Google and quickly Hugo and Jekyll were the first to pop up. A quick comparison and test showed my personal preference for Hugo, just a gut feeling, no real other reason.
One of the big advantages of static site generators is the immediate feedback loop. You run a local server, make a change, and the page refreshes instantly. I love working that way.
Technology decided, now a theme. Here another struggle began. So many options, just like I had with WordPress. Luckily most have demo pages so you can see what they look like before committing. I looked at whether the structural interface was appealing, but also whether the documentation was there to help me along.
Then I came across Blowfish. The look and feel were right, the documentation was solid, and it allows for quite some customisation if you want to go further.
Theme decided, time to build the infrastructure.
Azure Static Web Apps#
So for those who are not familiar with Azure Static Web Apps, a little background. It is a hosting service built specifically for, as the name suggests, static sites. So you are able to host sites like Hugo, Jekyll and Gatsby. But you are also able to host sites created with JavaScript frameworks like Angular, React and Vue or Next.js and many more. Last but certainly not least you can integrate a Static Web App with a Function App as an API backend. This gives you quite some options and integrations.
A few useful things to know:
- Free SSL on both the default hostname and custom domains
- GitHub Actions integration out of the box. The deploy workflow is generated automatically when you link a repository.
- Staging environments for pull requests, automatically provisioned and torn down
- Custom domain support including apex domain, without needing Azure Front Door or a Traffic Manager
- Authentication via Entra ID and GitHub included out of the box, both available on the free tier
The free tier is sufficient for a personal blog. No compute costs, no storage costs for the static assets.
The limitation worth knowing: Static Web Apps doesn’t support server-side rendering. If you need dynamic content (user authentication, API backends, database queries), you’d need to integrate with Azure Functions or reach for a different hosting model. For a static site that’s not a constraint at all.
Terraform and AVM#
As I work with Terraform quite a bit, managing the infrastructure with it was an easy choice. It gives full control over the deployed resources and their configuration, and adding new pieces later is straightforward.
Writing Terraform for Azure has become much easier since Microsoft is also chipping in with AVM, Azure Verified Modules. It is Microsoft’s standard for Terraform (and Bicep) modules for Azure. The modules follow a strict specification and are created and maintained by Microsoft or verified community contributors. You and I can also contribute features and bug fixes, which is part of what keeps the quality up.
In practice it gives you modules that are structured consistently and easy to adopt. Instead of writing modules yourself every time, you pick up something that already meets the standard.
AVM deserves its own post, and it will get one. There is more to say about the ecosystem, the structure, and the migrations that are happening.
GitHub Actions#
GitHUb Actions are a method to configure actions based on the repository structure and anything you could reach during a action. It gives amazing options to automate stuff. And we did in this case to deploy the Hugo content to the Static Web App.
The deployment workflow is simple. Every push to main, hugo builds the site and that build is deployed to the Static
Web App via the official Azure/static-web-apps-deploy action.
- name: Build and deploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: upload
app_location: "/"
output_location: "public"The one non-obvious thing: Hugo can use Git submodules for themes. If your Actions workflow doesn’t check out submodules, the build will fail silently. Hugo will build, but the theme won’t be there and you’ll get an empty site. The fix is one line in the checkout step:
- uses: actions/checkout@v4
with:
submodules: recursivePull requests get a staging environment automatically. GitHub posts a comment with the preview URL. When the PR closes, the environment is torn down. This is built into Static Web Apps, no extra configuration required.
What I’d do the same#
The Hugo + Static Web Apps combination works well for this use case. Build time is fast, the deploy pipeline is simple, and there’s nothing running in production that can break at 2am.
Terraform for the infrastructure was the right call even for something this small. The API key for GitHub Actions comes out as a Terraform output. Custom domain configuration is in code. If I need to rebuild this from scratch, or set up something similar for a client, the state is captured.
What I’d approach differently#
Blowfish is a good theme but Tailwind CSS complicates customisation. The compiled CSS only includes the utility classes actually used in the theme source. If you want to use a Tailwind class that isn’t in the compiled output, you either write plain CSS overrides or you rebuild the Tailwind CSS from source. Both are manageable, but it’s worth knowing before you start.
The media workflow, uploading photos to Blob Storage manually via Azure CLI or Storage Explorer, is functional but not particularly smooth. I’ll probably automate it at some point.
