Skip to content

Architecture overview

Tabletop Tracker — Hosting & Architecture Plan

Status: Accepted (pre-implementation) Date: 30 July 2026 Last verified: July 2026 — pricing and free-tier terms move constantly, re-check before committing spend.

This is the narrative overview. Individual decisions live in the decision catalogue; the rules agents are held to live in standards; the backlog is in work.


1. Context

A Letterboxd/Goodreads-style site and mobile app for tabletop games. Solo developer. Free to run at friends-and-family scale, with a credible path to monetisation if it proves out.

Priority features: curated lists & discovery, collection management, social feed & followers. Session logging and stats are present but deliberately understated in the UI.

Fixed constraints:

  • GitHub for source control — non-negotiable
  • £0 running cost at launch
  • Web + mobile from day one

2. The stack

LayerChoiceFree tier
Source & CIGitHub + GitHub ActionsYes
Monorepopnpm workspaces + Turborepon/a
APIHono on Cloudflare Workers100K req/day
WebAstro SSR + islands on Workers500 builds/month
MobileExpo + Expo Router, EAS BuildYes (build quotas apply)
DatabaseNeon Postgres via Cloudflare HyperdriveYes
Query layerDrizzle + drizzle-kit, drizzle-zod → shared schemasn/a
Object storageCloudflare R210 GB, zero egress
AuthClerk50,000 MRU
SearchPostgres FTS + pg_trgmIncluded
CatalogueWikidata (CC0) + Wikipedia (CC BY-SA), AI-normalised offlineFree sources; enrichment is paid
Docs subsiteAstro Starlight on Cloudflare PagesYes
ErrorsSentryYes

Shape: one Hono API on Workers serving two clients. Shared TypeScript types and Zod schemas live in a workspace package consumed by both web and mobile.


3. Decisions and rationale

Every decision now lives in the decision catalogue as a numbered ADR. This section is an index; the ADRs are the source of truth.

ADR-0001 to ADR-0007 originated here as ADR-1..7 and were extracted unchanged in substance.

ADRDecisionIn one line
0001Cloudflare over VercelVercel Hobby forbids commercial use; the cliff is $5/mo, not $20/seat
0002No Next.jsOpenNext friction, and Expo needs a standalone API anyway
0003Neon Postgres over D1Discovery needs FTS, pg_trgm, JSONB, and array columns
0004Hyperdrive, not the Neon driverDon’t stack two poolers — the trap most likely to bite
0005Clerk is the doorman, not the filing cabinetOwn the users table; never FK to a Clerk ID
0006Event-first data modelRatings live on the session; players are not users
0007Progressive disclosure for loggingSchema depth ≠ form depth. One tap to log
0008Drizzle as query layerOne schema source of truth, feeding drizzle-zod
0009Astro SSR + islandsOrganic search is the only free acquisition channel
0010CatalogueSource adapterUnblocks seven of nine build-order steps from BGG
0011Docker local, Neon branch in CIFast offline inner loop; real parity at the gate
0012Issues for state, repo for specsSplit by what each medium is good at
0013Astro Starlight on PagesReuses the stack; docs single-sourced from docs/
0014WSL2, repo off /mnt/cTaking §4’s own advice before it costs agent iterations
0015PR-gated agent autonomyFull autonomy on a branch, human at the merge
0016Open-data seeding with provenanceWikidata is CC0 — the answer to the BGG risk
0017AI normalisation, confidence-gatedFills what open data lacks; never silently authoritative

4. Gotchas

Cloudflare

  • 10ms CPU per invocation on free is the real ceiling, not the 100K daily request count. Average Workers use ~2.2ms, but auth validation + a database query + serialisation in one request gets close. Watch the feed queries.
  • No spend protection by default. Getting hammered blows the daily cap and forces a mid-month upgrade. Put rate limiting in the first commit.
  • CDN terms prohibit video and large-file distribution, with a 512 MB per-file cache limit. Serve box art from R2, don’t treat the CDN as an asset store.
  • nodejs_compat flag and a recent compatibility_date are required or database drivers simply won’t work.
  • Environment variables are runtime-only on Workers, and process.env stays empty unless compatibility_date is 2025-04-01 or later.

Clerk

  • A great deal of published material (and many models’ training data) still quotes the stale 10,000-user free tier. The current figure is 50,000 MRU. Check source dates.
  • User data is stored in the US with no EU data residency option. Irrelevant at friends scale; revisit if UK/EU usage grows.
  • MFA and passkeys are Pro features. Social login and the prebuilt UI are free.

