Azure Blob Static Site Hosting
This document describes the DCS architecture for hosting customer sites using Azure Blob Storage with static website hosting, fronted by Azure Front Door.
Overview
Starting January 2026, new customer sites are deployed to Azure Blob Storage accounts with static website hosting enabled, replacing Azure Static Web Apps (SWA). This architecture provides:
- Immediate content updates without full site rebuilds
- Cost efficiency via shared Front Door profile
- Direct blob editing for premium tier customers
- Simplified infrastructure with built-in index.html fallback
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Azure Front Door │
│ (afd-dcs-internal profile) │
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │
│ │ endpoint1 │ endpoint2 │ endpoint3 │ sitegroup01 │ │
│ │ (customer) │ (customer) │ (customer) │ (demos) │ │
│ └──────┬──────┴──────┬──────┴──────┬──────┴──────┬──────┘ │
└─────────┼─────────────┼─────────────┼─────────────┼──────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Storage │ │ Storage │ │ Storage │ │ Storage │
│ Account │ │ Account │ │ Account │ │ Accounts │
│ ($web) │ │ ($web) │ │ ($web) │ │ (demos) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘⚠️ Standard Tier Constraint: The diagram above shows per-customer endpoints for clarity, but Front Door Standard tier is limited to 10 endpoints per profile (7 currently used). In practice, customer sites should share endpoints using origin-group-per-customer + rule-set routing (the
sitegroup01pattern), NOT endpoint-per-customer. See the repo-local.docs/front-door-audit-2026-02-15.mdaudit for current usage.
Key Components
| Component | Resource | Purpose |
|---|---|---|
| Storage Account | Per-site GPv2 | Hosts static website files in $web container |
| Static Website | Built-in feature | Serves index.html, handles SPA routing |
| Front Door Profile | afd-dcs-internal | CDN, SSL, custom domains, cache control |
| Origin Group | Per-site | Routes traffic to storage static website endpoint |
Demo Sites
Five demo sites showcase different frameworks and CMS integration patterns:
| Site | Framework | Theme | Storage Account | Custom Domain |
|---|---|---|---|---|
| demo | VitePress | CloudSync Docs | stdemositeone | demo.duffcloudservices.com |
| demo-2 | Vue | Maple Street Bakery | stdemositetwo | demo-2.duffcloudservices.com |
| demo-3 | Astro | Pixel Forge Creative | stdemositethree | demo-3.duffcloudservices.com |
| demo-4 | React | TechVenture Labs | stdemositefour | demo-4.duffcloudservices.com |
| demo-5 | Angular | FitnessPro Gym | stdemositefive | demo-5.duffcloudservices.com |
Resource Group
All demo site storage accounts are in:
- Resource Group:
rg-dcs-demosites - Region:
northcentralus
Content Update Modes
Mode 1: Build-Time Injection (All Tiers)
Content is injected at build time via the @duffcloudservices/cms Vite plugin:
- GitHub Actions runs on content commit
dcsContentPlugin()reads.dcs/content.yaml- Content is embedded as
__DCS_CONTENT__global - Site is built and deployed to blob storage
Latency: 2-5 minutes
Mode 2: Runtime Injection (Premium Tier)
Content is fetched from blob storage at runtime:
- Portal editor saves changes
- Server writes updated
.dcs/content.yamlto blob storage - Front Door cache is purged
- Site fetches fresh content on load
Latency: <10 seconds
Database Schema
Site records in Table Storage include:
{
"storageAccountName": "stdemositeone",
"storageAccountResourceId": "/subscriptions/.../storageAccounts/stdemositeone",
"storageContainerName": "$web",
"hostingProvider": "blob-storage"
}Server Integration
SAS Token Generation
The server generates SAS tokens for direct blob uploads:
// server/internal/services/portal/sitehosting.go
func (s *Service) getBlobStorageDeploymentTarget(ctx context.Context, site *site.Row) (*BlobTarget, error) {
// Uses site.StorageAccountName to generate SAS token
// Container is always "$web" for static website hosting
}Quick Publish Flow
// QuickPublishContent updates content directly in blob storage
func (s *Service) QuickPublishContent(ctx context.Context, siteSlug string, content []byte) error {
// 1. Get site's storage account
// 2. Generate SAS token for $web container
// 3. Upload updated content.yaml to .dcs/content.yaml
// 4. Purge Front Door cache for /.dcs/* paths
}CMS Packages
@duffcloudservices/cms-core
Shared utilities used by all framework packages:
ContentConfiguration/SeoConfigurationtypesresolveTextKey()- Content resolution with page/global fallbackresolveSeoForPage()- SEO metadata resolutionloadContentYaml()/loadSeoYaml()- YAML parsingfetchRuntimeContent()- Runtime API fetching
Framework-Specific Packages
| Package | Framework | Features |
|---|---|---|
@duffcloudservices/cms | Vue 3 | useTextContent, useSEO composables + Vite plugins |
@duffcloudservices/cms-react | React | Hooks + Vite plugins |
@duffcloudservices/cms-angular | Angular | Services + post-build injection |
@duffcloudservices/cms-astro | Astro | Astro integration |
Visual Page Editor Integration
The portal's visual page editor requires properly annotated HTML for section detection. See Visual Editor Annotations for details.
Required HTML Attributes
Sites must include these data attributes for the page editor to detect editable content:
<section data-section="hero" data-section-label="Hero Banner">
<h1 data-text-key="hero.heading">{{ t('hero.heading') }}</h1>
<p data-text-key="hero.description">{{ t('hero.description') }}</p>
</section>Snapshot Capture
The deployment workflow captures page snapshots that include:
- Full-page screenshot
- Section boundaries (detected from
data-sectionelements) - Text keys (detected from
data-text-keyelements) - Current text values for each key
Deployment Pipeline
GitHub Actions Workflow
# .github/workflows/deploy.yml
jobs:
deploy:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm build
# Upload to blob storage
- uses: azure/CLI@v1
with:
inlineScript: |
az storage blob upload-batch \
--account-name ${{ secrets.STORAGE_ACCOUNT }} \
--destination '$web' \
--source ./dist \
--overwrite
# Capture snapshots and upload
- run: node scripts/capture-snapshots.js
# Purge Front Door cache
- run: |
az afd endpoint purge \
--resource-group rg-dcs-internal \
--profile-name afd-dcs-internal \
--endpoint-name ${{ secrets.FRONTDOOR_ENDPOINT }} \
--content-paths '/*'Related Documentation
- Visual Editor Annotations - HTML attribute requirements
Migration from SWA
Sites migrating from Azure Static Web Apps need:
- New storage account with static website enabled
- Front Door configuration (origin group, endpoint, route)
- Updated site record in Table Storage
- GitHub Actions workflow updated from
swa deployto blob upload - HTML annotations added for visual editor support
