Migrating Hubnet Cloud from WordPress to Azure Static Web Apps

| 12 min read

Introduction

After running my blog on WordPress in Azure for almost six years, moving from the UK to Norway unexpectedly forced me to rethink my entire hosting strategy. Rather than simply rebuilding the existing platform, I took the opportunity to migrate to Hugo and Azure Static Web Apps, reducing the operational cost from around £55 per month to almost nothing while improving performance, security and maintainability.

One of the practical aspects of moving that I hadn’t considered was that by design, it is not possible to change the Billing Country nor Billing Currency in a Microsoft Customer Agreement. This was not a major issue as my Azure/M365 spend is relatively low but nevertheless I needed a solution as this affects not only my blog but other resources I have deployed too.

Long story short, I had to create a new tenant that had its billing profile in Norway so I will be able to be billed in NOK instead of GBP. I will talk about the M365 migration in a future post but now I had an immediate priority, what did I want to do with my blog hosting platform?

Although I haven’t worked directly in web development for a long time, I used ChatGPT and Codex as engineering assistants throughout the project to accelerate research, implementation and troubleshooting.

Original Components

  • Azure App Service running WordPress
  • Azure Database for MySQL Flexible Server
  • Azure App Service Managed Certificate
  • Azure Key Vault
  • Virtual Network
  • Private DNS Zone for database
  • User-Assigned Managed Identity
Screenshot of Azure Portal showing resource group listing the current resources needed for the original WordPress site
Resource Group for existing WordPress site

Whilst all of this worked for me for the past six years or so, it’s not been cheap to run. All of these components together cost around £55 a month to operate with the exception of the Azure App Service Managed Certificate. That was around £50 a year. I know I could have arranged that myself for free using something like LetsEncrypt or Cloudflare but I just wanted something easy to maintain. I could have gone for a managed wordpress instance on WordPress.com but you get limited control so I thought the PaaS solution was a good compromise. The trade-off of this is that there were 18 supporting components in the resource group.

Now this opportunity has presented itself to migrate across to a new tenant I started thinking about what I did and did not like with the WordPress setup.

Old version of Hubnet Cloud hosted on WordPress

What I liked

  • Really easy to create new posts/drafts
  • Easy for media management too, media is automatically organised by date
  • Popular mature platform with lots of support

What I did not like

  • Needing a plugin to implement semi-advanced functionality instead of the feature being native to the platform
  • Having to update plugins and the core engine on a regular basis
  • Due to its popularity, WordPress is frequently targeted by bad actors/spammers
  • It’s very easy to break the whole site when a theme update goes wrong
  • Having to be online to work on the site
  • The operational cost was high for a personal blog site
  • Having to buy plugins/themes. A lot of them are paywalled

Choosing the right hosting solution

Clearly, lifting and shifting the site between tenants/subscriptions was going to be a non-starter. Now I had to think about what I wanted to prioritise for the new hosting framework/platform:

PriorityRequirementRationale
Must HaveEasy to migrate content from the existing WordPress siteThe migration should minimise manual effort and preserve existing content, and metadata where possible.
Must HaveCheap or free to operateThe solution should minimise ongoing hosting, licensing and maintenance costs.
Must HaveUse custom domainThe hosting platform should the support the use of custom domains.
Should HaveCI/CD for deploymentChanges should be automatically built, validated and deployed from source control using a CI/CD pipeline.
Should HaveMultiple language supportEspecially now I live in Norway I want to make sure my content is accessible in both Norsk Bokmål and Norsk Nynorsk in addition to English.
Should HaveNot be reliant on third parties for core functionalityCore site functionality should be self-contained and not depend on external SaaS platforms or proprietary services beyond the hosting platform.
Should HaveUtilise open-source components where possiblePreference should be given to mature open-source technologies to reduce vendor lock-in and benefit from community support.
Should HaveMore control over the site’s content and structureThe solution should support storing and serving site assets directly from the project repository, reducing reliance on external storage services and making the site more self-contained.
Should HaveManaged TLS CertificatesThe hosting platform should have built-in managed TLS certificates, ideally without extra cost.
Could HaveAbility to work on the site while offlineThe development workflow should support creating and editing content without an internet connection.
Could HaveEasy local development experienceI should be able to run, preview and test the site locally with minimal setup.
Could HaveHost in Microsoft AzureWhere practical, the solution should be deployable within Azure to align with existing skills and infrastructure.
Won’t Have (for now)Dynamic CMS editing, user authentication, comments or other server-side functionalityThese features are outside the scope of the initial migration and can be considered in the future if required.

