Skip to content

Angular MFE Azure Static Web Apps Deployment | Complete Guide

This comprehensive guide walks you through deploying your Angular Micro Frontend applications to Azure Static Web Apps, including CI/CD setup, custom domains, and production optimization.

  • Global CDN: Worldwide content distribution
  • CI/CD Integration: GitHub Actions automation
  • Custom Domains: Easy SSL/HTTPS setup
  • Serverless APIs: Optional backend integration
  • Cost-Effective: Pay-per-use pricing model
  • Built-in Security: Authentication and authorization
graph TB
A[GitHub Repository] --> B[GitHub Actions]
B --> C[Build Pipeline]
C --> D[Azure Static Web Apps]
D --> E[Global CDN]
E --> F[Custom Domain]
subgraph "MFE Deployment"
D1[Host App]
D2[Loan Calculator MFE]
D3[User Dashboard MFE]
D4[Transaction History MFE]
end

🚀 Step 1: Prepare Applications for Production

Section titled “🚀 Step 1: Prepare Applications for Production”

Update each application’s build configuration for production:

angular.json (for each MFE):

{
"projects": {
"your-app": {
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./webpack.config.js"
},
"outputPath": "dist/your-app",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
]
}
}
}
}
}
}
}

environment.prod.ts (Host Application):

export const environment = {
production: true,
apiUrl: 'https://api.yourdomain.com',
mfeConfig: {
loanCalculator: 'https://loan-calculator.azurestaticapps.net/remoteEntry.js',
userDashboard: 'https://user-dashboard.azurestaticapps.net/remoteEntry.js',
transactionHistory: 'https://transaction-history.azurestaticapps.net/remoteEntry.js'
},
appInsights: {
instrumentationKey: 'your-app-insights-key'
},
auth: {
clientId: 'your-auth-client-id',
authority: 'https://yourtenant.b2clogin.com',
redirectUri: 'https://banking-platform.azurestaticapps.net'
}
};

environment.prod.ts (Each MFE):

export const environment = {
production: true,
apiUrl: 'https://api.yourdomain.com',
hostUrl: 'https://banking-platform.azurestaticapps.net',
appInsights: {
instrumentationKey: 'your-app-insights-key'
}
};

webpack.prod.config.js (Host Application):

const ModuleFederationPlugin = require("@module-federation/webpack");
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
mode: "production",
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
plugins: [
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
"mfeLoanCalculator": "https://loan-calculator.azurestaticapps.net/remoteEntry.js",
"mfeUserDashboard": "https://user-dashboard.azurestaticapps.net/remoteEntry.js",
"mfeTransactionHistory": "https://transaction-history.azurestaticapps.net/remoteEntry.js",
},
shared: {
"@angular/core": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/common": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/router": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
},
}),
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8
})
],
};

☁️ Step 2: Create Azure Static Web Apps

Section titled “☁️ Step 2: Create Azure Static Web Apps”
  1. Sign in to Azure Portal: https://portal.azure.com

  2. Create Resource Group:

    Unix/macOS/Windows:

    Terminal window
    az group create --name rg-banking-mfe --location "East US"
  3. Create Static Web Apps for each application:

Host Application:

Unix/macOS/Windows:

Terminal window
az staticwebapp create \
--name banking-platform-host \
--resource-group rg-banking-mfe \
--source https://github.com/yourusername/host-banking-app \
--location "East US" \
--branch main \
--app-location "/" \
--output-location "dist/host-banking-app"

Loan Calculator MFE:

Unix/macOS/Windows:

Terminal window
az staticwebapp create \
--name loan-calculator-mfe \
--resource-group rg-banking-mfe \
--source https://github.com/yourusername/mfe-loan-calculator \
--location "East US" \
--branch main \
--app-location "/" \
--output-location "dist/mfe-loan-calculator"

Repeat for User Dashboard and Transaction History MFEs.

Ensure each repository has the correct structure:

Repository Structure:
├── .github/
│ └── workflows/
│ └── azure-static-web-apps-[name].yml
├── src/
├── angular.json
├── package.json
├── webpack.config.js
├── webpack.prod.config.js
└── staticwebapp.config.json

staticwebapp.config.json (Host Application):

