esc
← Back to blog
AzureStatic Web AppsCI/CDGitHubOpen Source

How I hosted my website on Azure Static Web Apps for free

Published on April 15, 2026 · by Matthew Gonzalez Nieves · 29 min read

Why I want you to build your own site on Azure

Hosting your own website used to be a weekend project that turned into a six-month commitment. You’d rent a VPS, fight with Nginx, renew SSL certificates by hand, and wake up one morning to find the disk full of log files. I did that for years. It was fun in the way that fixing your own car is fun, until it isn’t.

Azure Static Web Apps (SWA) is the opposite of that. You connect a GitHub repo, click through a few screens, and your site is live behind a global CDN with a free SSL certificate on a custom domain. Every commit you push triggers a build and a deploy. There is no server to patch. There is no certificate to renew. The Free tier costs nothing, and unless you start pulling serious traffic, that’s where you’ll stay.

I want this post to be the thing I wish I had read a year ago. By the end of it you should be able to: create a GitHub account and repo from scratch, install the tools you need, scaffold a static site, push a working build, hook it up to Azure via the portal, redeploy the same thing as Infrastructure as Code with an ARM template, and even know what to do if you’d rather use Azure DevOps instead of GitHub Actions. I’ll also tell you where I’m taking this site next, because the whole point of starting on Static Web Apps is that it leaves you a lot of room to grow.

I’m making the full source of this site open source on GitHub under GPL-3.0: github.com/mgn-techvault/mgn-techvault-website. You’re free to fork it, copy it, learn from it, or send a PR. If anything in this post is unclear, the working repo is the next best teacher. I believe in learning in public, and making this code available is part of that. If you spot a bug or a way to improve something, open an issue or submit a pull request. That’s the whole idea.

What Azure Static Web Apps actually is

SWA is a managed hosting service for modern web apps. Microsoft pitches it as “static + serverless API” but the static part is the headline. Under the hood, your site lives on Azure’s CDN edge nodes, so visitors anywhere in the world hit a server close to them. The build itself runs in a Microsoft-hosted environment called Oryx, which auto-detects your framework and runs the right install and build commands. You don’t configure a build server. You don’t manage containers. Oryx figures out whether you’re running Astro, Hugo, Next.js or something else and handles it.

Here’s what you get on the Free tier:

  • 100 GB of bandwidth per month (that’s a lot for a personal site)
  • 2 custom domains per app
  • Free SSL certificates for every custom domain you add, auto-renewed, no manual steps
  • Global CDN distribution, your site is cached on edge nodes worldwide
  • A unique preview URL for every pull request, so reviewers can click around the change before merge
  • Built-in authentication providers (GitHub, Microsoft, Twitter, custom OIDC) with zero backend code
  • An optional managed Functions API (Node, .NET, Python) for light backend work like contact forms
  • Staging environments tied to branches
  • A free *.azurestaticapps.net subdomain to start with
  • 0.5 GB storage for your app content

That is a serious amount of infrastructure for zero euros. The Standard plan is around 9 euros per month and adds an SLA, private endpoints, larger Functions, a linked backend (App Service, Container Apps), and 5 custom domains. For a personal site or a small business landing page, you will probably never need it.

Frameworks Static Web Apps supports

This surprised me. SWA is not tied to a single tool. The build presets cover almost everything modern:

  • Astro (what I use for this site)
  • Next.js (static export and hybrid with server side rendering)
  • Nuxt (Vue’s equivalent of Next)
  • SvelteKit
  • Gatsby
  • Hugo (Go-based, very fast builds)
  • Jekyll (Ruby, the classic GitHub Pages generator)
  • VuePress, VitePress (docs-focused Vue generators)
  • Docusaurus (React-based docs from Meta)
  • MkDocs (Python, popular for technical docs)
  • Angular, React, Vue, Lit, Preact (SPA frameworks)
  • Plain HTML/CSS/JS (no build needed at all)
  • Blazor WebAssembly (.NET in the browser)

