Azure Static Web Apps with a .NET API Backend

Azure Static Web Apps is a hosting service for static frontends — HTML, CSS, JavaScript, Blazor WASM — with an integrated API backend powered by Azure Functions. It handles SSL, global distribution, authentication, and CI/CD out of the box. For many projects, it replaces the need for a separate App Service entirely.

The .NET angle is the API backend. Instead of a Node.js function, you write your API in C# using the Azure Functions isolated worker model, and Static Web Apps routes /api/* requests to it automatically.

Project Structure

A typical repository looks like this:

├── src/
│   └── Client/           # Blazor WASM or static HTML
├── api/
│   └── Api/              # Azure Functions (.NET)
├── staticwebapp.config.json
└── swa-cli.config.json

The API project is a standard Azure Functions isolated worker project:

Program.cs
var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.AddSingleton<IWeatherService, WeatherService>();
    })
    .Build();

host.Run();

Building the API

Functions in the API project are exposed under /api/ automatically:

Example.cs
public class WeatherFunctions
{
    private readonly IWeatherService _weatherService;

    public WeatherFunctions(IWeatherService weatherService)
    {
        _weatherService = weatherService;
    }

    [Function("GetForecast")]
    public async Task<IActionResult> GetForecast(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "weather/{city}")]
        HttpRequest req,
        string city)
    {
        var forecast = await _weatherService.GetForecastAsync(city);

        if (forecast is null)
            return new NotFoundResult();

        return new OkObjectResult(forecast);
    }
}

This function is reachable at https://your-app.azurestaticapps.net/api/weather/london. The /api prefix is added by Static Web Apps — your route starts after it.

Configuration with staticwebapp.config.json

The configuration file controls routing, authentication, and headers:

staticwebapp.config.json
{
  "routes": [
    {
      "route": "/api/*",
      "allowedRoles": ["authenticated"]
    }
  ],
  "navigationFallback": {
    "rewrite": "/index.html",
    "exclude": ["/api/*", "/_framework/*", "/css/*"]
  },
  "globalHeaders": {
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY"
  }
}

The navigationFallback is essential for single-page applications. It rewrites all non-file, non-API requests to index.html, letting client-side routing handle the rest.

Built-in Authentication

Static Web Apps provides authentication with Azure AD, GitHub, and Twitter without any code. The authenticated user's information is available in your API via a custom header:

Example.cs
[Function("GetProfile")]
public IActionResult GetProfile(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "profile")] HttpRequest req)
{
    var clientPrincipal = req.Headers["x-ms-client-principal"].FirstOrDefault();

    if (string.IsNullOrEmpty(clientPrincipal))
        return new UnauthorizedResult();

    var decoded = Convert.FromBase64String(clientPrincipal);
    var principal = JsonSerializer.Deserialize<ClientPrincipal>(decoded);

    return new OkObjectResult(new
    {
        principal?.UserId,
        principal?.UserDetails,
        principal?.UserRoles
    });
}

public record ClientPrincipal(
    string IdentityProvider,
    string UserId,
    string UserDetails,
    IEnumerable<string> UserRoles);

No JWT validation, no middleware configuration. The platform handles authentication and passes the identity through.

Local Development with SWA CLI

The SWA CLI lets you run the full stack locally:

terminal
npm install -g @azure/static-web-apps-cli
swa start src/Client/wwwroot --api-location api/Api

This proxies the static content and API together, simulating the production routing behaviour. You get the same /api/* routing and authentication emulation locally.

Linking an Existing Functions App

The managed Functions backend has limitations — it runs on a consumption plan and supports a limited set of triggers. For more control, link a separately deployed Functions app:

Example.cs
// In your standalone Functions app, CORS is handled by Static Web Apps
// No additional CORS configuration needed when linked

Linking is done in the Azure portal or via the az staticwebapp backends link CLI command. Your API runs on its own plan with full control over scaling and networking.

Deployment

Static Web Apps integrates with GitHub Actions. A workflow is generated automatically when you create the resource:

config.yaml
- uses: Azure/static-web-apps-deploy@v1
  with:
    azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
    repo_token: ${{ secrets.GITHUB_TOKEN }}
    action: "upload"
    app_location: "src/Client/wwwroot"
    api_location: "api/Api"
    output_location: ""

Preview environments are created automatically for pull requests, giving you a unique URL per PR with both the frontend and API deployed together.

When to Use Static Web Apps

It's a great fit for Blazor WASM applications, documentation sites, and single-page apps with a lightweight API. The free tier is generous for personal projects and prototypes. The standard tier adds custom authentication providers and increased API limits.

It's not the right choice if your API needs WebSockets, long-running processes, or complex networking. For those, deploy a full App Service or Container App and point your static frontend at it.