Choosing the framework

I captured these priorities and evaluated several frameworks against them. ChatGPT helped produce an initial comparison matrix, which I then refined based on my own requirements and experience. Each priority was scored 1-5 with 1 being poor and 5 being excellent.

RequirementPriorityHugoEleventyAstroJekyllNext.jsBlazor WASMGhostWordPress
Easy to migrate content from WordPressMust44343255
Cheap or free to operateMust55554522
Use custom domainMustN/AN/AN/AN/AN/AN/AN/AN/A
CI/CD for deploymentShould55545533
Multiple language supportShould53434424
Not reliant on third parties for core functionalityShould55554544
Utilise open-source components where possibleShould55555555
More control over the site’s content and structureShould55554532
Managed TLS CertificatesShouldN/AN/AN/AN/AN/AN/AN/AN/A
Ability to work on the site while offlineCould55544532
Easy local development experienceCould44434534
Host in Microsoft AzureCould55555545

Lots of choice but Ghost and WordPress were immediately removed from consideration because of operational costs not meeting my requirements. Blazor WASM was also immediately discounted as its biggest weakness in the context of my requirements is content management.

Hugo was the clear winner from this comparison but loses out on full marks because it doesn’t get full IDE/Debugging support like Blazor WASM does in Visual Studio and there’s some manual work involved in transferring WordPress content over.

Framework Choice: Hugo

Choosing the platform

I repeated the same exercise for the hosting platform but with the added constraint of natively supporting Hugo static sites.

RequirementPriorityAzure Static Web AppsAzure App ServiceGitHub PagesCloudflare PagesNetlifyVercel
Cheap or free to operateMust525555
Custom domainsMust554555
Native support for Hugo deploymentMust534555
CI/CD for deploymentShould454555
Multiple language supportShould555555
Not reliant on third parties for core functionalityShould554333
Utilise open-source components where possibleShould555555
More control over the site’s content and structureShould555555
Managed TLS certificatesShould554555
Ability to work on the site while offlineCould555555
Easy local development experienceCould554555
Host in Microsoft AzureCould551111

Given my Azure background and the scoring against my requirements, Azure Static Web Apps was the clear choice.

Platform Choice: Azure Static Web Apps

Getting started

This was probably the hardest part because I’d never worked with Hugo previously and wasn’t sure what to expect. First, I made a new repo in my GitHub account. Then I had to think a lot about the appearance of the new site. As Hugo is a much newer platform than WordPress, there are fewer themes available. I spent an evening browsing the available themes on GoHugo and ended up settling for CareerCanvas by Felipe Cordero.

Screenshot of CareerCanvas Template on Hugo Template Gallery
CareerCanvas Template on Hugo Template Gallery

After cloning the repo locally, I then spent a good while customising the site to my preferences and making the content/site structure applicable to my personal profile. I also spent a lot of time making localised translations for my CV and other “static content”. One area where ChatGPT Work Mode genuinely saved a lot of time was localisation. I used it to generate Bokmål and Nynorsk versions of my Markdown files, then reviewed and refined the output myself. Apologies to my Norwegian teacher Ingrid, if you’re reading this. I was very impressed with the output. The translations were grammatically correct, read naturally rather than literally, and retained English technical terms where appropriate. This really sped up the development process.

