ADR 001 — Static export (Vite) over SSR for the AlphaSwarm client surface
- Status: Accepted (2026-05-18)
- Authors: Platform team
- Supersedes: None
- Related: ADR 002 — single container client,
alphaswarm_client/CUTOVER.md
Context
The AlphaSwarm frontend rewrite (alphaswarm_client/, Vite 7 + React 19 + Tailwind 4 + shadcn/ui) is the cutover-complete operator UI. The legacy webui/ (Next.js 15 / antd) remains in tree only as a rollback path. The new alphaswarm_client container needs to bundle a UI build, the legacy fallback, and the FastAPI gateway into a single deployable image.
Three rendering options were considered for the canonical UI:
- Server-side rendering (Next.js) — server-rendered React with client-side hydration, mounted under uvicorn via
WSGIMiddleware. - Static export (Next.js) —
next buildwithoutput: 'export', identical to the prompt's original §2.1 wording. - Static export (Vite) —
pnpm --dir alphaswarm_client buildemitting a singledist/static SPA bundle.
Decision
The canonical UI shipped in alphaswarm_client is the Vite static export under alphaswarm_client/. The Next.js legacy webui/ is mounted as a rollback surface at /webui and Solara at /legacy, but neither is the default landing page.
Concretely:
- Stage 1 of
/build/docker/alphaswarm_client/Dockerfilerunspnpm --dir alphaswarm_client buildand copiesalphaswarm_client/dist/to/app/static/. - The FastAPI app in
alphaswarm/api/main.pymounts/staticto the Vite asset directory and falls back toindex.htmlfor client-side routes (SPA fallback). - The Vite app calls API endpoints through a relative
/apiprefix; the FastAPI gateway proxies those to whatever theConnectivityConfigenv vars point at.
Consequences
Positive
- Single-process Python runtime — no Node.js in the production image, smaller attack surface, no
npmsupply-chain risk in production. - No SSR cold-start cost. The whole UI is ~3 MB of static assets served with
Cache-Control: immutable. - Identical container in dev, k3d, and Kubernetes — only env vars change.
- Vite is already canonical per
alphaswarm_client/CUTOVER.md. Picking the in-flight stack avoids reopening the cutover debate.
Negative
- No streaming-SSR for the operator UI. WebSocket and SSE streams (chat, live, telemetry) carry the live data instead, which matches the existing throttled
useChatStream/useLiveStreamhooks. - SEO and first-paint metrics are weaker than SSR, but the AlphaSwarm UI is an authenticated operator console, not a public site — neither matters.
- Pre-rendered routes per user/tenant are not possible. All personalisation happens client-side using Auth0 claims from
useUser().
Alternatives considered
- SSR — rejected because it forces Node.js into the runtime image and adds a separate process to supervise.
- Static export (Next.js) — rejected to avoid maintaining two frontend toolchains. The Next.js webui stays as rollback only.
Implementation references
- Frontend build target:
alphaswarm_client/package.json"build": "vite build" - Production Dockerfile:
alphaswarm_platform/build/docker/alphaswarm_client/Dockerfile - SPA fallback handler:
alphaswarm/api/main.py::serve_spa - Cutover history:
alphaswarm_client/CUTOVER.md