Why static?
This blog has run on WordPress for over a decade. Great for writing, but it's also a PHP app, a MySQL database, and a permanent stream of plugin updates and security patches for a site that is, in the end, just text and screenshots. Azure Static Web Apps serves plain HTML/CSS/JS straight from a CDN, deploys automatically from GitHub, and is free for a site this size. So: export WordPress to static files once, and never patch a database again.
Here's the step-by-step guide to doing this yourself — including the two gotchas that will most likely trip you up along the way.
Prerequisites
- A WordPress site with admin access, so you can install a plugin
- A GitHub repository to hold the exported site
- An Azure subscription — the Free tier is enough for a blog-sized site
- ImageMagick installed locally — only needed if your export ends up over 250MB (Step 3)
- Access to your domain's DNS settings — only needed if you're adding a custom domain (Step 4)
Step 1 — Export WordPress as static HTML
- Install and activate the Simply Static plugin on your WordPress site.
- Run an export from the plugin. It crawls the live site and writes out every page as a folder with an
index.html, pluswp-content/uploads,wp-content/themesandwp-includesalongside it — a byte-for-byte snapshot of what visitors already see, with none of the PHP. - Download the resulting zip file.
- Unzip it into a
website/folder inside your GitHub repo.
Checkpoint: open website/index.html locally in a browser. If your homepage renders — even with a few broken asset paths, since those still assume the live domain — the export worked and you're ready for Step 2.
Step 2 — Connect the repo to Azure Static Web Apps
- In the Azure portal, create a new Static Web App resource.
- Pick the Free hosting plan.
- Under deployment details, sign in with GitHub and select your repository and the
mainbranch. - For the build details, set App location to wherever your export lives — in our case
/website, since the repo root isn't the site itself:
app_location: "/website" # App source code path
api_location: ""
output_location: ""
- Leave Api location empty and set Output location to empty too — there's no build step and no framework, it's plain HTML.
- Click Review + create, then Create. Azure commits a ready-made GitHub Actions workflow to
.github/workflows/in your repo automatically. - Push your
website/folder tomain. That push triggers the first deployment.
Checkpoint: open the Actions tab in your GitHub repo and watch the workflow run. If your export is under 250MB, it succeeds and your site goes live at the default *.azurestaticapps.net address — skip ahead to Step 4. If it fails with a size error instead, that's Step 3.
Step 3 — Fix the 250MB content limit (only if you hit it)
Our first push failed at the deploy step with:
"The size of the app content was too large. The limit for this Static Web App is 262144000 bytes."
262,144,000 bytes is 250MB — the hard content limit on the Free tier. Our export was 266MB. A decade of tech-blog screenshots had quietly added up. If your export already comes in under 250MB, skip straight to Step 4.
3a. Check what's actually taking up the space
$ du -sh website/wp-content/uploads/*
221M wp-content/uploads
39M wp-content/uploads (wp-includes)
In our case: 2,823 PNGs and 735 JPEGs, mostly full-resolution UI screenshots and a handful of phone photos that had never been compressed for the web.
3b. Compress the images
Two upgrade paths existed here — pay for the Standard tier, or shrink the export. Screenshots compress extremely well because they're mostly flat colour and text, so try ImageMagick before reaching for the credit card.
Lossless PNG recompression barely moves the needle, so don't stop here:
magick mogrify -strip -define png:compression-level=9 \
-define png:compression-filter=5 -define png:compression-strategy=1 *.png
# 82M -> 80M (~2% smaller)
Quantizing to a 256-colour palette is the fix that actually matters:
magick mogrify -strip -colors 256 -define png:compression-level=9 *.png
# 82M -> 29M (~65% smaller)
For UI screenshots this is visually lossless — 256 colours is more than enough for a browser chrome and some text. Run the JPEGs through the equivalent treatment:
magick mogrify -strip -quality 82 -sampling-factor 4:2:0 *.jpg
# one folder of phone photos: 21M -> 11M (~48% smaller)
Watch out for: batch-processing thousands of files with
xargssilently skipped a couple of images whose filenames contained a Unicode middle-dot character (DALL·E-2022-...). The fix was switching from onemogrifycall per batch to one call per file (xargs -I{}), which handles unusual filenames correctly at the cost of a bit of speed.
Worth double-checking manually: images with smooth gradients (AI-generated art, for example) rather than flat colour can visibly band under palette reduction. A side-by-side comparison confirmed no visible artefacts even there — screenshots and AI art both held up fine at 256 colours, but look at yours before you trust it.
Checkpoint: re-run du -sh on your export folder and confirm the total is now under 250MB. Ours went from 266MB to 139MB, comfortably under the limit with room for years of future posts. Commit the compressed images and push again — this time the deploy should succeed.
Step 4 — Add a custom domain (optional)
4a. Subdomain (e.g. www.example.com) — the easy path
- In the Azure portal, open your Static Web App and go to Custom domains → Add.
- Enter your subdomain.
- At your DNS provider, add the CNAME record Azure gives you, pointing to the Static Web App's default hostname.
- Wait for validation — this happens automatically once the CNAME resolves.
DNS for gerjon.com runs on Cloudflare, and adding www.gerjon.com this way was exactly this straightforward.
4b. Apex/root domain (e.g. example.com) — the gotcha
Adding the bare gerjon.com apex domain the same way was not straightforward — it failed with:
"CNAME Record is invalid. Please ensure the CNAME record has been created."
Even though Cloudflare's dashboard displays a CNAME on the root record, the DNS protocol doesn't actually allow that — Cloudflare "flattens" it into a plain A record before it ever reaches a resolver. Azure's default validation checks for a literal CNAME, so on an apex domain it never finds one. The fix is to validate via TXT token instead of CNAME delegation:
- Run this from the Azure CLI instead of adding the domain through the CNAME flow:
az staticwebapp hostname set \
--name gerjon-com --resource-group gerjon-website \
--hostname gerjon.com \
--validation-method dns-txt-token
- Azure generates a validation token. Add it as a
_dnsauthTXT record at your DNS provider. - Wait for that TXT record to resolve.
Checkpoint: once the TXT record resolves, the apex domain activates automatically with a managed certificate. The whole rule: subdomains use cname-delegation (the default); apex/root domains need dns-txt-token.
TL;DR
- Export WordPress to static HTML with Simply Static; point the Static Web App's
app_locationat wherever that export lives. - Free tier caps content at 250MB (262,144,000 bytes). Screenshot-heavy sites hit this fast.
- 256-colour PNG quantization and quality-82 JPEGs cut a decade of uploads by roughly 60% with no visible quality loss — try that before upgrading your plan.
- Batch image tools can silently skip files with unusual Unicode filenames; process one file per invocation if anything looks off.
- Subdomains validate custom domains via CNAME; apex/root domains need TXT-token validation, because CNAME flattening (Cloudflare and similar) hides the literal CNAME record Azure is looking for.