Azure Static Web Apps with GitHub Actions: Pull Request Preview Environments, automatic teardown and stable production URLs

Azure Static Web Apps has one feature that is easy to underestimate until a team starts using it seriously: pull requests can behave like first-class deployment units.

Instead of treating a pull request as nothing more than a diff in GitHub, Azure Static Web Apps can build and publish that pull request to its own pre-production environment with its own URL. That makes code review materially better, because reviewers do not have to imagine what a change does. They can open it.

For advanced teams, the more important point is not convenience. It is lifecycle control. A pull request gets a preview deployment with a dedicated URL. New commits update that same preview environment. When the pull request is closed, Azure Static Web Apps removes the preview deployment again. Meanwhile, the tracked production branch, typically main, continues to deploy to the stable production URL.

This post explains that lifecycle in detail, shows how the GitHub Actions workflow is wired, and highlights the practical implications for teams that want predictable preview environments without having to build their own deployment orchestration.

Introduction

There are three distinct deployment behaviors that matter when you use Azure Static Web Apps with GitHub Actions:

  1. A push to the tracked production branch deploys your site to the fixed production URL.
  2. A pull request targeting that tracked branch deploys to a dedicated preview URL.
  3. Closing the pull request removes that preview environment again.

This is not just a cosmetic feature. It gives you an operational model that is surprisingly clean:

  • production stays stable and addressable under one canonical URL
  • every pull request can be reviewed as a running site
  • preview environments do not pile up forever because the platform cleans them up automatically when the pull request lifecycle ends

In practice, that means you can let product owners, testers and other engineers validate a change in a real deployed environment before the code ever reaches production.

The Core Mental Model

Azure Static Web Apps separates production from review environments by branch and pull request lifecycle.

The simplest mental model looks like this:

1main branch               -> production deployment -> fixed production URL
2pull request to main      -> preview deployment    -> PR-specific preview URL
3close pull request        -> preview teardown      -> preview URL removed

That is the operational baseline.

If you only remember one thing, remember this: production deployments are branch-based, preview deployments are pull-request-based.

This distinction matters because it explains why Azure Static Web Apps can keep production stable while still creating isolated review environments for feature work.

What Happens When a Pull Request Is Created

When a pull request is opened against the branch watched by the workflow, the GitHub Actions workflow for Azure Static Web Apps starts a build and deployment for a pre-production environment.

That environment is not just a partial artifact or a temporary build log. It is a deployed version of the application that can be opened in a browser and reviewed as a running site.

The important properties are these:

  • each pull request gets its own preview environment
  • the pull request environment has its own URL
  • the URL stays stable for the lifetime of that pull request
  • additional commits to the same pull request update the same preview deployment instead of creating a new one every time

According to the Azure documentation, the preview URL follows a pattern like this:

1https://<SUBDOMAIN-PULL_REQUEST_ID>.<AZURE_REGION>.azurestaticapps.net

That stable-per-PR behavior is operationally useful. If QA, product or external reviewers already bookmarked or shared the preview link, the link does not change just because you pushed another fixup commit.

What Happens When the Pull Request Is Closed

This is one of the cleanest parts of the model.

When the pull request is closed, Azure Static Web Apps tears down the corresponding preview environment. That means the pre-production deployment tied to that PR is deleted automatically.

This is important for two reasons.

First, it keeps the environment inventory understandable. You do not have to build your own janitor process to detect abandoned review apps.

Second, it makes the deployment model match the collaboration model. A pull request is temporary by nature, so its environment should be temporary as well.

The key nuance is that GitHub pull request state, not just branch existence, controls the lifecycle here. A long-lived feature branch by itself is not the primary unit. The pull request is.

What Happens on main

Production behaves differently.

When changes land in the tracked branch, usually main, the workflow deploys to the fixed production environment. That environment has the canonical URL of your Static Web App.

So the production path is not:

1every branch gets a permanent URL

It is this:

1tracked production branch -> stable production site

This distinction is worth stating explicitly because teams sometimes assume preview environments are just “more production slots”. They are not. They are pull-request-scoped review environments.

The Generated GitHub Actions Workflow

When you create an Azure Static Web App and connect it to GitHub, Azure generates a workflow file for you. In many repositories, it looks roughly like this:

 1name: Azure Static Web Apps CI/CD
 2
 3on:
 4  push:
 5    branches:
 6      - main
 7  pull_request:
 8    types:
 9      - opened
10      - synchronize
11      - reopened
12      - closed
13    branches:
14      - main
15
16jobs:
17  build_and_deploy_job:
18    if: github.event_name == 'push' || github.event.action != 'closed'
19    runs-on: ubuntu-latest
20    name: Build and Deploy Job
21    steps:
22      - uses: actions/checkout@v4
23        with:
24          submodules: true
25          lfs: false
26
27      - name: Build And Deploy
28        id: builddeploy
29        uses: Azure/static-web-apps-deploy@v1
30        with:
31          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
32          repo_token: ${{ secrets.GITHUB_TOKEN }}
33          action: upload
34          app_location: "/"
35          output_location: "dist"
36
37  close_pull_request_job:
38    if: github.event_name == 'pull_request' && github.event.action == 'closed'
39    runs-on: ubuntu-latest
40    name: Close Pull Request Job
41    steps:
42      - name: Close Pull Request
43        uses: Azure/static-web-apps-deploy@v1
44        with:
45          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
46          action: close

