web
The Next.js product site — the app replacing legacy, on port 3002.
apps/statshub-web is the StatsHub product site and the successor to
legacy. It serves the same product surface over the same
data, rebuilt on the current Next.js — reading it through
api rather than through a database client of its own.
bun run dev:web # http://localhost:3002
bun run build:webWhat it does
Value bets
/value-bets, and value-bets-v2/[view] and -v3/[view] beside it. Three
generations of the model run at once on purpose, so a change to it is
judged against the board it replaces rather than against memory.
Prop boards
ProfHunter ranks player props against their own history; the prop screener filters them by league, market, odds and hit rate. 100 Club, anytime goalscorer, assists and super subs are the same board cut differently.
Lineups and matchday
Confirmed and predicted starting XIs for upcoming matches, with fouls alerts next to them.
Trends and outliers
Best recent hit-rate streaks over a 3-to-40 game window, and the players sitting furthest from their own baseline.
Entity pages
A fixture, team, player or referee, each at /<kind>/<slug>/<id>. The slug
is decoration — only the trailing id resolves.
The assistant
A chat that answers over the same data, streaming from /api/agent. It is
the one route handler in the app; everything else it needs is an API call.
Referees, the odds converter, the bet builder, favourites and the data
visualiser round it out — one directory each under src/composed.
How it is built
Filters are parallel routes
@filters, @importantFilters and @rightSidebar are slots on the
(dashboard) layout. None may hold a [...segments] catch-all: a
catch-all matches every url, which makes Next treat every path as routed
and takes the legacy rewrites down with it.
Legacy is handed over a board at a time
LEGACY_ROUTES in next.config.js lists what still belongs to
legacy. Those rewrites are matched before this
app's own routes, so removing an entry is the hand-over — and adding a page
without removing one means the page is never reached.
No database client
src/lib/server-api.ts is the whole data layer: a typed fetch at the Go
API with a revalidate on every call. There is no src/db and no
connection string in this app.
Prerendering reads the sitemap
src/lib/static-params.ts takes generateStaticParams off the sitemaps
the API already serves, rather than adding a second enumeration to keep in
step. It caps at 250 paths a route and returns none when the API is
unreachable, because docker build has no API on its network.
Filter state lives in the URL
nuqs, in about forty files. A board's state is its query string, so a filtered view is a link — and the top bar and the filter panel bind to the same key, which is what stops them disagreeing.
proxy.ts, not middleware.ts
Next 16's rename. src/proxy.ts refreshes the Supabase session on every
request that is not a static asset; the matcher is where that exclusion
list lives.
Layout
| Directory | What is in it |
|---|---|
src/app | App Router routes — all of them; there is no Pages Router left |
src/ai | Model tooling: the chat agent and its evals |
src/composed | One directory per board — value-bets, prophunter, lineups |
src/server, src/lib | Server-only helpers, including server-api.ts |
src/hooks, src/contexts | Client-side state |
src/components | App-local components; shared ones live in @statshub/ui-web |
There is no src/pages/api
It was deleted: api is the API. Anything that looks like it
needs a Next.js route handler here almost certainly belongs in the Go service.
Environment
Copy apps/statshub-web/.env.example to .env.local. The Supabase URL and anon key are
the two it cannot serve a request without — missing them gives a 500 reading
Your project's URL and API key are required, not a build failure.
NEXT_PUBLIC_* values are inlined at build time, so changing one means a
rebuild, and every one of them has to be listed in turbo.json's build.env or
Turbo will serve a stale build.
Scripts beyond the usual
bun run chat # the AI chat agent, in the terminal
bun run chat:eval # its eval suite
bun run test # vitest