Install
$ agentstack add skill-alexpizarro-azure-lean-stack-skills-deploying-azure-static-web-apps ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Deploying Azure Static Web Apps
The default deployment target for React + Functions web apps. Free tier, global CDN, managed Functions baked in, zero ongoing cost when idle.
When to use SWA vs FC1 vs Container Apps
| Need | Use | |------|-----| | CRUD REST API, React frontend, HTTP-only, 30s execution | [FC1 Flex Consumption](../deploying-fc1-flex-consumption-functions/SKILL.md) | | Long-running server, WebSocket/SSE, custom Docker runtime | [Container Apps](../deploying-azure-container-apps/SKILL.md) |
Project structure
frontend/ — React 19 + Vite 6 + TypeScript
├── src/
│ ├── App.tsx
│ └── services/api.ts
├── public/
│ └── staticwebapp.config.json — routing + security headers
├── vite.config.ts — proxy /api/* → localhost:7071
└── package.json
api/ — Managed Azure Functions v4 (Node 22)
├── src/
│ ├── index.ts — Entry point — IMPORT EVERY FUNCTION FILE HERE
│ ├── functions/
│ │ ├── hello.ts — app.http(...) at the bottom registers the route
│ │ ├── getItems.ts
│ │ └── createItem.ts
│ └── lib/
│ └── database.ts — mssql pool, module-level singleton
├── host.json
├── tsconfig.json — "module": "commonjs" required for SWA
├── package.json — "main": "dist/index.js" (specific path, not glob)
└── local.settings.json.example — empty strings + __HINT_* keys
The two SWA gotchas you WILL hit
1. New function returns 404 — forgot to import in index.ts
// api/src/index.ts — every function file imported as a side effect
import './functions/hello';
import './functions/getItems';
import './functions/createItem';
import './functions/newThing'; // ← add this when you create newThing.ts
The app.http(...) registration in each function file only runs when the module is loaded. If index.ts doesn't import it, the route silently doesn't exist. The TypeScript compiles fine. The deploy succeeds. The URL returns 404.
Make this part of your "add a function" muscle memory: create the file, write the function, add the import.
2. CommonJS, not ESM
SWA managed functions require:
// api/tsconfig.json
{ "compilerOptions": { "module": "commonjs" } }
// api/package.json
{ "main": "dist/index.js" } // ← specific path, not "dist/**/*.js"
// (do NOT add "type": "module")
This differs from standalone FC1 which uses ESM. If you migrate api/ to FC1 later, you must flip both settings.
Function file template
// api/src/functions/getItems.ts
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
import { getPool } from '../lib/database';
export async function getItems(req: HttpRequest, ctx: InvocationContext): Promise {
// Mock when DB not configured (local dev convenience)
if (!process.env.SQL_CONNECTION_STRING) {
ctx.warn('SQL_CONNECTION_STRING not set — returning mock');
return { status: 200, jsonBody: { items: [{ id: 1, name: '[MOCK] Item' }] } };
}
const pool = await getPool();
const result = await pool.request().query('SELECT * FROM dbo.Items');
return { status: 200, jsonBody: { items: result.recordset } };
}
// Route registration — runs when this module is imported by index.ts
app.http('getItems', {
methods: ['GET'],
authLevel: 'anonymous',
route: 'items',
handler: getItems,
});
local.settings.json.example pattern
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_EXTENSION_VERSION": "~4",
"FUNCTIONS_WORKER_RUNTIME": "node",
"SQL_CONNECTION_STRING": "",
"__HINT_SQL_CONNECTION_STRING": "Server=tcp:{org}-{project}-sql-test.database.windows.net,1433;Database={org}-{project}-sqldb-test;User Id=sqladmin;Password=YOUR_PASSWORD;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
}
Use "" for all user-input values. Placeholder strings like "sk-YOUR_KEY" are truthy in JavaScript and fool if (!value) checks, causing confusing runtime errors instead of clean "not configured" mocks. Use __HINT_* keys for format documentation (Functions ignores __-prefixed keys).
Mock pattern
Always check the env var; fall back to a mock when not set. This makes local dev work without provisioning:
if (!process.env.AI_PROJECT_ENDPOINT) {
ctx.warn('AI_PROJECT_ENDPOINT not set — returning mock response');
return { status: 200, jsonBody: { result: '[MOCK] Set AI_PROJECT_ENDPOINT.' } };
}
if (process.env.SQL_CONNECTION_STRING) {
await saveToDB(...);
} else {
ctx.warn('SQL_CONNECTION_STRING not set — skipping DB write');
}
For a full offline stack (real local SQL + blob storage, not just mocks), use [developing-azure-apps-locally](../developing-azure-apps-locally/SKILL.md) — a Docker SQL Server 2022 + Azurite stack with a one-command bootstrap. Mock mode (above) covers external services you don't want to run locally (AI, email); the offline stack covers the DB + storage your app actually needs.
Shallow health check (avoid a costly anti-pattern)
If you add a health/status endpoint, make the default check DB-free — return 200 without querying the database. A health endpoint that runs a DB query on every call, combined with any uptime monitor or scheduler polling it, keeps a SQL Serverless database permanently awake and bills compute 24/7 (see [applying-azure-cost-guardrails](../applying-azure-cost-guardrails/SKILL.md) Guardrail #11).
// GET /api/health — shallow, DB-free. Safe to poll frequently.
export async function health(req: HttpRequest): Promise {
const deep = req.query.get('deep') === '1';
if (!deep) {
return { status: 200, jsonBody: { ok: true } }; // no DB call — won't wake serverless
}
// Deep check runs the DB query only on explicit request (e.g. a manual probe).
try {
await getPool();
return { status: 200, jsonBody: { ok: true, db: 'ok' } };
} catch (e) {
return { status: 503, jsonBody: { ok: false, db: 'error' } };
}
}
app.http('health', { methods: ['GET'], authLevel: 'anonymous', route: 'health', handler: health });
Point uptime monitors and schedulers at /api/health (shallow); reserve /api/health?deep=1 for on-demand diagnostics. Proven: trg-directory-website's status.ts queried the DB on every call and a 5-min scheduler hit it, defeating auto-pause.
staticwebapp.config.json
{
"navigationFallback": { "rewrite": "/index.html", "exclude": ["/api/*", "/assets/*", "*.{css,js,png,svg}"] },
"globalHeaders": {
"Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Permissions-Policy": "geolocation=(), microphone=(), camera=()"
}
}
See [references/swa-config.md](references/swa-config.md) for the full routing + auth pattern.
Bicep
The SWA Bicep module lives in [scaffolding-azure-bicep-infrastructure/templates/infra/modules/staticWebApp.bicep](../scaffolding-azure-bicep-infrastructure/templates/infra/modules/staticWebApp.bicep). It exposes:
skuName— per-env (Freein test,Standardin prod)sqlConnectionString—@secure(), sets theSQL_CONNECTION_STRINGapp setting
Composes with
- [scaffolding-azure-bicep-infrastructure](../scaffolding-azure-bicep-infrastructure/SKILL.md) — generates the SWA Bicep + workflow
- [managing-azure-sql-migrations](../managing-azure-sql-migrations/SKILL.md) — for the DB schema
- [diagnosing-azure-deployment-failures](../diagnosing-azure-deployment-failures/SKILL.md) — when functions 404 or return 500
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: alexpizarro
- Source: alexpizarro/azure-lean-stack-skills
- License: MIT
- Homepage: https://github.com/alexpizarro/azure-lean-stack-skills
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.