Tech Stack
Production stack (v2.0 PartyKit)β
| Layer | Technology | Notes |
|---|---|---|
| Frontend | Astro 5 (static) + React 19 islands | Zero JS by default; interactivity only where needed |
| Styling | Tailwind CSS v4 | Utility-first; no design system dependency |
| 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 | Cloudflare KV (SOMMELIER_HOSTS namespace) | Maps host:{id} β list of session codes |
| 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
back/game.ts. The frontend projects server state. - 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 |