Tools

Deploying an Azure Static Web App with an Azure DevOps Pipeline

Tools · Published 2026-10-27

To deploy to Azure Static Web Apps from Azure DevOps, create the static web app, store its deployment token as a secret pipeline variable, and add the AzureStaticWebApp@0 task to an azure-pipelines.yml file. The task can build your app with Microsoft's Oryx build system or upload a folder you built yourself, and it deploys an optional Azure Functions API in the same step.

Azure Static Web Apps hosts static front ends (plain HTML, React, Angular, Vue, Astro, Blazor WebAssembly and similar) on a global CDN, with optional serverless APIs, authentication and staging environments. GitHub Actions is its default CI system, but Azure DevOps is fully supported.

1. Create the static web app

In the Azure portal, create a Static Web App. For the deployment source you have two options. Selecting Azure DevOps lets the portal connect to your organization, generate a pipeline and commit it to the repository (the branch must not be protected by policies that block the push). Selecting Other creates the app without touching your repository, so you write and own the pipeline yourself, which is the approach followed here. Pick the Free plan for personal projects or Standard if you need custom authentication providers, a service-level agreement or larger limits.

Once the resource exists, open Manage deployment token on its overview page and copy the token. Anyone with this token can deploy to your site, so treat it as a secret.

The same can be done with the Azure CLI:

az staticwebapp create --name my-site --resource-group my-rg --location westeurope --sku Free
az staticwebapp secrets list --name my-site --resource-group my-rg --query "properties.apiKey" -o tsv

2. Store the token in Azure DevOps

In your pipeline (or a variable group under Pipelines › Library), add a variable named, for example, deployment_token, paste the token and tick Keep this value secret. Secret variables are masked in logs and are not passed to pipelines triggered from forks. A variable group linked to Azure Key Vault is the better option when several pipelines share the token.

3. The pipeline: let Oryx build the app

For a typical JavaScript app with an npm run build script, the task can do everything. Add azure-pipelines.yml at the repository root:

trigger:
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

steps:
  - checkout: self
    submodules: true

  - task: AzureStaticWebApp@0
    displayName: Build and deploy
    inputs:
      app_location: /            # folder with package.json
      api_location: api          # Azure Functions folder; leave empty if none
      output_location: dist      # build output, relative to app_location
      azure_static_web_apps_api_token: $(deployment_token)

The three location inputs cause most failed deployments, so check them against your framework:

Framework (default build)app_locationoutput_location
Vite (React, Vue, Svelte)/dist
Create React App/build
Angular (application builder)/dist/<project>/browser
Blazor WebAssemblythe client project folderwwwroot
Plain HTML, no buildthe site folderempty

Paths are relative to the repository root (app_location, api_location) or to app_location (output_location). A wrong output folder usually ends with a successful build followed by a "no index.html found" error.

4. Alternative: build in the pipeline, deploy the folder

Oryx guesses the Node version and runs a fixed command. When you need your own Node version, a private registry, tests before deployment or a monorepo layout, build with ordinary steps and tell the task to skip its build. With skip_app_build: true, app_location becomes the folder of built files:

steps:
  - checkout: self

  - task: UseNode@1
    inputs:
      version: 22.x

  - script: |
      npm ci
      npm test
      npm run build
    displayName: Install, test and build

  - task: AzureStaticWebApp@0
    displayName: Deploy
    inputs:
      app_location: dist
      output_location: ''
      skip_app_build: true
      skip_api_build: true
      azure_static_web_apps_api_token: $(deployment_token)

This version also makes failures easier to diagnose: an error in npm run build shows up in its own step instead of inside the deployment log. To be sure the pipeline uses the same tool versions as your machine, the usual habit of checking versions explicitly applies, as described for Python in checking a package version with pip.

5. Routing and headers: staticwebapp.config.json

Single-page applications need every unknown path to serve index.html, otherwise refreshing /orders/42 returns a 404. Put a staticwebapp.config.json file in the folder that gets deployed (or point config_file_location at it):

{
  "navigationFallback": {
    "rewrite": "/index.html",
    "exclude": ["/assets/*", "/*.{css,js,png,svg,ico,webp}"]
  },
  "globalHeaders": {
    "X-Content-Type-Options": "nosniff",
    "Referrer-Policy": "strict-origin-when-cross-origin"
  },
  "responseOverrides": {
    "404": { "rewrite": "/404.html" }
  }
}

The same file handles redirects, role-based route protection and MIME types. The older routes.json format is deprecated.

6. Preview environments for pull requests

Azure Static Web Apps can host named environments next to production. The task's deployment_environment input deploys to a named environment, and production_branch tells it which branch counts as production, so builds from other branches go to preview environments automatically. A common pattern is a second pipeline triggered by pull requests that deploys to an environment named after the branch, so reviewers get a URL to test before merging. Remember to delete stale environments: the Free plan allows only a few.

Troubleshooting

Summary

Create the Static Web App, keep its deployment token in a secret variable, and use AzureStaticWebApp@0 either with Oryx (set app_location, api_location, output_location) or with your own build steps and skip_app_build: true. Add staticwebapp.config.json for SPA routing and headers, and use named environments to preview pull requests.

Sources

Read next

More in Tools