🏗️ Architecture
The monorepo
LIPAIX uses a monorepo — a single Git repository that contains multiple applications and shared packages. This means all the code for the web app, the Discord bot, and the documentation lives in one place, making it easier to share code and keep things consistent.
lipaix/
├── apps/
│ ├── web/ # Next.js 15 + PayloadCMS 3
│ └── discord-bot/ # Discord.js 14 bot
├── shared/
│ └── common/ # Shared TypeScript code
└── docs/
└── vitepress/ # This documentationThe repository uses pnpm workspaces to manage dependencies across all these packages from a single pnpm install at the root.
apps/web — Web application
This is the largest part of the project. It's a Next.js 15 application that runs three distinct sections in one process:
Route groups
Next.js organizes routes using route groups — folders in parentheses that group related routes without affecting the URL. The apps/web app has three:
(frontend) — The public website
Everything visitors see at lipaix.com: the home page, event listings, event detail pages, the About page, Impro, Videos, and Partners pages.
(payload) — MyLipaix and API
The PayloadCMS MyLipaix interface (at /admin) and all the custom API endpoints under /api/v1/. PayloadCMS runs inside Next.js and shares the same process.
(live) — The live show display
The public display page at /live/[eventId] used during Public Investigation shows. It connects to the server via a real-time stream (SSE) to receive updates as the coordinator makes changes in the Live Admin.
PayloadCMS
PayloadCMS is a headless CMS (Content Management System) — think of it as a configurable database with a built-in MyLipaix interface. In LIPAIX, it handles:
- The database schema (all collections are defined in TypeScript)
- The MyLipaix UI
- Authentication (Discord OAuth)
- A REST API for the collections
The PayloadCMS configuration lives at apps/web/src/payload.config.ts.
apps/discord-bot — Discord bot
A standalone Node.js service built with Discord.js 14. It:
- Registers slash commands with Discord
- Listens for slash command interactions
- Calls the web app's
/api/v1/API to fetch data - Formats the response and sends it back to the user
It runs as a separate process, completely independent of the web app.
shared/common — Shared code
A TypeScript package imported by both apps/web and apps/discord-bot. It contains:
- Adapters: transforms data from the PayloadCMS format into domain objects
- Message builders: formats Discord messages (lineup posts, availability lists)
- Type definitions: shared TypeScript types for events, players, availabilities, etc.
This avoids duplicating business logic across both apps.
How data flows
Public website → data
Server Components in the (frontend) route group call the PayloadCMS local API directly (in-process, no HTTP). This is fast and secure — no network round-trip needed.
Discord bot → data
The bot calls the web app's REST API (/api/v1/) over HTTP, authenticating with the LIPAIX_API_TOKEN.
Live show display → data
The /live/[eventId] page subscribes to a Server-Sent Events (SSE) stream at /api/v1/pi-session/[eventId]/stream. The Live Admin pushes updates through this stream in real time.
Public site → PayloadCMS local API → PostgreSQL
Discord bot → /api/v1/ (HTTP) → PostgreSQL
Live display → /api/v1/.../stream → PostgreSQL (SSE)Technology summary
| Layer | Technology |
|---|---|
| Frontend framework | Next.js 15 (App Router, React 19) |
| CMS & MyLipaix | PayloadCMS 3 |
| Database | PostgreSQL |
| Styling | TailwindCSS |
| Discord bot | Discord.js 14 |
| Language | TypeScript (everywhere) |
| Package manager | pnpm workspaces |
| Hosting | Railway |
