Fix Azure Static Web Apps: Deployment & Config Issues
Why This Is Happening
I've spent years watching developers , sharp, experienced people , hit a wall with Azure Static Web Apps and assume they did something wrong. They didn't. Azure Static Web Apps has a genuinely clever architecture, but it also has a handful of sharp edges that bite almost everyone the first time (and often the second). The error messages it throws are frequently vague, the GitHub Actions workflow output can be cryptic, and the gap between "it works locally" and "it works on Azure" is wider than you'd expect.
Here's the core thing to understand: Azure Static Web Apps separates your static front-end assets, your HTML, CSS, JavaScript, images, from your API layer entirely. Your static files get distributed globally across Azure's CDN points of presence, and your API endpoints run on a serverless Azure Functions backend. That's a fundamentally different model from deploying to a traditional VM or App Service, and a lot of common fixes people know from those environments just don't apply here.
The most frequent issues I see fall into a few buckets. First, build failures in the GitHub Actions or Azure DevOps pipeline, usually because the app_location, api_location, or output_location values in your workflow YAML don't match your actual project structure. Second, API calls returning 404 or CORS errors even though the endpoint exists, almost always a routing misconfiguration or a misunderstanding of how the managed Functions integration works. Third, custom domain and SSL issues where the DNS hasn't fully propagated or the certificate renewal hit a snag. Fourth, confusion around the Free vs. Standard plan limits, the 250 MB app size cap on the Free plan catches people off guard when their production build grows unexpectedly.
Who sees these problems most? Frontend developers deploying a React app to Azure Static Web Apps for the first time, teams migrating from Netlify or Vercel who expect identical behavior, and enterprise shops trying to bolt on private endpoints or custom authentication providers, both of which require the Standard plan, not Free.
What makes this especially frustrating is that Azure's error messages don't tell you which of these buckets you're in. A build failure log might say "Error: ENOENT" without naming the directory it couldn't find. A 404 on your API route doesn't distinguish between a missing function, a misconfigured route, and a plan-tier limitation. I know that ambiguity is maddening when you have a deadline.
The good news: every one of these issues has a concrete fix. Browse all Microsoft fix guides →
The Quick Fix, Try This First
If your Azure Static Web Apps deployment just failed and you need it running now, start here. Seventy percent of the deployment issues I've seen come from one root cause: the workflow YAML file generated by Azure doesn't match your actual project's output directory.
Open your repository and navigate to .github/workflows/. You'll find a YAML file that looks something like azure-static-web-apps-[random-name].yml. Open it and look for this block:
- name: Build And Deploy
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: "/"
api_location: "api"
output_location: "dist"
The three values you need to verify against your actual project:
- app_location, the folder containing your source code. For a Vite project scaffolded with
npm create vite@latest, this is typically/. For a monorepo, it might be/frontendor/client. - api_location, the folder containing your Azure Functions API code. If you have no API, set this to an empty string:
"". Leaving it asapiwhen no such folder exists will cause your build to fail every time. - output_location, the build output folder relative to
app_location. Vite outputs todist. Create React App outputs tobuild. Angular outputs todist/[project-name]. Get this wrong and your deployment will succeed but serve a blank page.
Fix those three values, commit the change, and push. Watch the Actions tab in GitHub, a fresh run will trigger automatically. If it goes green and your site loads, you're done.
npm run build and then check exactly which folder gets created. Whatever folder name appears, dist, build, out, public, that's your output_location. Azure will never guess it for you, and the default value in the generated YAML is frequently wrong for non-standard frameworks.
Before debugging your code, confirm the Azure side is healthy. I've seen developers spend hours on their YAML when the real problem was a deleted or expired deployment token.
Sign into the Azure Portal. In the search bar at the top, type Static Web Apps and select it from the results. You should see your app listed. Click it to open the resource overview.
On the left sidebar, under Settings, click Deployment tokens. You'll see your current token. Here's the thing: if someone on your team regenerated this token and didn't update the GitHub secret, every deployment will fail with an authentication error. Copy the token value.
Now go to your GitHub repository. Click Settings → Secrets and variables → Actions. Find the secret named AZURE_STATIC_WEB_APPS_API_TOKEN (the exact name referenced in your workflow YAML). Click Update and paste the token from the Azure Portal.
Back in the Azure Portal, also check your app's Overview tab. Look at the URL field, it should show a live URL ending in .azurestaticapps.net. If the resource shows as unhealthy or the URL field is blank, the resource itself may need to be recreated. Also check the Source field, it should point to your correct GitHub repo and branch. If it's pointing to the wrong branch, that's why your pushes to main aren't triggering deployments.
If everything looks correct here, trigger a manual re-run of the last failed workflow in GitHub Actions by clicking Re-run all jobs. Watch the output in real time. If it passes now, a stale token was your issue.
Your workflow YAML is the single most important configuration file for Azure Static Web Apps deployments, and it's also the one Azure generates with the most assumptions baked in. Let's make sure every field is right.
Open .github/workflows/azure-static-web-apps-[name].yml in your editor. The full build step should look something like this for a standard Vite + React project:
- name: Build And Deploy
id: builddeploy
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: "/"
api_location: ""
output_location: "dist"
app_build_command: "npm run build"
Key things to check beyond the three location fields:
- Node.js version: Azure's build environment defaults to a Node version that may not match your local setup. Add an explicit version step before the build:
uses: actions/setup-node@v3withnode-version: '20'. The official docs specify Node 20.0 or later as the requirement for the Azure CLI, and your app should match. - app_build_command: If your project uses a non-standard build script, say
npm run build:prod, specify it explicitly here. Omitting it causes the action to fall back to a generic build attempt that may not produce the right output. - skip_app_build: If you're pre-building in an earlier CI step and just want to deploy the artifact, set this to
trueand pointoutput_locationat the folder containing the already-built files.
After saving your changes, commit and push. You should see the Actions run turn green within a few minutes. If it fails again, click into the failed step, the raw log output will show the exact npm or build error that needs addressing next.
This is the most common issue people hit after a successful deployment: the site loads on the homepage, but any direct URL, like navigating to https://yourapp.azurestaticapps.net/dashboard, returns a 404. Azure Static Web Apps doesn't automatically know you're running a single-page app that handles routing client-side.
The fix is a staticwebapp.config.json file placed at the root of your output folder (or in your app source root, depending on your setup). Here's the essential routing rule for SPAs:
{
"navigationFallback": {
"rewrite": "/index.html",
"exclude": ["/images/*.{png,jpg,gif}", "/css/*", "/js/*"]
}
}
This tells Azure: if a request comes in for a path that doesn't correspond to a real file on disk, serve index.html instead and let the front-end router handle it. The exclude array prevents static assets from accidentally getting rewritten to index.html, make sure it covers all your asset file types.
You can also add response headers, custom error pages, and redirect rules in the same file. For example, to set a global security header:
{
"globalHeaders": {
"X-Frame-Options": "SAMEORIGIN",
"X-Content-Type-Options": "nosniff"
},
"navigationFallback": {
"rewrite": "/index.html"
}
}
Commit this file, push, and wait for the deployment to complete. Then test a direct URL, you should land on your app instead of a 404 page. If you're still seeing 404s, double-check the file is being included in your build output folder, not left behind in the source directory.
Your front end loaded fine, but your API calls are returning 404, 500, or hanging entirely. API issues with Azure Static Web Apps tend to fall into one of three scenarios, and the fix is different for each.
Scenario A: Managed Functions not deploying. If you're using the built-in managed Azure Functions, your api_location in the workflow YAML must point to a folder containing valid Azure Functions code. The folder needs a host.json at its root and at least one function subfolder. If this structure is missing or broken, the managed API simply won't deploy, and you'll get 404s on every /api/* route with no obvious error message.
Scenario B: Bringing your own Functions app. On the Standard plan, you can link an existing Azure Functions app instead of using the managed Functions. In the Azure Portal, go to your Static Web Apps resource → Settings → APIs. Click Link and select your existing Functions app. Be aware: linking a Functions app is only available on the Standard plan. If you're on Free and trying to link an external app, it won't work, you need to upgrade first.
Scenario C: Route prefix mismatch. Azure Static Web Apps proxies all API calls through /api by default. If your front-end code is calling /functions/myEndpoint or /v1/myEndpoint, those calls will never reach your Functions. Make sure your front-end API calls use the /api/[function-name] pattern. You can verify the correct path by opening your Static Web App in the Azure Portal and looking under Functions in the left sidebar, each function listed shows its URL pattern.
# Correct API call format from your front end
fetch('/api/getUserData')
.then(res => res.json())
.then(data => console.log(data));
One more thing: Azure Static Web Apps handles CORS for API calls automatically when going through the built-in reverse proxy. If you're getting CORS errors, it likely means your front end is calling the API directly by full URL rather than through the relative /api path. Switch to relative paths and the CORS errors should disappear.
Custom domains are one of the most polished features of Azure Static Web Apps, free SSL certificates, automatically renewed, no configuration required on your end. But there are a few specific ways the setup can go sideways, and the portal's error messages aren't always clear about which one you're hitting.
First, understand the plan limits. On the Free plan, you get 2 custom domains per app. On the Standard plan, you get 5. If you're trying to add a third domain on the Free plan, the portal will reject it. The fix is to either remove an existing domain or upgrade to Standard.
To add a custom domain, go to your Static Web App in the Azure Portal → Settings → Custom domains → Add. Enter your domain name and follow the DNS validation steps Azure provides. You'll need to add either a CNAME record (for subdomains like www.yourdomain.com) or a TXT record plus an ALIAS/ANAME record (for apex domains like yourdomain.com).
The most common sticking point is DNS propagation. Azure will show your domain as "Validating" for up to 48 hours while it waits for DNS changes to propagate globally. Don't panic if it stays in this state for a few hours. You can check propagation status using a tool like nslookup:
nslookup www.yourdomain.com
# Should return your Static Web Apps hostname, e.g.:
# [your-app-name].azurestaticapps.net
Once DNS resolves correctly, Azure automatically provisions the SSL certificate via its certificate authority. This usually completes within 10-15 minutes of successful validation. If the certificate shows as "Failed" after DNS is confirmed correct, try removing and re-adding the custom domain, this triggers a fresh certificate request. If it fails a second time, check that your DNS provider doesn't have CAA records that block Azure's certificate authority from issuing certificates for your domain.
Advanced Troubleshooting
You've worked through the standard steps and things still aren't right. Let's go deeper.
Diagnosing Build Failures with Detailed Logs
The default GitHub Actions log output truncates a lot. When a build fails, click the failing step in the Actions run view to expand it. Look for lines prefixed with [ORYX], that's the build system Azure uses under the hood. ORYX errors often reveal the real issue: a missing lock file, an incompatible package manager version, or a build script that exited with a non-zero code. If you see Error: ORYX build failed followed by a cryptic path error, 90% of the time ORYX can't find your package.json because app_location is set incorrectly.
Staging Environments and Pull Request Previews
Azure Static Web Apps generates a separate staging environment for every pull request automatically, this is one of the genuinely excellent features of the platform. On the Free plan you get 3 staging environments per app; Standard gives you 10. If your PR preview URLs are returning 404 or not generating at all, check that the workflow YAML has the pull_request trigger configured:
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
The closed event type is important, it's what triggers cleanup of the staging environment when the PR is merged or closed. Without it, staging environments pile up and you'll hit the plan limit.
Authentication Configuration Issues
Azure Static Web Apps includes built-in authentication with Microsoft Entra ID and GitHub as preconfigured providers. On the Free plan, you're limited to these preconfigured providers. If you need a custom identity provider, say, Auth0, Okta, or your own Azure AD B2C tenant, you must be on the Standard plan, which supports custom provider registrations.
Custom authentication is configured in staticwebapp.config.json under the auth key:
{
"auth": {
"identityProviders": {
"customOpenIdConnectProviders": {
"myProvider": {
"registration": {
"clientIdSettingName": "MY_CLIENT_ID",
"clientCredential": {
"clientSecretSettingName": "MY_CLIENT_SECRET"
},
"openIdConnectConfiguration": {
"wellKnownOpenIdConfiguration": "https://yourprovider.com/.well-known/openid-configuration"
}
}
}
}
}
}
}
The client ID and secret values are referenced by name, not value, you store the actual secrets in your Static Web App's Application settings in the Azure Portal. If authentication isn't working, first verify those application settings are present and correctly named.
Private Endpoints and Enterprise Network Scenarios
Private endpoints for Azure Static Web Apps are a Standard plan feature only. If your organization requires that the app not be publicly accessible on the internet, common in financial or healthcare enterprise scenarios, you'll need Standard plus an Azure Virtual Network. This is a significant infrastructure change and involves configuring Azure Private DNS zones alongside the private endpoint. If you're in a domain-joined or fully managed enterprise environment, your network team will need to be involved.
Checking Quota and Size Limits
The Free plan caps your app at 250 MB total file size. The Standard plan raises this to 500 MB. If your production build exceeds the limit, deployments will fail with a quota error. Run du -sh dist/ (or dir /s dist on Windows) after building locally to check your output size. Common culprits are large image assets, unminified JavaScript bundles, or accidentally including node_modules in your build output. If you genuinely need more than 500 MB, you'll need to offload large assets to Azure Blob Storage or a CDN and reference them by URL.