StatsHub documentation
Four apps over one Postgres database, and a Go API being ported route-for-route from a Next.js one. Start with the app you are working in.
Start with the app you are working in. Each has its own section — what it does, how a screen is put together, where its data comes from, and the rules for writing code in it.
web
The product site. Value bets, the prop boards, lineups, entity pages and the assistant, on Next.js 16.
expo
The iOS and Android app. Five tabs, filters as sheets, home-screen widgets.
docs
This site. Every doc in the repository, plus the generated endpoint reference.
legacy
The Next.js app web and the API are replacing. Still the oracle the port
is checked against.
An app is a deployable: it has a Dockerfile, a service in docker-compose.yml
and a port. Code that two of them share is a package instead,
and the HTTP surface they all read through is the API — one Go
service behind two prefixes, not an app.
Ports
| App | Dev port |
|---|---|
legacy | 3000 |
docs | 3001 |
web | 3002 |
api | 8080 |
expo (Metro) | 8142 |
bun run dev starts all of them except legacy. Two apps sharing a port fail
in a way that reads like a caching bug, so check this table before picking one
for a new app.
How each app's pages are laid out
The bigger apps carry the same five, in the same order, so you can read one app against another:
| Page | What is on it |
|---|---|
| Overview | What the app is, the stack in it, what it does and how it is built |
| Features | Every screen or surface it serves, and what each one is for |
| Architecture | How a screen is put together — routing, layout, the seams |
| Data | Where the data comes from, what caches it, what invalidates it |
| Conventions | The rules for writing code in it, and what breaks when you skip one |
Smaller apps stop earlier. legacy is one page because it is being deleted.
What every deployable has
apps/<name>/Dockerfile, built with the repository root as the context, not the app directory. The first stage runsturbo prune <name> --dockerso a change in an unrelated app does not invalidate the layer cache.- A service in
docker-compose.yml. - Scripts named
dev,build,lint,typecheck— Turbo picks a task up by script name, so nothing has to be registered. - Root shortcuts:
bun run dev:<name>,build:<name>,start:<name>. - A
/api/healthroute, or an equivalent, that answers before its dependencies are reachable. It is what the containerHEALTHCHECKand Coolify both call.
Add an app is the walkthrough, and it ends with the docs page — an app without one is invisible from here.
Not an app
API
Two HTTP surfaces over one Go implementation, the generated reference for all 298 operations, and how far the port has got. Every app reads through it; none of them owns it.
Packages
Code two apps share — the two UI kits, the shared config, and the contract suite that proves the Go port faithful.
Working here
Getting started
Install once, bring up every dev server, and know which gate to run before you push.
Architecture
What each app owns, why there are two UI kits, and how a change reaches production.
Guides
Add a docs page, an app, a package, a UI component.
Conventions
The rules that hold across apps — where state lives, how a tool screen is built, how docs are written.
Agents
What a coding agent reads here, and which skill to load for which task.
Every doc in the repo is a file under apps/statshub-docs/content/docs. There is no
docs/ at the repository root and none inside an app — if you find one, it is a
leftover, and it belongs here.