Self-Hosting
Deploy your own instance of FangDash.
TL;DR - FangDash runs entirely on Cloudflare infrastructure (Workers + D1) - Three services to
deploy: API, Web, and PartyKit (multiplayer) - bun run ship deploys all three at once
FangDash is designed to run on Cloudflare's infrastructure. Here's how to deploy your own instance.
What You'll Need
- Node.js >= 24
- Bun 1.3.10
- Cloudflare account (free tier works)
- Twitch Developer application (for OAuth sign-in)
- PartyKit account (for real-time multiplayer)
Step 1 of 3 — Deploy the API
The API runs on Cloudflare Workers with a D1 database.
- Create a D1 database via the Cloudflare dashboard (Workers & Pages → D1) or CLI:
bunx wrangler d1 create fangdash-db- Update
apps/api/wrangler.tomlwith your database ID and production domains. The[[d1_databases]]block should look like:
[[d1_databases]]
binding = "DB"
database_name = "fangdash-db"
database_id = "your-database-id-here"
migrations_dir = "drizzle/"And the production environment variables:
[env.production.vars]
ENVIRONMENT = "production"
BETTER_AUTH_URL = "https://your-api-domain.workers.dev"
WEB_URL = "https://your-web-domain.workers.dev"- Run migrations against the remote database:
bunx wrangler d1 migrations apply fangdash-db --remote- Set your secrets:
bunx wrangler secret put BETTER_AUTH_SECRET --env production
bunx wrangler secret put TWITCH_CLIENT_ID --env production
bunx wrangler secret put TWITCH_CLIENT_SECRET --env productionGenerate a strong secret with: openssl rand -base64 32 — paste the output when prompted for
BETTER_AUTH_SECRET.
- Deploy:
bun run ship:apiStep 2 of 3 — Deploy the Web App
The web app deploys to Cloudflare Workers using OpenNext.
- Set your API URL as a build-time environment variable:
NEXT_PUBLIC_API_URL=https://your-api-domain.workers.dev bun run ship:webOr add it to apps/web/.env.local first:
NEXT_PUBLIC_API_URL=https://your-api-domain.workers.dev
NEXT_PUBLIC_PARTYKIT_HOST=fangdash.your-username.partykit.dev- Deploy:
bun run ship:webStep 3 of 3 — Deploy PartyKit
PartyKit handles real-time multiplayer WebSocket connections.
- Set your PartyKit token:
export PARTYKIT_TOKEN=your-token- Deploy:
bun run ship:partyAll three services are deployed! Your FangDash instance should now be live. Visit your web domain to verify everything works.
Deploy Everything at Once
bun run shipThis runs all three deploy commands in sequence.
Environment Variables Reference
API (apps/api)
| Variable | Type | Where set | Description |
|---|---|---|---|
BETTER_AUTH_SECRET | Secret | wrangler secret put | Long random string for signing sessions |
TWITCH_CLIENT_ID | Secret | wrangler secret put | Twitch OAuth app client ID |
TWITCH_CLIENT_SECRET | Secret | wrangler secret put | Twitch OAuth app client secret |
BETTER_AUTH_URL | Plaintext | wrangler.toml | Public URL of the API worker |
WEB_URL | Plaintext | wrangler.toml | Public URL of the web worker (for CORS + redirects) |
ENVIRONMENT | Plaintext | wrangler.toml | "development" or "production" |
Web (apps/web)
| Variable | Type | Where set | Description |
|---|---|---|---|
NEXT_PUBLIC_API_URL | Plaintext | .env.local / CI env | Public URL of the API worker |
NEXT_PUBLIC_PARTYKIT_HOST | Plaintext | .env.local / CI env | PartyKit host (e.g. fangdash.user.partykit.dev) |
Local Development
Copy the example files and fill in your values:
cp apps/api/.dev.vars.example apps/api/.dev.vars
cp apps/web/.env.example apps/web/.env.localSetting Up Twitch OAuth
Add both OAuth Redirect URLs: - Local development:
http://localhost:8787/api/auth/callback/twitch - Production:
https://your-api-domain.workers.dev/api/auth/callback/twitch
The Twitch OAuth redirect URL must match your deployed API domain exactly — including the
protocol (https://) and the path /api/auth/callback/twitch. If sign-in redirects to a blank
page or error, this is almost always the cause.
Troubleshooting
| Error / Symptom | Cause | Fix |
|---|---|---|
| OAuth redirects to blank page | Redirect URL mismatch in Twitch dev console | Ensure the redirect URL matches your API domain exactly, including protocol and /api/auth/callback/twitch path |
| "Auth not configured" in prod | Missing Cloudflare secrets | Run all wrangler secret put commands: BETTER_AUTH_SECRET, TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET |
| D1 "table not found" | Database migrations not applied | Run bunx wrangler d1 migrations apply fangdash-db --remote |
| PartyKit connection refused | Wrong host or missing token | Verify NEXT_PUBLIC_PARTYKIT_HOST matches your PartyKit deployment URL and PARTYKIT_TOKEN is set |
| CORS errors in browser console | WEB_URL mismatch | Update WEB_URL in wrangler.toml [env.production.vars] to match your actual web domain exactly |
| Admin panel shows "Forbidden" | User role is not admin or dev | Run bun run promote-admin YourUsername --remote (see Admin docs) |
| Multiplayer rooms won't start | PartyKit not deployed | Run bun run ship:party and verify the deployment URL |
| Score submission fails silently | Rate limit hit (429) | Wait 60 seconds and retry, or check the response for a 429 status and Retry-After header |
| "FORBIDDEN" on all actions | Account is banned | Check the user's ban status in the admin panel or D1 database |
| Build fails with type errors | Dependencies out of date | Run bun install then bun run typecheck to identify issues |
PartyKit Deployment Details
To expand on Step 3 above:
-
Get a PartyKit token: Sign up at partykit.io, go to your account settings, and generate an API token.
-
Set the token:
export PARTYKIT_TOKEN=your-token-here- Deploy:
bun run ship:party- Verify deployment: After deploying, PartyKit will output your host URL. It looks like:
fangdash.your-username.partykit.dev-
Update your web app: Set
NEXT_PUBLIC_PARTYKIT_HOSTinapps/web/.env.localto this URL (withouthttps://prefix). Redeploy the web app for the change to take effect. -
Test the connection: Open your web app, start a multiplayer race, and check the browser console for WebSocket connection messages.