azure-static-web-apps
Use when hosting a static front end on Azure Static Web Apps — deploying a local build with the SWA CLI, routing and fallback rules in staticwebapp.config.json, custom domains, managing the resource w
SKILL.mdskills/tech/azure-static-web-apps/SKILL.md
GitHubAzure Static Web Apps
Default: Free plan, build locally, deploy with the SWA CLI from one script on the production branch, routing in staticwebapp.config.json, the resource and custom domain declared in Terraform.
Deploy
npm ci && npm run build && npm test
npx @azure/static-web-apps-cli deploy ./dist --env production \
--deployment-token "$SWA_DEPLOYMENT_TOKEN"
- Provide the deployment token from the environment (or fetch it at deploy time with
az staticwebapp secrets list); never commit it. - Without
--env productionthe CLI deploys to a preview environment. - Run the deploy only from the main branch with a clean working tree, as with any manual production deploy.
Routing
{
"navigationFallback": { "rewrite": "/index.html", "exclude": ["/assets/*", "/*.{png,svg,css,js}"] },
"routes": [{ "route": "/blog/*", "headers": { "cache-control": "public, max-age=3600" } }],
"globalHeaders": { "x-content-type-options": "nosniff" },
"responseOverrides": { "404": { "rewrite": "/404.html" } }
}
- The fallback makes client-side routes work on refresh and deep links; exclude real files so missing assets still 404.
- Static pages (articles, policy pages) are served as files; keep them as real HTML for crawlers.
Infrastructure as code
- Declare
azurerm_static_web_appand the custom domain in Terraform. If the resources already exist,importthem into state and runplanuntil it shows no changes before editing anything. - DNS: a CNAME (subdomain) pointing to the app's default host name; validation can take time — Terraform may need a retry.
Cost hygiene
- The Free plan costs nothing, but companions created during experiments (container registries, container apps, log workspaces with high retention) do. List every resource in the resource group and the cost per resource; delete what the front end does not call.
- Removing a backend the front end never calls can be done without downtime — verify by checking the built JavaScript for its URL before deleting.
Verification loop
- Deploy, then
curl -sI https://<domain>/(200) and a deep link (200 via fallback) and a missing asset (404). - Check headers from
globalHeadersare present. terraform planshows no drift after any portal change.
Pitfalls
- Stuck custom domains can block deleting an app; remove the domain first (via CLI or REST) and then the resource.
- Region and plan limits: the Free plan has size and custom-domain limits; check them before adding content.
- Build in CI vs locally: the default GitHub integration builds in Actions on every push; if you deploy locally, remove that workflow so the two do not race.
- Case sensitivity: routes are case-insensitive but file systems used in builds may not be; keep file names lowercase.