The exact shape can vary depending on framework, paths and whether you use an API backend, but the lifecycle logic remains the same.

For advanced users, there are two lines in this workflow that matter most.

The first one is the pull_request trigger:

1pull_request:
2  types:
3    - opened
4    - synchronize
5    - reopened
6    - closed
7  branches:
8    - main

This defines that pull requests targeting main are part of the deployment lifecycle.

The second one is the split between action: upload and action: close.

1action: upload

This publishes production or preview content depending on the event context.

1action: close

This tells the action to remove the preview environment when the pull request is closed.

That close step is the explicit teardown hook in the generated workflow. The broader lifecycle is still tied to the pull request itself: Azure documents that pre-production environments are automatically deleted once the pull request is closed. In other words, the generated close job makes the teardown explicit in workflow code instead of defining a separate lifecycle model.

The Deployment Token Does Not Have To Be Hardcoded

One important operational detail is easy to miss: AZURE_STATIC_WEB_APPS_API_TOKEN does not have to be pasted into a workflow as a literal value.

When Azure creates the GitHub integration for you, the token is usually stored as a repository secret automatically. If you provision or reference the Static Web App through Bicep, you can also retrieve the deployment token from the resource and expose it as a secure output during bootstrap.

For example:

 1param staticWebAppName string
 2
 3resource staticWebApp 'Microsoft.Web/staticSites@2024-04-01' existing = {
 4	name: staticWebAppName
 5}
 6
 7@secure()
 8output staticWebAppsDeploymentToken string = listSecrets(
 9	staticWebApp.id,
10	'2024-04-01'
11).properties.apiKey

That pattern is useful when infrastructure and CI bootstrapping are automated together. The important constraint is security hygiene: treat the deployment token as a secret, use @secure() if you surface it from Bicep, and prefer handing it off into a secret store or repository secret rather than keeping it as a plain value in source control.

Why the Preview URL Is So Useful

Teams often focus on the fact that a preview URL exists. The more interesting detail is that the URL is attached to the pull request identity.

That gives you a few practical advantages.

1. Review becomes environment-based instead of screenshot-based

A reviewer can open the deployed application instead of mentally simulating the change from a code diff.

That matters most for:

  • layout changes
  • navigation changes
  • routing behavior
  • API integration behavior
  • authentication boundaries
  • feature flags and environment-specific wiring

If a reviewer reports a bug and you push a fix, the same preview environment is updated. The URL does not need to be redistributed.

3. The preview URL can be consumed by automation

The Azure Static Web Apps GitHub Action exposes the deployed site URL through static_web_app_url. That is particularly useful for advanced CI setups, because you can chain browser tests or smoke tests against the actual preview deployment.

For example:

 1- name: Build And Deploy
 2  id: builddeploy
 3  uses: Azure/static-web-apps-deploy@v1
 4  with:
 5    azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
 6    repo_token: ${{ secrets.GITHUB_TOKEN }}
 7    action: upload
 8    app_location: "/"
 9    output_location: "dist"
10
11- name: Run Playwright tests against preview
12  if: github.event_name == 'pull_request' && github.event.action != 'closed'
13  run: npx playwright test
14  env:
15    PLAYWRIGHT_BASE_URL: ${{ steps.builddeploy.outputs.static_web_app_url }}

This is where preview environments stop being just a developer convenience and become part of a real deployment-quality pipeline.

A Minimal but Useful Advanced Workflow Shape

If you want to reason about the full behavior, this is the lifecycle in sequence.

Step 1: A developer opens a pull request against main

GitHub fires the pull_request event with type opened.

The Azure Static Web Apps action builds the application and deploys it as a preview environment.

Result:

  • the PR receives its own preview URL
  • reviewers can test the feature without touching production

Step 2: The developer pushes more commits to the same PR branch

GitHub fires the pull_request event with type synchronize.

The same preview environment is updated.

Result:

  • the preview URL stays the same
  • the deployed content changes to reflect the new commits

Step 3: The PR is merged

Two things happen conceptually.

First, the PR lifecycle ends, which means the preview environment is no longer needed.

Second, the merge updates main, which triggers the push-based production deployment.

Result:

  • the preview environment is eventually closed
  • the production site is deployed on the fixed production URL

Step 4: The PR is closed without merge

GitHub fires the pull_request event with type closed.

The workflow runs the action: close step.

Result:

  • the preview environment is removed
  • production remains unchanged

That last case is especially important. A rejected or abandoned feature does not leave behind a stale deployment.

Production and Preview Are Not the Same Trust Boundary

Advanced teams should treat preview and production environments differently.

