Skip to main content
Version: 0.4.0 β€” Wine Answers

Tech Stack

Production stack (v0.4.0)​

LayerTechnologyNotes
FrontendAstro 6 (static) + React 19 islandsZero JS by default; interactivity only where needed
StylingTailwind CSS v4CSS-first config β€” no tailwind.config.mjs; settings live in front/src/styles/tailwind.css
UI Components@headlessui/reactAccessible, unstyled primitives (combobox, dialog, etc.)
StateZustandOne store each for host and participant
WebSocketsPartySocket (partysocket)Drop-in wrapper with auto-reconnect
BackendPartyKit (Cloudflare Workers + Durable Objects)One DO instance per game session
PersistenceDurable Object storage (SQLite-backed)Game state survives DO eviction
Session indexBrowser localStorageHost session list per device β€” sommelier-arena-host-{hostId} key (KV binding disabled; see Data Persistence)
Wine answersCloudflare KV (WINE_ANSWERS_KV namespace)Category β†’ JSON array of curated answer strings
Wine Answers WorkerCloudflare Worker (wine-answers-worker/)REST API for curated answer suggestions (port 1998 locally)
DNS + TLSCloudflare (ducatillon.net)Managed; zero cert renewal
Docs proxyCloudflare Worker (proxy-worker/index.ts)Routes /docs/* to Docusaurus Pages project

Design principles​

  1. No server to maintain β€” Durable Objects are managed infrastructure; no VMs, no Docker in production.
  2. €0/month β€” Cloudflare free tier covers all traffic for a casual dinner-party app.
  3. Islands architecture β€” Astro renders everything at build time; React hydrated only for the game UI.
  4. State machine discipline β€” All business logic in the back/ module (split across game.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.

  1. 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?​

ConcernPlain DOPartyKit
WebSocket fan-outManual this.state.getWebSockets()this.room.broadcast()
Local devwrangler dev (requires auth)npx partykit dev (no auth)
Deploywrangler deploynpx partykit deploy
Free tierYesYes