agentsclimarketplace

Deploying azure static web apps

Skill alexpizarro/azure-lean-stack-skills/skills/deploying-azure-static-web-apps

Azure apps that cost nothing when nobody's using them. A Claude Code skill pack — 14 composable skills, 37+ documented gotchas, branch-per-env CI/CD.

Install
npx -y skills add alexpizarro/azure-lean-stack-skills --skill deploying-azure-static-web-apps

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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 deployed function that returns 404 even though it compiled successfully.

SKILL.md

8.4 KB, as published. Nobody here has run it

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

NeedUse
CRUD REST API, React frontend, HTTP-only, < 30s requestsSWA managed functions (this skill)
Timer triggers, queue triggers, AI workloads, > 30s executionFC1 Flex Consumption
Long-running server, WebSocket/SSE, custom Docker runtimeContainer Apps

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<HttpResponseInit> {
  // 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 — 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 Guardrail #11).

// GET /api/health — shallow, DB-free. Safe to poll frequently.
export async function health(req: HttpRequest): Promise<HttpResponseInit> {
  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 for the full routing + auth pattern.

Bicep

The SWA Bicep module lives in 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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.