I made a few changes to the CSS/theme layout partials to improve performance and change the layout to how I wanted. For instance, I configured SVG flags instead of assuming browsers would load the correct flag emoji. The DevIcon library used in the techstack section of the site is missing some technologies I use so I brought in some local SVG files that are used instead which look better than the backup FontAwesome icons.

I’ve found the i18n functionality in Hugo for translations really useful. Just define translation configuration TOML files with consistent language keys and you can dynamically change text depending on localisation.

Migrating content from old WordPress site

During my research, I discovered the WP2Hugo Project which downloads all content from a WordPress site and automatically converts all pages/posts into markdown. It also downloads all media. This was much quicker than I had originally envisaged, which involved exporting the WordPress XML manually before converting it myself.

This also gave me a chance to tidy up the content and make sure that the metadata was consistent. I then bulk translated all the historic blog posts. This one took a bit more effort than before as work/agent mode wasn’t that efficient with the translations using the model I had selected (GPT 5.6 Terra Medium). I had to do several iterations for all the text to get translated. It also seemed to consistently miss comments in code blocks.

Setting up Azure Static Web Apps and the CI/CD pipeline

This was really simple. I just made a static web app in my new subscription, pointed it to the main branch of my GitHub repo and that was configured. I have reduced my Azure footprint in this context from 18 to 1. In hindsight, I should have done this at the beginning of the project so the config was in place in my feature branch. Anyway, configured the GitHub actions workflow, pulling in Microsoft Clarity project Id and Google Analytics tag configurations from the repo secrets on deployment. Deployment consistently takes under 2 minutes. Much faster than having to maintain the WordPress site where engine or plugin updates could take a while to complete.

Screenshot of Azure Portal showing Azure Static Web App resource
Azure Static Web App in Azure Portal
Screenshot of GitHub Action Execution History
GitHub Action Execution History
Screenshot of a successful GitHub Action execution
Successful GitHub Action Execution

It took a bit of trial and error but the deployments eventually triggered after every merge into the main branch. Not needed in feature branches as I can run the site locally.

Optimisation

This took the most time. I must have spent a few days on this. First thing I did after deploying was get my partner to access the site. Some things weren’t loading on Safari and Firefox on iOS but were fine on other browsers. I really wasn’t sure why this was happening. I investigated the issues with the help of ChatGPT, eventually tracing the problem to layout partials that weren’t optimised for mobile browsers. After getting that working, I ran a PageSpeed Insights report on the site and it suggested quite a few points of improvement in Performance, Accessibility, Best Practices, SEO and Agentic Browsing over several iterations.

PlatformFirst ReportFinal Report
Mobile
Desktop

It’s worth noting that the PageSpeed Insights mobile report simulates a poor 4G connection, which somewhat skews the results. I worked through each PageSpeed report, using ChatGPT to explain unfamiliar recommendations and suggest potential improvements.

Key Issues resolved:

  • External CSS libraries are now hosted locally
  • Duplicated CSS library imports
  • Profile picture and hero backgrounds are responsive depending on the client screen size
  • All images that were PNG or JPEG have been converted to WEBP or AVIF formats to improve performance
  • Site now optimised for screen readers
New version of Hubnet Cloud hosted on Azure Static Web Apps

Conclusion

This was a fun project allowing me to massively improve the hosting of my blog site without costing lots of money. It taught me a lot about how static sites work and how much more modern/dynamic than their CMS ancestors like WordPress.

More importantly, the migration reduced my Azure footprint from eighteen resources to one while improving performance, security, maintainability and reducing operational costs to almost zero.

Throughout the project I used ChatGPT and Codex as engineering assistants rather than code generators. They were invaluable for exploring unfamiliar areas, generating initial implementations and explaining browser behaviour, but every architectural decision, code review and final implementation remained my responsibility.

To conclude, moving countries unexpectedly became the catalyst for modernising my entire blogging platform and, in hindsight, I’m very pleased with the outcome.

Acknowledgements