Deploying an Azure Static Web App with an Azure DevOps Pipeline
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_location | output_location |
|---|---|---|
| Vite (React, Vue, Svelte) | / | dist |
| Create React App | / | build |
| Angular (application builder) | / | dist/<project>/browser |
| Blazor WebAssembly | the client project folder | wwwroot |
| Plain HTML, no build | the site folder | empty |
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
- "deployment_token was not provided": the variable name in
$(...)does not match, or the pipeline cannot read the variable group (check its pipeline permissions). - Build succeeds, deployment fails on missing files:
output_locationis wrong. Add als -Rscript step after the build to see where files really land. - Wrong Node version in Oryx: set
"engines": { "node": "22.x" }inpackage.json, or switch to the build-it-yourself pipeline. - API not deployed:
api_locationmust point to an Azure Functions project; managed functions on Static Web Apps support HTTP triggers only. - 404 on page refresh: add
navigationFallbackas shown above.
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
- Microsoft Learn, Tutorial: Publish Azure Static Web Apps with Azure DevOps.
- Microsoft Learn, AzureStaticWebApp@0 task reference.
- Microsoft Learn, Configure Azure Static Web Apps.
ToolsCheck a package version with pip
Check an installed Python package version with pip show, pip list and pip freeze, or in code with importlib.metadata, and compare versions safely.
ToolsInstall packages in PyCharm
Install Python packages in PyCharm via the Packages tool window, interpreter settings or pip; venv vs conda, requirements.txt and fixing import errors.
ToolsPython version in Jupyter
Check the Python version a Jupyter kernel really runs with sys.version and sys.executable, avoid the !python --version trap and register a venv as a kernel.