The staticwebapp.config.json file

The staticwebapp.config.json file

The staticwebapp.config.json file

This post is part of the Azure Static Web Apps with Blazor series.

A complete working example is available here: mongeon/code-examples · azure-swa-blazor/02-configuration.

In the previous article, we deployed a Blazor WASM project to Azure Static Web Apps with a minimal staticwebapp.config.json. That file only contained the navigationFallback to make refreshes work. But this file can do much more than that.

Why does Blazor need the navigationFallback?

Blazor WASM is a SPA. Routing happens client-side, in the browser. When you click the Counter link in your app, Blazor intercepts the navigation and displays the right component without making a request to the server.

The problem shows up when you refresh the page or type https://mysite.com/counter directly in the address bar. That’s when the server receives the request, and there is no /counter file on disk. Without a fallback, you get a 404.

The navigationFallback tells the server: if you can’t find the requested file, return index.html and let Blazor handle the routing.

{
  "navigationFallback": {
    "rewrite": "/index.html"
  }
}

However, you don’t want the fallback to apply to everything. CSS files, images and calls to /api/ should not fall back to index.html. That’s where the exclude property comes in:

{
  "navigationFallback": {
    "rewrite": "/index.html",
    "exclude": ["/css/*", "/images/*.{png,jpg,gif,svg}", "/api/*", "/_framework/*"]
  }
}

Excluding /_framework/* is important for Blazor. That folder contains the .NET assemblies and the WebAssembly runtime. If the fallback intercepts a request for a missing file in _framework, you’ll receive HTML instead of a binary and your app will crash with no clear error message.

Controlling routes

The routes section lets you define rules that apply to HTTP requests. Rules are evaluated in array order, and the first match wins.

Redirect an old URL to a new one, with a 301 code so search engines follow along:

{
  "routes": [
    {
      "route": "/old-path",
      "redirect": "/new-path",
      "statusCode": 301
    }
  ]
}

Rewrite a URL without changing the address in the browser. Useful for serving a specific page under a cleaner path:

{
  "routes": [
    {
      "route": "/docs",
      "rewrite": "/documentation/index.html"
    }
  ]
}

Restrict HTTP methods on a route. For example, allowing only GET on the public API:

{
  "routes": [
    {
      "route": "/api/public/*",
      "methods": ["GET"],
      "allowedRoles": ["anonymous"]
    }
  ]
}

Wildcards only work at the end of a path. /admin* will match /admin, /admin/users, /admin/settings, etc. Notice it’s * without a slash before it, which also includes /admin itself.

Adding security headers

The globalHeaders section adds HTTP headers to every response. It’s the perfect place for security headers that often get forgotten:

{
  "globalHeaders": {
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",
    "Referrer-Policy": "strict-origin-when-cross-origin"
  }
}

You can also set headers on specific routes. Route headers override global headers when they conflict. For example, to set aggressive caching on static files:

{
  "routes": [
    {
      "route": "/images/*",
      "headers": {
        "Cache-Control": "public, max-age=604800, immutable"
      }
    }
  ]
}

Protecting pages with roles

SWA has two built-in roles: anonymous (everyone) and authenticated (logged-in users). We’ll cover authentication in detail in a future article, but the basic config to protect a section looks like this:

{
  "routes": [
    {
      "route": "/admin/*",
      "allowedRoles": ["authenticated"]
    }
  ]
}

An unauthenticated user trying to access /admin/ will get a 401. You can combine this with a responseOverrides to redirect them to the login page instead of showing an error:

{
  "responseOverrides": {
    "401": {
      "statusCode": 302,
      "redirect": "/.auth/login/github"
    }
  }
}

You can also define custom roles, like administrator, and assign them through the Azure portal or via invitations.

Customizing error pages

The responseOverrides section lets you replace the default behavior for HTTP error codes. Beyond the 401 we just saw, you can customize the 404:

{
  "responseOverrides": {
    "404": {
      "rewrite": "/404.html"
    }
  }
}

This will serve your own 404 page instead of Azure’s generic one. You could create a Blazor component for this, but make sure the 404.html file is an actual static file in wwwroot, because the fallback does not apply to responseOverrides.

A complete example for Blazor

Putting it all together, a typical staticwebapp.config.json for a Blazor WASM project looks like this:

{
  "navigationFallback": {
    "rewrite": "/index.html",
    "exclude": ["/css/*", "/images/*.{png,jpg,gif,svg}", "/api/*", "/_framework/*"]
  },
  "globalHeaders": {
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",
    "Referrer-Policy": "strict-origin-when-cross-origin"
  },
  "routes": [
    {
      "route": "/admin/*",
      "allowedRoles": ["authenticated"]
    }
  ],
  "responseOverrides": {
    "401": {
      "statusCode": 302,
      "redirect": "/.auth/login/github"
    },
    "404": {
      "rewrite": "/404.html"
    }
  }
}

The file must be in your Blazor project’s wwwroot folder so it ends up in the publish output.

In the next article, we’ll hook up an Azure Functions API behind our Static Web App and see how the /api/ routing works automatically.

Articles in this series

  1. What is an Azure Static Web App?
  2. The staticwebapp.config.json file (This article)
  3. Adding an Azure Functions API
  4. SWA’s built-in authentication
  5. Local development with the swa CLI
  6. PR preview environments

Happy deploying, and don’t forget to test your routes with a refresh before you publish.


This post was written with AI assistance and edited by me.


See also