{
"routes": [
{
"route": "/dashboard/*",
"allowedRoles": ["authenticated"]
},
{
"route": "/loan-calculator",
"allowedRoles": ["anonymous", "authenticated"]
},
{
"route": "/api/*",
"allowedRoles": ["authenticated"]
},
{
"route": "/*",
"serve": "/index.html",
"statusCode": 200
}
],
"navigationFallback": {
"rewrite": "/index.html",
"exclude": ["/assets/*", "/*.{js,css,png,jpg,jpeg,gif,svg,ico,woff,woff2,ttf,eot}"]
},
"mimeTypes": {
".js": "application/javascript",
".css": "text/css"
},
"globalHeaders": {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"Content-Security-Policy": "default-src 'self' *.azurestaticapps.net; script-src 'self' 'unsafe-inline' *.azurestaticapps.net; style-src 'self' 'unsafe-inline'"
}
}

staticwebapp.config.json (MFE Applications):

{
"routes": [
{
"route": "/*",
"serve": "/index.html",
"statusCode": 200
}
],
"navigationFallback": {
"rewrite": "/index.html"
},
"globalHeaders": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization"
}
}

🔄 Step 3: GitHub Actions CI/CD Pipeline

Section titled “🔄 Step 3: GitHub Actions CI/CD Pipeline”

.github/workflows/azure-static-web-apps-host.yml:

name: Azure Static Web Apps CI/CD - Host
on:
push:
branches:
- main
- develop
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@v3
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint code
run: npm run lint
- name: Run tests
run: npm run test:ci
- name: Build for production
run: npm run build:prod
env:
NODE_ENV: production
- name: Deploy to Azure Static Web Apps
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_HOST }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
app_location: "/"
output_location: "dist/host-banking-app"
config_file_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_HOST }}
action: "close"

.github/workflows/azure-static-web-apps-loan-calculator.yml:

name: Azure Static Web Apps CI/CD - Loan Calculator
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@v3
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint code
run: npm run lint
- name: Run tests
run: npm run test:ci
- name: Build for production
run: npm run build:prod
env:
NODE_ENV: production
- name: Copy remoteEntry.js to root
run: cp dist/mfe-loan-calculator/remoteEntry.js dist/mfe-loan-calculator/remoteEntry.js
- name: Deploy to Azure Static Web Apps
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_LOAN_CALCULATOR }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
app_location: "/"
output_location: "dist/mfe-loan-calculator"
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_LOAN_CALCULATOR }}
action: "close"

Add these scripts to each package.json:

{
"scripts": {
"build:prod": "ng build --configuration production --output-hashing all",
"test:ci": "ng test --watch=false --browsers=ChromeHeadless --code-coverage",
"lint": "ng lint",
"e2e:ci": "ng e2e --watch=false",
"analyze": "ng build --configuration production --stats-json && npx webpack-bundle-analyzer dist/*/stats.json"
}
}

  1. Purchase Domain: Buy your domain from a registrar
  2. Configure DNS in Azure Portal or your DNS provider:
DNS Records:
├── banking.yourdomain.com (Host App)
├── loan-calculator.yourdomain.com (Loan Calculator MFE)
├── user-dashboard.yourdomain.com (User Dashboard MFE)
└── transactions.yourdomain.com (Transaction History MFE)
  1. Add Custom Domain in Azure Portal:

Unix/macOS/Windows:

Terminal window
az staticwebapp hostname set \
--name banking-platform-host \
--hostname banking.yourdomain.com

4.2 Update MFE Configuration for Custom Domains

Section titled “4.2 Update MFE Configuration for Custom Domains”

mf.manifest.json (Production):

{
"production": {
"mfeLoanCalculator": "https://loan-calculator.yourdomain.com/remoteEntry.js",
"mfeUserDashboard": "https://user-dashboard.yourdomain.com/remoteEntry.js",
"mfeTransactionHistory": "https://transactions.yourdomain.com/remoteEntry.js"
}
}

5.1 Azure Application Insights Integration

Section titled “5.1 Azure Application Insights Integration”

app-insights.service.ts:

import { Injectable } from '@angular/core';
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { environment } from '../environments/environment';
@Injectable({
providedIn: 'root'
})
export class AppInsightsService {
private appInsights: ApplicationInsights;
constructor() {
this.appInsights = new ApplicationInsights({
config: {
instrumentationKey: environment.appInsights.instrumentationKey,
enableAutoRouteTracking: true,
enableRequestHeaderTracking: true,
enableResponseHeaderTracking: true
}
});
this.appInsights.loadAppInsights();
this.appInsights.trackPageView();
}
trackEvent(name: string, properties?: any) {
this.appInsights.trackEvent({ name }, properties);
}
trackException(exception: Error, properties?: any) {
this.appInsights.trackException({ exception }, properties);
}
trackMfeLoad(mfeName: string, loadTime: number) {
this.trackEvent('MFE_LOAD', {
mfeName,
loadTime,
timestamp: new Date().toISOString()
});
}
}

