Tech Stack
Production stack (v0.4.0)β
| Layer | Technology | Notes |
|---|---|---|
| Frontend | Astro 6 (static) + React 19 islands | Zero JS by default; interactivity only where needed |
| Styling | Tailwind CSS v4 | CSS-first config β no tailwind.config.mjs; settings live in front/src/styles/tailwind.css |
| UI Components | @headlessui/react | Accessible, unstyled primitives (combobox, dialog, etc.) |
| State | Zustand | One store each for host and participant |
| WebSockets | PartySocket (partysocket) | Drop-in wrapper with auto-reconnect |
| Backend | PartyKit (Cloudflare Workers + Durable Objects) | One DO instance per game session |
| Persistence | Durable Object storage (SQLite-backed) | Game state survives DO eviction |
| Session index | Browser localStorage | Host session list per device β sommelier-arena-host-{hostId} key (KV binding disabled; see Data Persistence) |
| Wine answers | Cloudflare KV (WINE_ANSWERS_KV namespace) | Category β JSON array of curated answer strings |
| Wine Answers Worker | Cloudflare Worker (wine-answers-worker/) | REST API for curated answer suggestions (port 1998 locally) |
| DNS + TLS | Cloudflare (ducatillon.net) | Managed; zero cert renewal |
| Docs proxy | Cloudflare Worker (proxy-worker/index.ts) | Routes /docs/* to Docusaurus Pages project |
Design principlesβ
- No server to maintain β Durable Objects are managed infrastructure; no VMs, no Docker in production.
- β¬0/month β Cloudflare free tier covers all traffic for a casual dinner-party app.
- Islands architecture β Astro renders everything at build time; React hydrated only for the game UI.
- State machine discipline β All business logic in the
back/module (split acrossgame.ts,utils.ts,constants.ts,scoring.ts,timer.ts). The frontend projects server state.
Deploy after backend changes
Any edit to files under back/ must be deployed to Cloudflare:
npx partykit deploy # from repo root
The proxy-worker does not need redeployment for backend-only changes.
- Single source of truth β Session state is DO built-in storage. No database, no ORM.
Backendβ
back/ β PartyKit Durable Object game logic (back/game.ts). This is the real-time game server.
Why PartyKit over plain Durable Objects?β
| Concern | Plain DO | PartyKit |
|---|---|---|
| WebSocket fan-out | Manual this.state.getWebSockets() | this.room.broadcast() |
| Local dev | wrangler dev (requires auth) | npx partykit dev (no auth) |
| Deploy | wrangler deploy | npx partykit deploy |
| Free tier | Yes | Yes |