If your tool can produce a folder of static files, SWA can host it. The only thing you ever have to tell it is which folder that is. Different frameworks put their output in different places: Astro uses dist, Hugo uses public, Next.js static export uses out, Jekyll uses _site. That’s the one value you’ll configure, and SWA handles the rest.

The takeaway: you are not locked into any framework by choosing SWA. If you start with plain HTML today and migrate to Astro next month, you change one line in the workflow and push. Done.

Prerequisites: what you need before you start

I’m going to be detailed here, because “install Git” is not helpful if you’ve never done it.

1. A computer with a terminal

Windows, macOS, or Linux all work. On Windows, I’d recommend installing Windows Terminal from the Microsoft Store. On macOS, the built-in Terminal app is fine. On Linux, you already know.

2. Git

Git is the version control tool that tracks your code changes and lets you push them to GitHub. Check if you have it:

git --version

If that prints something like git version 2.44.0, you’re good. If not:

  • Windows: download from https://git-scm.com/download/win and run the installer. The defaults are fine. Alternatively, install with winget install --id Git.Git.
  • macOS: run xcode-select --install in Terminal. That installs Git as part of the Xcode command line tools.
  • Linux (Debian/Ubuntu): sudo apt install git

After installing, configure your name and email. These end up in every commit you make:

git config --global user.name "Your Name"
git config --global user.email "your@email.com"

3. A GitHub account

Go to https://github.com and create an account if you don’t have one. It’s free. Pick a username you won’t be embarrassed by in two years; it’ll show up in your repo URLs.

Once you have an account, I’d recommend setting up SSH keys so you don’t have to type your password on every push:

ssh-keygen -t ed25519 -C "your@email.com"

Press Enter for the default file location, and set a passphrase (or leave it blank for convenience). Then copy the public key:

cat ~/.ssh/id_ed25519.pub

Go to GitHub > Settings > SSH and GPG keys > New SSH key. Paste the key and save.

Test the connection:

ssh -T git@github.com

If it says “Hi username! You’ve successfully authenticated”, you’re set.

If you’d rather skip SSH, GitHub also works with HTTPS and a personal access token, or with the GitHub CLI (gh auth login). Use whatever feels right.

4. Node.js (for JavaScript-based frameworks)

If you’re going with Astro, Next.js, Nuxt, Svelte, React, Vue, or any other JS framework, you need Node.js. I use version 22 (LTS). Check:

node --version
npm --version

If you need to install it: download from https://nodejs.org (pick the LTS version) or use a version manager like nvm:

# Install nvm (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

Tip: Always inspect install scripts before running them. You can open the URL in your browser first to review the script contents.

# Then install Node 22
nvm install 22
nvm use 22