performance.service.ts:

import { Injectable } from '@angular/core';
import { AppInsightsService } from './app-insights.service';
@Injectable({
providedIn: 'root'
})
export class PerformanceService {
constructor(private appInsights: AppInsightsService) {}
measureMfeLoadTime(mfeName: string): () => void {
const startTime = performance.now();
return () => {
const endTime = performance.now();
const loadTime = endTime - startTime;
this.appInsights.trackMfeLoad(mfeName, loadTime);
console.log(`${mfeName} loaded in ${loadTime.toFixed(2)}ms`);
};
}
trackBundleSize() {
if ('navigator' in window && 'connection' in navigator) {
const connection = (navigator as any).connection;
this.appInsights.trackEvent('NETWORK_INFO', {
effectiveType: connection.effectiveType,
downlink: connection.downlink,
rtt: connection.rtt
});
}
}
}

Update staticwebapp.config.json with strict CSP:

{
"globalHeaders": {
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline' *.yourdomain.com *.azurestaticapps.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: *.yourdomain.com; font-src 'self' fonts.googleapis.com fonts.gstatic.com; connect-src 'self' *.yourdomain.com api.yourdomain.com"
}
}

auth.config.ts (Production):

export const authConfig = {
instance: "https://login.microsoftonline.com/",
tenantId: "your-tenant-id",
clientId: "your-client-id",
redirectUri: "https://banking.yourdomain.com",
scopes: ["user.read", "banking.access"]
};

Deploy in this specific order to avoid dependency issues:

  1. Deploy MFEs First:

    Unix/macOS:

    Terminal window
    # Loan Calculator
    cd mfe-loan-calculator
    git push origin main
    # User Dashboard
    cd ../mfe-user-dashboard
    git push origin main
    # Transaction History
    cd ../mfe-transaction-history
    git push origin main

    Windows:

    Terminal window
    # Loan Calculator
    Set-Location mfe-loan-calculator
    git push origin main
    # User Dashboard
    Set-Location ..\mfe-user-dashboard
    git push origin main
    # Transaction History
    Set-Location ..\mfe-transaction-history
    git push origin main
  2. Deploy Host Application:

    Unix/macOS:

    Terminal window
    cd host-banking-app
    git push origin main

    Windows:

    Terminal window
    Set-Location host-banking-app
    git push origin main
  3. Update Host Configuration:

    • Update mf.manifest.json with production URLs
    • Update webpack configuration

Create verification scripts to test deployment:

Unix/macOS (verify-deployment.sh):

#!/bin/bash
echo "🔍 Verifying MFE Deployment"
echo "==========================="
# URLs to check
HOST_URL="https://banking.yourdomain.com"
LOAN_CALC_URL="https://loan-calculator.yourdomain.com"
DASHBOARD_URL="https://user-dashboard.yourdomain.com"
TRANSACTIONS_URL="https://transactions.yourdomain.com"
check_url() {
local name=$1
local url=$2
echo -n "Checking $name... "
if curl -s -f "$url" > /dev/null; then
echo "✅ Available"
else
echo "❌ Not available"
return 1
fi
}
# Check all URLs
check_url "Host Application" "$HOST_URL"
check_url "Loan Calculator MFE" "$LOAN_CALC_URL"
check_url "User Dashboard MFE" "$DASHBOARD_URL"
check_url "Transaction History MFE" "$TRANSACTIONS_URL"
# Check remoteEntry.js files
echo ""
echo "🔍 Checking Remote Entries..."
check_url "Loan Calculator Remote" "$LOAN_CALC_URL/remoteEntry.js"
check_url "Dashboard Remote" "$DASHBOARD_URL/remoteEntry.js"
check_url "Transactions Remote" "$TRANSACTIONS_URL/remoteEntry.js"
echo ""
echo "🎯 Testing MFE Integration..."
# Test if host can load MFEs (simplified check)
if curl -s "$HOST_URL" | grep -q "banking"; then
echo "✅ Host application is responding"
else
echo "❌ Host application issue detected"
fi
echo ""
echo "✅ Deployment verification complete!"

Windows (verify-deployment.ps1):

