Azure Static Web Apps is Microsoft’s purpose-built hosting service for modern static websites and single-page applications. It offers an incredibly generous free tier, seamless GitHub integration, and enterprise-grade features that make it an excellent choice for blogs, documentation sites, portfolios, and web applications built with frameworks like Astro, Next.js, React, Vue, and more.
In this comprehensive guide, we’ll walk through deploying a static website to Azure Static Web Apps from scratch, covering everything from creating your site to configuring custom domains and adding serverless APIs.
What Are Azure Static Web Apps?
Azure Static Web Apps (SWA) is a service that automatically builds and deploys full-stack web applications to Azure from a GitHub or Azure DevOps repository. When you push code changes, Azure automatically triggers a build and deploys the updated site to a globally distributed CDN.
Key characteristics include:
- Static content hosting — HTML, CSS, JavaScript, and media files served from points of presence worldwide
- Integrated API support — Serverless Azure Functions for backend logic
- Built-in authentication — Support for Azure AD, GitHub, Twitter, and custom providers
- Staging environments — Automatic preview deployments for pull requests
- Custom domains with free SSL — Automatic HTTPS certificate provisioning and renewal
Benefits of Azure Static Web Apps
Free Tier Highlights
The free plan is surprisingly generous and includes:
- 100 GB bandwidth per month
- 2 custom domains
- 0.5 GB storage
- Built-in authentication
- Staging environments for pull requests
- Global CDN distribution
- Free SSL certificates
For personal blogs, portfolios, and small business sites, the free tier is more than sufficient.
Why Choose Azure SWA Over Other Platforms?
- GitHub Actions integration — CI/CD is configured automatically with zero effort
- Global CDN — Your site is served from the nearest Azure edge location worldwide
- Automatic SSL — Free certificates are provisioned and renewed automatically
- Preview environments — Every pull request gets its own staging URL for testing
- Serverless APIs — Add backend functionality without managing servers
- Enterprise-ready — Easy upgrade path for organizations already using Azure
Prerequisites
Before you begin, you’ll need:
- An Azure account — Sign up for free to get $200 in credits and 12 months of free services
- A GitHub account — For source code hosting and CI/CD integration
- Node.js 18+ — Installed on your local machine
- Git — For version control
- Azure CLI (optional) — For command-line deployment
Verify your Node.js installation:
node --version
npm --version
Step 1: Create a Simple Static Site
Let’s create a simple static website that we’ll deploy to Azure. For this tutorial, we’ll build a minimal site, but the same process works for Astro, React, Vue, Next.js, or any static site generator.
Create a new project directory and initialize it:
mkdir my-azure-site
cd my-azure-site
npm init -y
Create the project structure:
mkdir -p src/css src/js
Create src/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 Azure Static Web App</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>
<nav>
<a href="/" class="logo">MyApp</a>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about.html">About</a></li>
</ul>
</nav>
</header>
<main>
<section class="hero">
<h1>Welcome to My Azure Static Web App</h1>
<p>This site is hosted on Azure Static Web Apps with a global CDN, free SSL, and automatic deployments from GitHub.</p>
<button id="api-button">Call API</button>
<p id="api-response"></p>
</section>
</main>
<footer>
<p>© 2025 MyApp. Powered by Azure Static Web Apps.</p>
</footer>
<script src="/js/app.js"></script>
</body>
</html>
Create src/css/style.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
line-height: 1.6;
color: #1e293b;
}
header {
background: #2563eb;
padding: 1rem 2rem;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.logo {
color: white;
font-size: 1.5rem;
font-weight: bold;
text-decoration: none;
}
nav ul {
list-style: none;
display: flex;
gap: 1.5rem;
}
nav a {
color: white;
text-decoration: none;
}
.hero {
max-width: 800px;
margin: 4rem auto;
padding: 2rem;
text-align: center;
}
.hero h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
button {
background: #2563eb;
color: white;
border: none;
padding: 0.75rem 2rem;
font-size: 1rem;
border-radius: 0.5rem;
cursor: pointer;
margin-top: 1.5rem;
}
button:hover {
background: #1d4ed8;
}
footer {
text-align: center;
padding: 2rem;
color: #64748b;
border-top: 1px solid #e2e8f0;
margin-top: 4rem;
}
Create src/js/app.js:
document.getElementById('api-button').addEventListener('click', async () => {
const responseEl = document.getElementById('api-response');
try {
const response = await fetch('/api/message');
const data = await response.json();
responseEl.textContent = data.message;
} catch (error) {
responseEl.textContent = 'API not configured yet. Deploy to Azure to enable APIs!';
}
});
Step 2: Push to GitHub
Initialize a Git repository and push your code to GitHub:
git init
echo "node_modules/" > .gitignore
git add .
git commit -m "Initial commit: static site ready for Azure deployment"
Create a new repository on GitHub, then push your code:
git remote add origin https://github.com/YOUR_USERNAME/my-azure-site.git
git branch -M main
git push -u origin main
Step 3: Create a Static Web App in Azure Portal
- Sign in to the Azure Portal
- Click Create a resource and search for Static Web Apps
- Click Create and fill in the details:
| Setting | Value |
|---|---|
| Subscription | Your Azure subscription |
| Resource Group | Create new: my-site-rg |
| Name | my-azure-site |
| Plan type | Free |
| Region | Select nearest to your audience |
| Source | GitHub |
- Click Sign in with GitHub and authorize Azure
- Configure the build details:
| Setting | Value |
|---|---|
| Organization | Your GitHub username |
| Repository | my-azure-site |
| Branch | main |
| Build Presets | Custom |
| App location | /src |
| API location | api (leave blank if no API) |
| Output location | “ (empty for plain HTML) |
- Click Review + Create, then Create
Azure will automatically create a GitHub Actions workflow in your repository and trigger the first deployment.
Step 4: Understanding the GitHub Actions Workflow
Azure automatically generates a GitHub Actions workflow file in your repository at .github/workflows/. Here’s what a typical workflow looks like:
name: Azure Static Web Apps CI/CD
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
jobs:
build_and_deploy_job:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- uses: actions/checkout@v4
with:
submodules: true
lfs: false
- 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: "/src"
api_location: "api"
output_location: ""
close_pull_request_job:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
name: Close Pull Request Job
steps:
- name: Close Pull Request
id: closepullrequest
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
action: "close"
The AZURE_STATIC_WEB_APPS_API_TOKEN secret is automatically added to your GitHub repository by Azure during the setup process.
For an Astro site like FreeTechLearner.com, you would customize this workflow to run npm ci and npm run build before the deploy step, and set output_location to dist.
Step 5: Custom Domain Setup
Once your site is deployed, you’ll want to connect a custom domain:
- In the Azure Portal, navigate to your Static Web App
- Click Custom domains in the left menu
- Click Add and choose your method:
Using a Root Domain (example.com)
- Select Custom domain on other DNS
- Enter your domain name (e.g.,
example.com) - Azure will provide an IP address
- Create an A record in your DNS provider pointing to this IP
- Create a TXT record for domain verification
- Click Validate and wait for DNS propagation (can take up to 48 hours)
Using a Subdomain (www.example.com)
- Select Custom domain on other DNS
- Enter the subdomain (e.g.,
www.example.com) - Azure will provide a CNAME target
- Create a CNAME record in your DNS provider pointing to the Azure URL
- Click Validate
Azure automatically provisions and renews a free SSL certificate for your custom domain. No manual certificate management is needed.
Step 6: Environment Variables
For configuration that shouldn’t be committed to source control, use environment variables:
- In the Azure Portal, go to your Static Web App
- Click Configuration in the left menu under Settings
- Click Add to create new application settings
These variables are available to your Azure Functions API at runtime. For build-time variables used by your static site generator, add them to your GitHub Actions workflow:
- name: Build
run: npm run build
env:
PUBLIC_SITE_URL: "https://www.example.com"
PUBLIC_ANALYTICS_ID: "G-XXXXXXXXXX"
Advanced Features
Adding an API with Azure Functions
Azure Static Web Apps support serverless APIs through Azure Functions. Create an api directory in your project root:
mkdir -p api/message
Create api/message/index.js:
module.exports = async function (context, req) {
context.res = {
status: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
message: 'Hello from Azure Functions!',
timestamp: new Date().toISOString()
}
};
};
Create api/message/function.json:
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
Your API will be available at /api/message when deployed.
Authentication
Azure Static Web Apps includes built-in authentication with several providers. Add route rules to your staticwebapp.config.json:
{
"routes": [
{
"route": "/admin/*",
"allowedRoles": ["authenticated"]
}
],
"responseOverrides": {
"401": {
"statusCode": 302,
"redirect": "/.auth/login/github"
}
}
}
Built-in auth providers include:
- GitHub:
/.auth/login/github - Azure AD:
/.auth/login/aad - Twitter:
/.auth/login/twitter
Users can log out via /.auth/logout.
Fallback Routes and Navigation
For single-page applications, configure a navigation fallback in staticwebapp.config.json:
{
"navigationFallback": {
"rewrite": "/index.html",
"exclude": ["/images/*.{png,jpg,gif,svg}", "/css/*", "/*.{js,json}"]
}
}
Pricing Overview
Azure Static Web Apps offers two tiers:
| Feature | Free | Standard ($9/month) |
|---|---|---|
| Bandwidth | 100 GB/month | 100 GB included |
| Storage | 0.5 GB | 2 GB |
| Custom domains | 2 | 5 |
| SSL certificates | Free | Free |
| Staging environments | 3 | 10 |
| Azure Functions | Managed | Bring your own |
| SLA | None | 99.95% |
| Authentication | Built-in | Built-in + Custom |
| Private endpoints | No | Yes |
For most personal projects and small business sites, the free tier provides everything you need.
Deploying an Astro Site
If you’re deploying an Astro site (like this blog), the configuration differs slightly. Here’s what your GitHub Actions workflow should look like:
name: Azure Static Web Apps CI/CD
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened, closed]
branches: [main]
jobs:
build_and_deploy:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build Astro site
run: npm run build
- name: Deploy to Azure
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: '/'
output_location: 'dist'
skip_app_build: true
The key settings for Astro are:
app_location: '/'— The root of your projectoutput_location: 'dist'— Astro’s default build output directoryskip_app_build: true— We’ve already built the site in a previous step, so Azure doesn’t need to build it again
Troubleshooting Common Issues
Build Failures
Check the GitHub Actions logs for detailed error messages. Common causes include missing dependencies, incorrect Node.js versions, or wrong app/output locations.
404 Errors After Deployment
Ensure your output_location in the workflow matches your framework’s build output directory. For Astro, it’s dist. For Next.js, it’s out (with static export). For plain HTML, leave it empty.
Custom Domain Not Working
DNS propagation can take up to 48 hours. Verify your DNS records are correct using:
dig your-domain.com
nslookup your-domain.com
API Routes Not Found
Ensure your api directory is at the root of your repository and that the api_location in your workflow configuration matches.
Conclusion
Azure Static Web Apps provides an excellent hosting solution for modern static websites. The combination of a generous free tier, automatic CI/CD through GitHub Actions, global CDN distribution, free SSL certificates, and built-in authentication makes it hard to beat for personal projects and small to medium websites.
Whether you’re deploying a personal blog built with Astro, a documentation site, or a React single-page application, Azure Static Web Apps handles the infrastructure so you can focus on creating great content and experiences.
The key steps to remember are:
- Create your static site and push it to GitHub
- Connect your repository to Azure Static Web Apps
- Let the automatic CI/CD pipeline handle builds and deployments
- Configure your custom domain for a professional touch
- Add serverless APIs with Azure Functions as your needs grow
Happy deploying! If you have questions about deploying your specific framework or need help with advanced configuration, feel free to leave a comment below.