On Windows, nvm-windows (https://github.com/coreybutler/nvm-windows) does the same thing.

If you’re using Hugo (Go-based) or Jekyll (Ruby-based), you don’t need Node.js. Install Hugo via brew install hugo on macOS or from https://gohugo.io/installation/. Install Jekyll via gem install jekyll bundler (you’ll need Ruby for that).

5. A free Azure account

Go to https://azure.microsoft.com/free. You get $200 of credit for the first 30 days and twelve months of free-tier services. For SWA on the Free plan, you won’t spend any of that credit, but you still need an account.

Azure will ask for a credit card to verify your identity. They won’t charge it on the free tier. I’ve been running this site for months and my SWA bill is exactly zero.

6. The Azure CLI (optional, for the IaC section)

If you want to deploy via the command line or use ARM templates, install the Azure CLI:

# macOS
brew install azure-cli

# Windows
winget install --id Microsoft.AzureCLI

# Linux (Debian/Ubuntu)
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

Tip: Always inspect install scripts before running them. You can open the URL in your browser first to review the script contents.

Verify:

az --version

7. A code editor

Visual Studio Code is what I use and what I’d recommend. Download from https://code.visualstudio.com. Install the “Azure Static Web Apps” extension while you’re at it. It lets you manage your SWA resources directly from the editor sidebar.

8. A domain name (optional)

If you want a custom URL instead of the default *.azurestaticapps.net subdomain, buy one from any registrar. Cloudflare, Namecheap, Porkbun, TransIP, they all work. Mine is registered at TransIP. Expect to pay between 5 and 15 euros per year depending on the TLD.

You don’t need a domain to follow this guide. You can add one later at any time.

Part 1: Setting up the GitHub side

You can skip this part if you already have a static site in a GitHub repo and know how to push to it. For everyone else, here is every step.

Step 1. Create a GitHub repository

Sign in to GitHub. Click the + in the top-right corner and choose New repository.

You’ll see a form. Fill it in:

  • Owner: your personal account, or a GitHub Organization if you have one. I created an organization called mgn-techvault specifically for this project.
  • Repository name: something short and descriptive, like my-site or my-blog. Mine is mgn-techvault-website.
  • Description: a one-liner, e.g. “My personal tech blog built with Astro.” This shows up on your repo page.
  • Visibility: Public. I’d recommend public for a personal site. It means anyone can see and fork your code. More on why I chose this in a moment.
  • Check Add a README file. This creates a README.md so the repo isn’t empty when you clone it.
  • Add .gitignore: pick Node from the dropdown (even if you’re not sure yet, the Node gitignore covers node_modules/, .env, and build output, which is useful for most frameworks).
  • Choose a license: I picked GNU General Public License v3.0 (GPL-3.0). This means anyone can use and modify the code, but they have to keep it open source too. If you don’t care about that, MIT is a simpler, more permissive license.

Click Create repository.

Step 2. Clone the repository to your computer

You now have a repo on GitHub with three files in it. You need a local copy to work with.

On the repo page, click the green Code button. If you set up SSH earlier, copy the SSH URL (git@github.com:your-username/my-site.git). If you’re using HTTPS, copy that one instead.

Open your terminal, navigate to where you keep your projects, and clone:

cd ~/projects    # or wherever you like
git clone git@github.com:your-username/my-site.git
cd my-site

Check that it worked:

ls -la

You should see the README, LICENSE, and .gitignore.

Step 3. Scaffold your static site

This is where you pick a framework and create the project structure. I’ll show Astro (what I use), Hugo, and plain HTML.

Option A: Astro

Astro is the static site generator I chose for this blog. It’s fast, flexible, and ships zero JavaScript to the browser by default (which means fast pages for your visitors).

npm create astro@latest .

The . at the end tells it to scaffold in the current directory. The wizard will ask a few questions:

  • How would you like to start your new project? Pick “Empty” for a blank canvas, or “Blog” for a starter template with a blog layout already done.
  • Install dependencies? Yes.
  • Do you plan to write TypeScript? I pick “Strict” but “Yes” is fine too.
  • Initialize a new git repository? No, we already have one.

When it finishes, you’ll have a project structure like this:

my-site/
  src/
    pages/
      index.astro       # Your homepage
    layouts/
    content/
  public/                # Static files (images, favicons)
  astro.config.mjs       # Astro config
  package.json
  tsconfig.json

Option B: Hugo

Hugo is a Go-based static site generator. Builds are extremely fast (milliseconds for small sites).

hugo new site . --force

The --force flag lets Hugo write into the existing directory. You’ll also want a theme. For example:

git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
echo 'theme = "ananke"' >> hugo.toml

Create your first post:

hugo new content posts/hello-world.md

Option C: Plain HTML

This is the no-framework option. Just create an index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Site</title>
</head>
<body>
    <h1>Hello, world</h1>
    <p>This is my site. It cost me nothing to host.</p>
</body>
</html>

No build step needed. SWA will serve this directly.

Step 4. Run your site locally

Make sure it actually works before pushing anything.

Astro:

npm run dev

Open http://localhost:4321 in your browser. You should see your site.

Hugo:

hugo server -D

Open http://localhost:1313.

Plain HTML:

You can just open index.html directly in your browser. Or if you want a local server:

npx serve .

Step 5. Check the .gitignore

Before committing, make sure your .gitignore covers the right things. For an Astro project, it should include at minimum:

node_modules/
dist/
.env
.env.*
.astro/

For Hugo: add public/ and resources/_gen/. For plain HTML, you probably don’t need to change anything.

The point: never commit node_modules/ (it’s huge and regenerated by npm install) and never commit .env files (they might contain secrets later).

Step 6. Stage, commit, and push

Now you save everything to GitHub. This is the flow you’ll repeat every time you make a change:

# See what's new or changed
git status
# Stage everything
git add .

# Check what's staged
git status
# Commit with a message
git commit -m "Initial Astro project scaffold"
# Push to GitHub
git push origin main

Step 7. Verify on GitHub

Go back to your repo on GitHub in the browser. Refresh the page. You should see all your project files.

Click into a few files to make sure they look right. Check that node_modules/ is not in there (the .gitignore should have excluded it). Check that no .env file was uploaded.

Your code is now on GitHub. The hosting part comes next.

Why I’m open-sourcing this site

I’m publishing the full source of this site on GitHub on purpose. I want to be transparent about that.

First, it’s a portfolio that actually runs. A working repo says more about what you can do than any line on a resume. Recruiters, potential collaborators, and community members can look at real code and see how I structure a project, how I write commit messages, how I solve problems.

Second, it’s how I learn faster. Every issue someone opens is a thing I get to fix. Every PR is a code review I didn’t have to ask for. The feedback you get on public code is free mentorship.

Third, it keeps me honest. Things I’d let slide in a private repo, I actually think about when I know people can read them. It forces me to write cleaner code and better documentation.

I chose GPL-3.0 specifically because I want anyone who forks and modifies this code to also share their changes. If that feels too restrictive for you, MIT is the other popular option, it lets people do whatever they want with your code, including using it in closed-source projects.

Practical advice if you go public: never commit secrets. Use .env files for API keys and tokens, add them to .gitignore from day one, and treat your main branch as “things I don’t mind strangers reading.” Everything else can live in feature branches.

Part 2: Deploying via the Azure Portal (the GUI route)

This is the click-through path. I recommend it the first time, because you actually see what’s being created and where every setting lives.

Step 1. Log in to the Azure Portal

Go to https://portal.azure.com and sign in with the account you created earlier. You’ll land on the dashboard.

Step 2. Search for Static Web Apps

In the search bar at the top, type “Static Web Apps”. You’ll see the service appear in the results under “Services”. Click on it.

Step 3. Click Create

On the Static Web Apps overview page, click the Create button (or + Create in the top-left).

Step 4. Fill in the Basics tab

This is the first tab in the creation wizard. Fill in each field:

Subscription: pick your subscription. If you only have one, it’ll be selected already.

Resource Group: click Create new and give it a name. I use rg-personal-web for all resources related to my personal website. A resource group is just a folder inside Azure to keep related things together.

Name: the name of the Static Web App resource. I use swa-mgntechvault. This becomes part of the default URL that Azure gives you, so pick something readable. No spaces, keep it lowercase.

Plan type: select Free. This is the one that costs nothing.

Region for Azure Functions API and staging environments: pick the closest one to you or to most of your visitors. This only affects where the Functions backend and staging environments run. The static content itself is served globally from the CDN no matter what you pick. I chose West Europe because I’m in the Netherlands.

Click Next: Deployment > to move to the next tab.

Step 5. Configure deployment source (GitHub)

Under Deployment details, you choose where your code lives.

Source: select GitHub.

Click the Sign in with GitHub button. A popup appears asking you to authorize Azure to access your GitHub account. Click Authorize Azure Static Web Apps.

Once authorized, three new dropdowns appear:

Organization: select your GitHub user or organization. Mine is mgn-techvault.

Repository: find and select the repo you created earlier.

Branch: pick main (or whatever your default branch is).

Step 6. Configure build settings

Under Build Details, you tell Azure how to build your site. SWA uses this to generate a GitHub Actions workflow file.

Build Presets: this dropdown lists common frameworks. If you see yours, pick it and the fields auto-fill. For Astro, I pick Custom because it gives me full control.

App location: / — this is the root of your repo where the app code lives. If your app lives in a subfolder like frontend/, change this.

Api location: leave this empty unless you have an /api folder with Azure Functions code.

Output location: dist for Astro. This is the folder where your framework puts the built files after running npm run build.

Here’s a quick reference for other frameworks:

FrameworkOutput location
Astrodist
Hugopublic
Next.js (static export)out
Gatsbypublic
Jekyll_site
Vue/React/Svelte (Vite)dist
Angulardist/<project-name>
Nuxt (static).output/public
Plain HTML/

Click Next: Tags > (you can skip tags for now) and then Review + create.

Step 7. Review and create

The review screen shows all your settings in a summary. Scan it to make sure the repo, branch, and build settings are correct.

Click Create. Provisioning takes about 30 seconds.

When it’s done, you’ll see “Your deployment is complete.” Click Go to resource.

Step 8. See what Azure created

On the resource overview page, you’ll see a few things:

  • A URL like https://lively-river-0123abc.azurestaticapps.net. This is your site’s default address.
  • A Status showing “Ready” or “Waiting for deployment.”
  • A link to the GitHub Action that Azure just added to your repo.

While you were looking at this screen, Azure pushed a GitHub Actions workflow file into your repo. That file tells GitHub how to build and deploy your site on every push.

Step 9. Watch the first GitHub Actions build

Go to your repository on GitHub and click the Actions tab. You’ll see a workflow run already in progress (or just completed). It has an orange dot if it’s running, a green checkmark if it succeeded, or a red X if something went wrong.

Click on the workflow run to see the details. You’ll see two jobs: “Build and Deploy.” Click on it.

Expand the steps to see what happened:

  1. Checkout checked out your code from the repo.
  2. Build And Deploy ran npm install, then npm run build, then uploaded the dist folder to Azure.

The first build usually takes 2 to 4 minutes. After that, your site is live.

Step 10. Visit your live site

Go back to the Azure Portal, find the URL on your resource overview page, and click it. Or just paste it in your browser.

That’s it. Your site is live. Worldwide. Behind a CDN. With HTTPS. For zero euros.

Step 11. Pull the workflow file to your local machine

Azure committed the workflow file directly to your main branch on GitHub. Your local copy doesn’t have it yet, so pull:

git pull origin main

Now look at the file:

cat .github/workflows/azure-static-web-apps-*.yml

This is the file that runs your CI/CD pipeline. Every time you push to main, GitHub runs this workflow and deploys the result to Azure. You can edit this file to customize the build, add tests, or change the Node version.

Step 12. Configure a custom domain

This is optional, but if you have a domain name, now is the time.

In the Azure Portal, open your SWA resource and click Custom domains in the left menu.

Click + Add. Choose whether you’re adding a subdomain (like www.example.com) or an apex/root domain (like example.com).

For a subdomain (www.example.com):

Azure gives you a CNAME target. Go to your domain registrar (Cloudflare, TransIP, Namecheap, wherever) and add a CNAME record:

TypeNameTarget
CNAMEwwwlively-river-0123abc.azurestaticapps.net

For an apex domain (example.com):

Azure first asks you to add a TXT record for verification:

TypeNameValue
TXT@(the verification string Azure shows you)

After validation, Azure will give you either an ALIAS/ANAME record target or, for some registrars, an A record with an IP.

Back in the Azure Portal, click Validate. DNS propagation can take anywhere from a few seconds to an hour. You can check the status with:

dig www.example.com +short
# or
nslookup example.com

Once validated, Azure automatically provisions a free SSL certificate. This takes another minute or two. You don’t configure anything. The cert auto-renews.

Step 13. Make a change and watch it deploy

The real test. Open a file in your editor, change something visible (the homepage title, a paragraph, anything), and push:

git add .
git commit -m "Update homepage content"
git push origin main

Go to GitHub > Actions. A new workflow run starts within seconds.

Wait about two minutes, refresh your site, and see the change live.

Step 14. Test the pull request preview

This is one of my favorite features. Create a branch, push a change, and open a PR:

git checkout -b test-preview
# make a visible change
git add .
git commit -m "Test PR preview"
git push origin test-preview

On GitHub, open a pull request from test-preview into main.

GitHub Actions builds a preview environment for this PR. After a few minutes, you’ll see a comment on the PR from the Azure bot with a unique URL.

Click that URL. You’ll see the site with your PR changes, completely separate from the production site. Reviewers can test the change visually before approving. When you merge the PR, the preview environment is automatically cleaned up.

That’s the full GUI path, from zero to a live site with CI/CD and PR previews.

Part 3: Deploying via ARM templates (Infrastructure as Code)

Once you’ve done the click-through path once, you’ll want to skip it forever. The moment you find yourself spinning up a second environment, staging, a side project, a friend’s site, you should switch to IaC.

The ARM template I use is in the same repo: github.com/mgn-techvault/mgn-techvault-website under the /infra folder.

Step 1. Get the template

git clone https://github.com/mgn-techvault/mgn-techvault-website.git
cd mgn-techvault-website/infra

You’ll find two files:

  • staticwebapp.json is the ARM template. It declares the resource, the GitHub connection, and the build settings.
  • staticwebapp.parameters.json is where you fill in the values: site name, GitHub repo URL, branch, and a GitHub PAT.

Step 2. Create a GitHub Personal Access Token

ARM needs to write a workflow file into your repo, so it needs permission. This is the one manual step.

Go to https://github.com/settings/tokens. Click Generate new token > Generate new token (classic).

Give it a name like azure-swa-deploy. Set an expiration (90 days is reasonable for a personal project). Check two scopes:

  • repo (full access to private and public repositories)
  • workflow (ability to update GitHub Action workflows)

Click Generate token and copy the token immediately. You won’t be able to see it again.

Paste it into staticwebapp.parameters.json in the repositoryToken field.

Do not commit the parameters file with the token in it. Add it to .gitignore:

echo "staticwebapp.parameters.json" >> .gitignore

I ship a staticwebapp.parameters.example.json in the repo so others know the format without seeing my token.

Step 3. Log in to Azure via the CLI

az login

A browser window opens. Sign in with your Azure account. Back in the terminal, set the right subscription:

az account list --output table
az account set --subscription "Your Subscription Name"

Step 4. Create the resource group (if it doesn’t exist)

az group create --name rg-personal-web --location westeurope

Step 5. Deploy the template

az deployment group create \
  --resource-group rg-personal-web \
  --template-file staticwebapp.json \
  --parameters @staticwebapp.parameters.json

This takes about a minute. The CLI prints the new resource and its properties, including the default URL.

Step 6. Trigger the first build

The ARM deployment created the SWA resource and pushed a workflow file to your GitHub repo. GitHub Actions picks it up on the next commit, so either push a change or trigger manually:

git commit --allow-empty -m "Trigger first deploy"
git push

Check GitHub Actions to confirm the build runs and succeeds.

Why bother with IaC?

For me, three reasons:

I can blow away the resource and recreate it in a minute. That’s useful for testing, for moving between subscriptions, and for starting over cleanly. I’ve done it three times already.

Changes to my infrastructure live in git, just like code. If I change the region or add a linked backend next month, the diff shows exactly what changed and why.

It’s content for this blog. Writing about IaC is something I want on this site anyway. Practicing it on my own infrastructure is the cheapest way to learn.

If you’d rather use Bicep (a friendlier syntax that compiles to ARM), Microsoft has a converter:

az bicep decompile --file staticwebapp.json

That gives you a staticwebapp.bicep you can deploy with az deployment group create --template-file staticwebapp.bicep. Same result, much cleaner file. I’m migrating my templates to Bicep in a future post.

Part 4: Other deployment options worth considering

GitHub Actions is the default and the easiest, but it is not your only choice. Here are three other approaches, depending on what your situation looks like.

Azure DevOps Pipelines

If your team or organization already uses Azure DevOps for other projects, you can deploy SWA from a DevOps pipeline instead of GitHub Actions.

The setup:

  1. In the Azure Portal, on your SWA resource, go to Overview and change the deployment source to Other. This gives you a deployment token instead of a GitHub connection. Copy that token.
  2. In Azure DevOps, create a new pipeline in your project. Create a file called azure-pipelines.yml in the root of your repo:
trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '22.x'
    displayName: 'Install Node.js'

  - script: |
      npm ci
      npm run build
    displayName: 'Build'

  - task: AzureStaticWebApp@0
    inputs:
      app_location: '/'
      output_location: 'dist'
      azure_static_web_apps_api_token: $(deployment_token)
  1. In the pipeline settings, go to Variables and add deployment_token as a secret variable. Paste the token from step 1.
  2. Run the pipeline. It builds your site and deploys to SWA using the token.

This is the route to take if your organization standardizes on Azure DevOps for governance reasons, if you need pipeline approvals and gates between environments, or if your code lives in Azure Repos instead of GitHub.

Manual deploys with the SWA CLI

For one-off uploads or for environments where you don’t want a CI pipeline at all:

npm install -g @azure/static-web-apps-cli

# Build your site first
npm run build

# Then deploy directly
swa deploy ./dist --deployment-token <your-token>

This is useful for quick tests, demos, and “my CI is broken but I need to ship something right now” situations.

GitHub Actions with manual approval

Same workflow as the default, but with a gate. Add a workflow_dispatch trigger so you can run it manually, and configure an environment with required reviewers in your GitHub repo settings (Settings > Environments > New environment > add required reviewers).

Then update the workflow:

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    environment: production     # requires approval
    steps:
      # ... same build and deploy steps

Now pushes to main still trigger the workflow, but the deploy step waits for a human to approve it in the GitHub Actions UI.

This is good for sites where you want an extra sanity check before anything goes live.

What else can you do with Static Web Apps?

The free tier is more than “just hosting.” Here’s what else is available, and what I’m planning to use.

Authentication and authorization. You can add GitHub, Microsoft, Twitter, or custom OIDC sign-in to your site without writing backend code. SWA handles the entire OAuth flow. Your frontend reads user info at /.auth/me. You protect routes by adding rules to staticwebapp.config.json:

{
  "routes": [
    {
      "route": "/admin/*",
      "allowedRoles": ["authenticated"]
    }
  ]
}

Anyone not logged in gets a 401 on /admin/ routes. Zero server code.

Managed Functions API. Drop an /api folder in your repo with a Node, .NET, or Python function, and SWA serves it as a serverless endpoint at /api/<function-name>. This is how I plan to build a contact form and a comment system without spinning up a separate backend.

Linked backend (Standard plan). On the Standard plan, you can link an external Azure Functions app, App Service, or Container App as the backend. Requests to /api/* get proxied to that backend. This is the upgrade path when managed Functions are no longer enough.

Custom routing, headers, and rewrites. The staticwebapp.config.json file handles 404 fallbacks, response headers, redirects, and MIME type overrides. I already use it for security headers (X-Frame-Options, Content-Security-Policy, X-Content-Type-Options) and a custom 404 page.

Pull request previews. Every PR automatically gets its own URL. You don’t configure anything. It just happens. This is invaluable for reviewing visual changes.

Application Insights integration. You can connect Application Insights for analytics, request logs, and performance monitoring without adding instrumentation to your site.

Staging environments. Beyond PR previews, you can create named environments tied to specific branches. Useful if you have a staging branch that gets its own URL for testing.

What I’m planning next

Phase 1 of this site is exactly what you’re reading: a static blog on the SWA Free tier, fully open source, bilingual English and Dutch, with content focused on Azure and cloud engineering. That’s intentional. Static is fast, cheap, and lets me spend my time on writing instead of ops.

Here’s what’s coming next, and you’ll be able to follow along because I’ll write about every step:

Bicep migration. The ARM template does the job, but Bicep is the newer, cleaner syntax that Microsoft recommends. I want to redeploy this whole stack from a single main.bicep file and publish the walkthrough as a blog post.

Azure DNS for all three domains. Right now mgntechvault.com, mgntechvault.nl, and mgntechvault.net are stitched together at the registrar. Moving DNS to Azure means I can manage records as code too, and write about the experience.

A Functions API for comments and a newsletter. The Free tier includes managed Functions. I want to build a simple comment system (stored in Azure Table Storage or Cosmos DB free tier) and a newsletter signup, without leaving the free tier boundary.

Microsoft Clarity for analytics. It’s free, privacy-friendly, and doesn’t require a cookie banner. I’ll add it and write about what I learn about my traffic.

A Phase 2 migration to Azure Container Apps. Once I have a real backend, I want to write about the trade-offs of moving from “static + managed Functions” to a containerized setup. Different price point, different capabilities, different operational model.

Eventually, AKS. Not because this site needs Kubernetes. It doesn’t. But writing about Azure responsibly means getting your hands dirty with the harder services too, and Kubernetes is the service that keeps coming up in Azure enterprise conversations.

The whole reason I started with Static Web Apps is that none of those steps require throwing anything away. Each piece is composable. SWA is the smallest possible thing that gets a real site online, and everything else can grow on top of it.

If something breaks

The most common things I’ve hit, and what fixed them:

“Output location not found” in the build log. The folder name in the workflow doesn’t match what your framework produces. Astro produces dist, Next static export produces out, Hugo produces public, Jekyll produces _site. Open the workflow YAML file, fix the output_location value, commit and push.

Custom domain stuck on “Validating.” DNS hasn’t propagated yet. Give it an hour. Check with dig www.yourdomain.com +short or nslookup yourdomain.com. If nothing comes back, the DNS record is wrong at your registrar, double-check the CNAME target or TXT value.

Workflow fails after renaming a branch. The workflow file is pinned to the old branch name in its trigger section. Open .github/workflows/azure-static-web-apps-*.yml, find the branches: line, change it to the new name, commit and push. Or delete the SWA resource and recreate it with the new branch.

GitHub Actions runs out of minutes. Public repos get unlimited minutes on GitHub free. Private repos get 2,000 per month. If you’re hitting the limit, make the repo public or upgrade your GitHub plan.

Old preview environments hanging around. SWA cleans them up when the PR closes, but you can also delete them manually in the Azure Portal under the Environments section of your SWA resource.

Build succeeds but site shows old content. CDN caching. Wait a few minutes or hard-refresh your browser (Ctrl+Shift+R / Cmd+Shift+R). If it persists, check the build log to confirm the new content was actually built.

Wrapping up

The cost of getting your own site online has never been lower. A GitHub account, an Azure free tier, and twenty minutes of clicking gets you a globally distributed site with CI/CD, PR previews, free SSL, and a custom domain. No servers, no containers, no monthly invoices.

Everything in this post, the GitHub setup, the GUI deploy, the ARM template, the DevOps option, the future plans, is reproducible from the open-source repo: github.com/mgn-techvault/mgn-techvault-website.

Fork it. Break it. Fix it. Build something. If you do, send me a link. I’d like to see what you make.

Share this article

Tags

AzureStatic Web AppsCI/CDGitHubOpen Source

Comments