Two services, one outcome

If all you need is to put a folder of HTML, CSS and JavaScript on the internet behind a custom domain and a TLS certificate, Azure will do that in at least two ways. You can enable the static website feature on a storage account and point a CDN or Azure Front Door at it, or you can create an Azure Static Web Apps resource and let it own the whole arrangement.

Teams frequently choose between them on the basis of price, which is the least interesting difference. The useful question is how much of the surrounding arrangement — routing, redirects, response headers, deployment, preview environments, certificates — you want to assemble yourself.

What Static Web Apps adds

Static Web Apps is best understood as a bundle. The file serving is the least of it. What you are actually buying is a set of conventions that you would otherwise have to build:

  • A deployment pipeline. Connecting a repository generates a workflow that builds and publishes on every push to the tracked branch.
  • Preview environments. Each pull request gets its own URL, created and destroyed automatically. This changes review behaviour more than any other single feature.
  • A configuration file that owns routing, fallback behaviour, response headers, MIME types and custom error pages, versioned with the site.
  • Managed certificates for custom domains, issued and renewed without intervention.
  • An optional API backed by Azure Functions, served from the same origin, which removes cross-origin configuration for the one or two endpoints a brochure site typically needs.

None of these are things you cannot build on a storage account. All of them are things you would have to remember to build, document and maintain.

Note

Feature availability and limits differ between the free and standard tiers, and both have changed more than once. Check the current Microsoft documentation for your region before committing to a tier — do not rely on any article, including this one, for current limits.

Routing and configuration

The configuration file sits at the root of the deployed output and is called staticwebapp.config.json. It is worth understanding early, because it is where most of the behaviour that people expect a web server to provide actually lives.

staticwebapp.config.json

{
  "navigationFallback": {
    "rewrite": "/404.html",
    "exclude": ["/assets/*", "/css/*", "/js/*", "/config/*", "/content/*"]
  },
  "responseOverrides": {
    "404": { "rewrite": "/404.html", "statusCode": 404 }
  },
  "globalHeaders": {
    "X-Content-Type-Options": "nosniff",
    "Referrer-Policy": "strict-origin-when-cross-origin",
    "Content-Security-Policy": "default-src 'self'; img-src 'self' data:; object-src 'none'"
  },
  "mimeTypes": {
    ".json": "application/json",
    ".svg": "image/svg+xml"
  }
}

Navigation fallback is not a 404 handler

This is the single most common source of confusion. navigationFallback exists for single-page applications: it serves a given file for any request that does not match a physical file, with a 200 status. If you point it at /index.html on a multi-page site, every mistyped URL returns your homepage with a success status, and search engines will index the resulting mess.

For a multi-page static site, either exclude your asset paths and fall back to a real 404 document, or set responseOverrides so that the status code is honest. Both are shown above.

Headers are global, and that is a limitation

globalHeaders applies to every response. There is a per-route headers block, but route matching is simpler than a full web server's, so a policy that varies substantially by path is awkward to express. If you need fine-grained header control, that is an argument for putting Front Door in front.

Custom domains and certificates

Static Web Apps will issue and renew a certificate for a custom domain once the domain validates. The apex domain is the part that catches people out: an apex record cannot be a CNAME, so validation uses either an ALIAS-style record from a provider that supports one, or Azure DNS with an alias record.

Plan the DNS before the launch date rather than during it. Propagation and validation are usually quick, but they are not instant, and a launch that waits on DNS is a launch that has already gone wrong.

When a storage account is enough

A storage account with static website hosting enabled, fronted by a CDN profile, is still a completely reasonable choice. It suits you if:

  • you already run a mature deployment pipeline and do not want a second one;
  • you need header or caching behaviour that is easier to express at the CDN;
  • the site is one origin among several behind a single Front Door instance;
  • your organisation's landing zone standards constrain which resources you may create, which is common in regulated environments.

What you give up is preview environments, managed certificates on the origin, and the configuration file. You will end up re-implementing the parts you need, which is fine if you were going to do that anyway.

Choosing between them

Comparison of Azure Static Web Apps and storage account hosting
Consideration Static Web Apps Storage account + CDN
Deployment Generated pipeline, opinionated Whatever you already have
Preview environments Per pull request, automatic Build it yourself
Routing and headers Config file in the repository CDN or Front Door rules engine
Certificates Managed Managed at the CDN layer
Serverless API Integrated, same origin Separate Function App and CORS
Governance fit One more resource type to approve Usually already approved

Our default for a corporate website is Static Web Apps, because preview environments genuinely improve review quality and the configuration file keeps routing decisions in version control where they can be reviewed. Our default for a site that must sit behind an existing Front Door instance alongside other origins is the storage account, because consistency at the edge is worth more than convenience at the origin.

Watch out

Whichever you choose, do not put configuration containing secrets into the deployed output. Everything published to a static host is public, including any JSON file you only meant your own JavaScript to read. Configuration in the browser is configuration in the newspaper.

Where to start

Deploy something trivial first — a single page and a stylesheet — and take it all the way through to a custom domain and a working 404. The interesting problems are all in that path, and they are considerably cheaper to meet with a two-file site than with a completed one.