AgentStack
SKILL verified MIT Self-run

Deploying Azure Static Web Apps

skill-alexpizarro-azure-lean-stack-skills-deploying-azure-static-web-apps · by alexpizarro

Deploys React + Azure Functions apps to Azure Static Web Apps with managed API functions, including the CommonJS / index.ts import / route-registration gotchas that make new functions 404 silently. Provides the SWA Bicep module, staticwebapp.config.json routing + security headers, and the API entrypoint convention. Use when scaffolding a SWA-based project, adding a new API function, or fixing a d…

No reviews yet
0 installs
4 views
0.0% view→install

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

✓ Passed

No 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.

Are you the author of Deploying Azure Static Web Apps? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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 (Free in test, Standard in prod)
  • sqlConnectionString@secure(), sets the SQL_CONNECTION_STRING app 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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.