Azure documents an important limitation: preview environments can be reachable by URL, even when the GitHub repository is private. That means a preview deployment is convenient, but it should not be treated as a safe place for sensitive data or internal-only content by default.

That operationally implies the following:

  • do not expose secrets in static frontend code, preview or production
  • do not assume repository privacy automatically makes preview URLs private
  • use backend authorization properly instead of assuming obscurity through an unshared link
  • avoid seeding preview environments with sensitive test data unless the data model is designed for that

This is not unique to Static Web Apps, but preview environments make the risk more visible because they are so easy to create and share.

Common Misunderstandings

There are a few misunderstandings that appear regularly in teams adopting Azure Static Web Apps.

“Each branch gets its own permanent deployment”

Not in the default GitHub pull request model.

The default behavior is centered around the tracked production branch and pull requests against it. Production is branch-based. Preview is pull-request-based.

“Closing a pull request only closes the GitHub review”

No. In the Static Web Apps workflow, closing the pull request is also an infrastructure lifecycle signal. It tells the deployment action to remove the preview environment.

“Merging a pull request promotes the preview environment to production”

Not literally.

What actually happens is that the merge updates the tracked branch. That branch change then triggers a fresh production deployment on the stable production URL.

This distinction matters because it explains why production remains a separately built deployment path.

A More Explicit Workflow Example

If you prefer a slightly more annotated version, the following workflow shape makes the intent very obvious:

 1name: swa-cicd
 2
 3on:
 4  push:
 5    branches:
 6      - main
 7  pull_request:
 8    branches:
 9      - main
10    types:
11      - opened
12      - synchronize
13      - reopened
14      - closed
15
16jobs:
17  deploy:
18    if: github.event_name == 'push' || github.event.action != 'closed'
19    runs-on: ubuntu-latest
20    permissions:
21      contents: read
22      pull-requests: write
23    steps:
24      - name: Checkout
25        uses: actions/checkout@v4
26
27      - name: Deploy production or preview environment
28        id: swa
29        uses: Azure/static-web-apps-deploy@v1
30        with:
31          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
32          repo_token: ${{ secrets.GITHUB_TOKEN }}
33          action: upload
34          app_location: apps/frontend
35          output_location: dist
36
37      - name: Smoke test deployed site
38        if: github.event.action != 'closed'
39        run: curl --fail --silent --show-error "${SITE_URL}" > /dev/null
40        env:
41          SITE_URL: ${{ steps.swa.outputs.static_web_app_url }}
42
43  teardown_preview:
44    if: github.event_name == 'pull_request' && github.event.action == 'closed'
45    runs-on: ubuntu-latest
46    steps:
47      - name: Remove preview environment
48        uses: Azure/static-web-apps-deploy@v1
49        with:
50          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
51          action: close

This example makes the separation of responsibilities explicit:

  • push to main deploys production
  • open or updated pull requests deploy preview environments
  • closed pull requests trigger teardown

That is the behavior most teams actually want.

Operational Benefits for Advanced Teams

For small demos, preview environments are mostly a convenience feature.

For advanced teams, they solve several real workflow problems.

Better release confidence

Reviewers validate deployed behavior before merge, not just source code.

Cleaner collaboration with non-developers

Product, QA and stakeholders can review a real link without needing local setup.

Less custom CI/CD plumbing

You do not have to invent your own preview-app naming scheme, deployment routing or teardown automation.

Easier end-to-end validation

Because the workflow exposes the deployed preview URL, browser automation and smoke tests can run against the actual review environment.

Stable production semantics

Production is still tied to the tracked branch and its canonical URL. Preview activity does not replace or destabilize that model.

Limits and Practical Constraints

Azure Static Web Apps preview environments are very useful, but they are not infinite and they are not identical to production.

The official documentation points out several practical constraints:

  • the number of simultaneous pre-production environments depends on the hosting plan
  • preview environments are not geo-distributed in the same way production may be perceived operationally
  • pull-request-based preview support is available for GitHub Actions, not automatically for every other CI system

So the right way to think about this feature is not “free unlimited ephemeral environments forever”.

The right way is: “a managed and opinionated review-environment lifecycle built directly into the Static Web Apps GitHub integration”.

That is still a strong capability.

Conclusion

Azure Static Web Apps gives pull requests a real deployment lifecycle, a bit like Cloudflare and Vercel.

That lifecycle is straightforward and powerful:

  • opening a pull request creates a dedicated preview deployment with its own URL
  • pushing more commits updates that same preview environment
  • closing the pull request removes the preview environment again
  • pushing to main deploys the live site to the fixed production URL

For beginners, this feels like a very convenient review feature.

For advanced teams, it is more than that: it is a clean separation between temporary review infrastructure and stable production infrastructure, implemented with very little custom CI/CD code.

That is why Azure Static Web Apps works so well for frontend-heavy delivery workflows. The platform understands that a pull request is not just source control metadata. In a modern web delivery model, it is often the correct unit for a temporary deployment.


Let's Work Together

Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.

New Platforms
Modernization
Training & Consulting

Comments

Twitter Facebook LinkedIn WhatsApp