Terminal window
Write-Host "🔍 Verifying MFE Deployment" -ForegroundColor Blue
Write-Host "===========================" -ForegroundColor Blue
# URLs to check
$HOST_URL = "https://banking.yourdomain.com"
$LOAN_CALC_URL = "https://loan-calculator.yourdomain.com"
$DASHBOARD_URL = "https://user-dashboard.yourdomain.com"
$TRANSACTIONS_URL = "https://transactions.yourdomain.com"
function Check-Url {
param(
[string]$Name,
[string]$Url
)
Write-Host "Checking $Name... " -NoNewline
try {
$response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 10
if ($response.StatusCode -eq 200) {
Write-Host "✅ Available" -ForegroundColor Green
return $true
} else {
Write-Host "❌ Not available (Status: $($response.StatusCode))" -ForegroundColor Red
return $false
}
} catch {
Write-Host "❌ Not available (Error: $($_.Exception.Message))" -ForegroundColor Red
return $false
}
}
# Check all URLs
Check-Url "Host Application" $HOST_URL
Check-Url "Loan Calculator MFE" $LOAN_CALC_URL
Check-Url "User Dashboard MFE" $DASHBOARD_URL
Check-Url "Transaction History MFE" $TRANSACTIONS_URL
# Check remoteEntry.js files
Write-Host ""
Write-Host "🔍 Checking Remote Entries..." -ForegroundColor Cyan
Check-Url "Loan Calculator Remote" "$LOAN_CALC_URL/remoteEntry.js"
Check-Url "Dashboard Remote" "$DASHBOARD_URL/remoteEntry.js"
Check-Url "Transactions Remote" "$TRANSACTIONS_URL/remoteEntry.js"
Write-Host ""
Write-Host "🎯 Testing MFE Integration..." -ForegroundColor Yellow
# Test if host can load MFEs (simplified check)
try {
$hostContent = Invoke-WebRequest -Uri $HOST_URL -UseBasicParsing
if ($hostContent.Content -match "banking") {
Write-Host "✅ Host application is responding" -ForegroundColor Green
} else {
Write-Host "❌ Host application issue detected" -ForegroundColor Red
}
} catch {
Write-Host "❌ Host application issue detected: $($_.Exception.Message)" -ForegroundColor Red
}
Write-Host ""
Write-Host "✅ Deployment verification complete!" -ForegroundColor Green

Configure Azure CDN for better global performance:

Unix/macOS/Windows:

Terminal window
az cdn profile create \
--name banking-mfe-cdn \
--resource-group rg-banking-mfe \
--sku Standard_Microsoft
az cdn endpoint create \
--name banking-platform \
--profile-name banking-mfe-cdn \
--resource-group rg-banking-mfe \
--origin banking.yourdomain.com

Configure caching headers in staticwebapp.config.json:

{
"routes": [
{
"route": "/assets/*",
"headers": {
"Cache-Control": "public, max-age=31536000, immutable"
}
},
{
"route": "/*.js",
"headers": {
"Cache-Control": "public, max-age=31536000, immutable"
}
},
{
"route": "/*.css",
"headers": {
"Cache-Control": "public, max-age=31536000, immutable"
}
},
{
"route": "/remoteEntry.js",
"headers": {
"Cache-Control": "public, max-age=300"
}
}
]
}

✅ Azure Deployment Verification Checklist

Section titled “✅ Azure Deployment Verification Checklist”
  • Azure Static Web Apps created for all applications
  • GitHub Actions workflows configured and working
  • Production builds optimized and compressed
  • Custom domains configured with SSL
  • DNS records pointing to correct endpoints
  • CORS headers configured for MFE communication
  • Security headers implemented (CSP, HSTS, etc.)
  • Application Insights monitoring configured
  • Performance metrics being tracked
  • Error handling and fallbacks working
  • Authentication integrated (if required)
  • CDN configured for global distribution
  • Caching strategy implemented
  • Deployment verification script passing
  • Load Time: < 3 seconds globally
  • 📦 Bundle Size: Optimized and compressed
  • 🔒 Security Score: A+ rating
  • 🌍 Global Availability: 99.9% uptime
  • 📊 Performance Score: > 90
  • Accessibility: AA compliance

Your Angular MFE application is now deployed to production! Next steps:

  1. Azure Deployment Complete
  2. ➡️ Continue to: Documentation Guide - Create comprehensive documentation
  3. 📚 Alternative: Advanced Concepts - Learn advanced MFE patterns

Your Angular Micro Frontend platform is now live and serving users globally! 🎉

  1. CORS Errors: Check staticwebapp.config.json headers
  2. Module Not Found: Verify remoteEntry.js URLs
  3. Build Failures: Check GitHub Actions logs
  4. SSL Issues: Verify custom domain configuration
  5. Performance Issues: Review bundle sizes and CDN configuration
  • 📊 Azure Portal: Monitor application health
  • 📈 Application Insights: Performance and error tracking
  • 🔍 GitHub Actions: CI/CD pipeline monitoring
  • 🌐 Azure CDN: Global distribution metrics