Eventuall Architecture Overview
Eventuall is a live event platform built on Next.js 15 and Cloudflare's edge infrastructure. The system combines a monolithic frontend with microservice-style backend workers, connected by three real-time communication channels (video, chat, and state synchronization).
This section provides visual diagrams and high-level context. For detailed explanations of each layer, see the sub-pages below.
Deep Dives
- Frontend Architecture — Next.js 15 App Router, Server vs Client Components, OpenNext.js on Cloudflare, and styling patterns
- Backend Architecture — Cloudflare Workers, Hono routing, all four Durable Objects, queue processing, and webhook handling
- Compositor and Recording — The recording pipeline: CloudcasterWorkflow, Coordinator lock management, Mux streaming, LiveKit web egress, composite page layouts, and participant clip generation
- Database Architecture — Two-database design with D1 and Drizzle ORM, migration workflows, and schema conventions
- Real-Time Architecture — LiveKit video, Twilio chat, PartyKit presence, recording pipeline, and connection lifecycle
- Infrastructure and Deployment — Terraform provisioning, environment types, Cloudflare tunnels, secrets management, and Docker containers
System Architecture Diagram
graph TB
subgraph "Client Layer"
Browser["Browser
Next.js 15 App"]
Mobile["Mobile Clients
(Future)"]
end
subgraph "Application Layer - Next.js"
AppRouter["App Router
File-based Routing"]
ServerComponents["Server Components
Data Fetching"]
ClientComponents["Client Components
Interactivity"]
ServerActions["Server Actions
Form Handling"]
tRPCServer["tRPC Server
Type-safe API"]
end
subgraph "Authentication"
NextAuth["NextAuth
Multiple Providers"]
GoogleAuth["Google OAuth"]
OTPAuth["OTP/SMS Auth"]
end
subgraph "Data Layer"
DrizzleORM["Drizzle ORM
Type-safe Queries"]
D1Database["Cloudflare D1
SQLite Database"]
R2Storage["Cloudflare R2
Object Storage"]
KVStore["Cloudflare KV
Metadata Store"]
end
subgraph "Real-time Services"
LiveKit["LiveKit
Video/Audio"]
Twilio["Twilio
Messaging"]
PartyKit["PartyKit
WebSockets"]
end
subgraph "Worker Services"
CFWorkers["Cloudflare Workers
Edge Computing"]
DurableObjects["Durable Objects
Stateful Logic"]
Queues["Cloudflare Queues
Event Processing"]
end
Browser --> AppRouter
Mobile --> tRPCServer
AppRouter --> ServerComponents
AppRouter --> ClientComponents
ServerComponents --> tRPCServer
ClientComponents --> tRPCServer
ClientComponents --> ServerActions
tRPCServer --> NextAuth
NextAuth --> GoogleAuth
NextAuth --> OTPAuth
tRPCServer --> DrizzleORM
ServerActions --> DrizzleORM
DrizzleORM --> D1Database
ServerComponents --> R2Storage
ServerActions --> KVStore
ClientComponents --> LiveKit
ClientComponents --> Twilio
ClientComponents --> PartyKit
tRPCServer --> CFWorkers
CFWorkers --> DurableObjects
DurableObjects --> Queues
Request Flow Diagrams
Server Component Data Flow
sequenceDiagram
participant Browser
participant NextJS as Next.js Server
participant tRPC as tRPC Router
participant DB as D1 Database
Browser->>NextJS: Request Page
NextJS->>NextJS: Render Server Component
NextJS->>tRPC: api.event.getMyEvents()
tRPC->>DB: Query Events
DB-->>tRPC: Event Data
tRPC-->>NextJS: Typed Response
NextJS->>NextJS: Render HTML with Data
NextJS-->>Browser: HTML Response
Note over Browser: Hydration occurs
Client Component Interaction Flow
sequenceDiagram
participant User
participant ClientComp as Client Component
participant tRPCClient as tRPC Client
participant NextAPI as Next.js API Route
participant tRPCServer as tRPC Server
participant DB as Database
User->>ClientComp: Click "Create Event"
ClientComp->>tRPCClient: mutation.mutate()
tRPCClient->>NextAPI: POST /api/trpc
NextAPI->>tRPCServer: Execute Procedure
tRPCServer->>tRPCServer: Validate Input (Zod)
tRPCServer->>DB: Insert Event
DB-->>tRPCServer: Success
tRPCServer-->>NextAPI: Typed Response
NextAPI-->>tRPCClient: JSON Response
tRPCClient->>ClientComp: Update UI
ClientComp->>User: Show Success
Server Action Flow
sequenceDiagram
participant Form as Form Component
participant ServerAction as Server Action
participant SafeAction as next-safe-action
participant Auth as NextAuth
participant DB as Database
Form->>ServerAction: Submit Form Data
ServerAction->>SafeAction: Validate Schema
SafeAction->>Auth: Check Session
Auth-->>SafeAction: User Context
SafeAction->>ServerAction: Execute Action
ServerAction->>DB: Database Operation
DB-->>ServerAction: Result
ServerAction-->>Form: Return Result
Form->>Form: Update UI State
Key Architectural Decisions
| Decision | Choice | Why |
|---|---|---|
| Frontend framework | Next.js 15 with App Router | Server Components reduce client JS, streaming improves TTFB |
| API layer | tRPC | End-to-end type safety without code generation |
| Edge runtime | Cloudflare Workers | Global distribution, integrated D1/R2/KV services |
| Database | Cloudflare D1 (SQLite) | Co-located with Workers, no connection pooling needed |
| ORM | Drizzle | Type-safe SQL, lightweight, D1-compatible |
| Real-time video | LiveKit | Open-source WebRTC, scalable, self-hostable |
| Real-time chat | Twilio Conversations | Managed service, reliable message delivery |
| Real-time state | PartyKit (Durable Objects) | WebSocket with persistent state, Yjs CRDT support |
| Monorepo | pnpm workspaces | Shared code between webapp and workers, atomic changes |
| Infrastructure | Terraform | Per-environment isolation, reproducible setup |
Database Schema Overview
erDiagram
USERS ||--o{ USER_PROVIDERS : has
USERS ||--o{ SESSIONS : has
USERS ||--o{ ACCOUNT_OWNERSHIP : owns
ACCOUNTS ||--o{ ACCOUNT_OWNERSHIP : owned_by
ACCOUNTS ||--o{ EVENTS : hosts
EVENTS ||--o{ ROOMS : contains
EVENTS ||--o{ TICKETS : offers
ROOMS ||--o{ ROOM_ACCESS : grants
TICKETS ||--o{ ROOM_ACCESS : provides
USERS ||--o{ ROOM_ACCESS : has
EVENTS ||--o{ EVENT_PARTICIPANTS : has
ROOMS ||--o{ VET_CONVERSATIONS : manages
USERS {
string id PK
string email UK
string name
string displayName
timestamp createdAt
}
ACCOUNTS {
string id PK
string name
string logo
timestamp createdAt
}
EVENTS {
string id PK
string accountId FK
string name
string status
timestamp startsAt
timestamp endsAt
}
ROOMS {
string id PK
string eventId FK
string name
string livekitRoomName
boolean isMainRoom
}
TICKETS {
string id PK
string eventId FK
string name
boolean isUpgrade
string skuId
}
Deployment Architecture
graph LR
subgraph "Development"
LocalDev["Local Development
pnpm dev"]
LocalDB["Local D1 Database"]
LocalTunnel["Cloudflare Tunnel"]
end
subgraph "Git Worktrees"
Worktree1["Feature Branch 1"]
Worktree2["Feature Branch 2"]
IsolatedInfra1["Isolated Infrastructure"]
IsolatedInfra2["Isolated Infrastructure"]
end
subgraph "Preview Environment"
PreviewApp["Preview Deployment"]
PreviewDB["Preview D1 Database"]
PreviewWorkers["Preview Workers"]
end
subgraph "Production"
ProdApp["Production App
eventuall.live"]
ProdDB["Production D1"]
ProdWorkers["Production Workers"]
ProdCDN["Cloudflare CDN"]
end
LocalDev --> LocalDB
LocalDev --> LocalTunnel
Worktree1 --> IsolatedInfra1
Worktree2 --> IsolatedInfra2
PreviewApp --> PreviewDB
PreviewApp --> PreviewWorkers
ProdApp --> ProdDB
ProdApp --> ProdWorkers
ProdApp --> ProdCDN