Expo & app stores

  • OAuth, native components and passkeys all require a development build — they do not run in Expo Go. Basic email/password does. Adding Google sign-in changes the local dev loop.
  • Social buttons appear even without native credential setup, then fail on tap. Silent until pressed.
  • Google needs three OAuth client IDs (iOS, Android, Web) plus Team ID, Bundle ID, and Android SHA-256 fingerprints.
  • App Store Guideline 4.8: any app offering third-party social sign-in must also offer Sign in with Apple on iOS.
  • useSignInWithGoogle needs expo-crypto; useSignInWithApple needs expo-apple-authentication + expo-crypto and is iOS-only.

Catalogue data — was the one that could sink the project, now de-risked

The BoardGameGeek XML API is the obvious catalogue source, but its terms are permissive for non-commercial use and require permission for commercial use. Since this project intends to earn eventually, email BGG and get an answer in writing before building sync code (WI-H01).

This is no longer a project-level risk. Two decisions defused it:

  • ADR-0010 puts the catalogue behind a CatalogueSource interface, so the licensing answer blocks one swappable implementation rather than seven build-order steps.
  • ADR-0016 sources from Wikidata, which is CC0 — public domain, no permission to request, no terms that can be withdrawn. Wikipedia supplements it under CC BY-SA, which does carry an attribution obligation.

The original assessment that alternatives are “all worse” held only for hand-building a catalogue or depending on a third-party API. Harvesting open structured data was the option not considered, and it is strictly better than both.

Environment

  • OpenNext/Wrangler tooling is more reliable on Linux. On Windows, plan for WSL rather than debugging Windows-specific runtime issues.

5. Cost model

ItemCost
Cloudflare Workers, R2, Pages£0
Neon Postgres£0
Clerk (to 50K MRU)£0
GitHub£0
Catalogue sources (Wikidata, Wikipedia)£0
Apple Developer Program$99/year — unavoidable
Google Play Developer$25 one-off
LLM catalogue enrichmentOne-off, human-approved per run (WI-H05)

Apple’s fee is the only compulsory recurring spend, and Guideline 4.8 makes it compulsory the moment Google sign-in ships on iOS.

The enrichment cost is new and worth being explicit about. ADR-0017 uses Claude to derive mechanics, weight, and player count from prose — the fields discovery runs on and that no free structured source provides. It is:

  • Not a running cost. It scales with catalogue size, not with traffic, and is paid once per game rather than per request.
  • Reduced substantially by the Message Batches API (50% off) and prompt caching of the shared vocabulary prefix.
  • Never triggered by an agent. A human approves the volume and tier under WI-H05.

The £0 constraint in §1 covers the deployed system, and it still holds. It never covered a one-off build-time data pipeline.

Upgrade path when it grows: Workers Paid $5/mo → Clerk Pro $25/mo (above 50K MRU) → Neon paid tier. Nothing here has a cliff edge.


6. Build order

  1. Schema and migrations — Game, User, Player, Shelf, Session, List. Get the Player/User split right now.
  2. Catalogue ingestCatalogueSource adapter, then the open-data seeder (tools/catalogue-forge: Wikidata → Wikipedia → AI normalisation). No longer pending the BGG answer — see ADR-0016. The BGG sync worker remains a later, optional source.
  3. Auth — email OTP only. Enough to pass App Store review and get friends using it.
  4. Collection management — the feature that’s useful with a single user.
  5. Lists — hand-seed twenty good ones yourself. They bootstrap discovery before any behavioural data exists.
  6. Session logging — tier-one only (“Played”), expand later.
  7. Social feed — fan-out-on-read. A join across follows and recent activity serves Postgres happily into the tens of thousands of users.
  8. Discovery — content-based first (designer, mechanic, weight, player count). Works on day one with zero users.
  9. Social sign-in — once people actually use the thing.

7. Open questions

Resolved

QuestionResolution
Astro vs Vite SPA for webAstro SSR + islandsADR-0009. SEO is the only free acquisition channel.
Drizzle vs Kysely for the query layerDrizzleADR-0008. One schema source of truth feeding drizzle-zod.
BGG commercial licensing — “blocking for anything past step 2”No longer blocking. ADR-0010 puts the catalogue behind an adapter, and ADR-0016 sources from Wikidata (CC0). The email (WI-H01) now blocks only the BGG sync worker. If BGG says no, the project is unaffected.

Still open

  • Meta/Facebook sign-in — whether it earns its place at all. Google + Apple likely covers everyone, and each extra provider adds a credential surface. Defer until WI-050.
  • Enrichment volume and model tier — how many games to enrich, at what model tier and effort. Needs real cost numbers from a pilot run rather than a guess. Tracked as WI-H05; see ADR-0017.