The left4craft.org website: Next.js (pages router), deployed to Cloudflare Workers via OpenNext.
- Hosting: Cloudflare Workers (
@opennextjs/cloudflare+wrangler). ISR pages (/shop,/punishments/*) are cached in theleft4web-inc-cacheR2 bucket. - Database: PlanetScale Postgres, reached through pgbouncer via a Hyperdrive binding. Holds
better-auth tables, Stripe customer/trial/event tables, the
job_queuetable, and the LiteBans data. - Auth: better-auth — Discord OAuth + email magic links (sent with SES v2 over
HTTP via
aws4fetch). Routes live under/api/auth/*. - Shop: Stripe Checkout. The webhook (
/api/stripe/webhook) records events idempotently instripe_eventsand enqueues rank commands intojob_queue, which the Left4Hub plugin polls withFOR UPDATE SKIP LOCKED. - Punishments: LiteBans tables queried directly (
utils/litebans.js);/api/punishments/checkserves the client-side ban search.
cp .dev.vars.example .dev.vars # fill in secrets
npm install
npm run dev # next dev with wrangler's local binding proxynext build needs DATABASE_URL, STRIPE_SECRET_KEY, and NEXT_PUBLIC_* set to fully pre-render;
without them the ISR pages build empty and self-heal at runtime.
npm run preview # build + run the real worker locally
npm run deploy # build + deploy to CloudflareOne-time setup: create the R2 bucket (npx wrangler r2 bucket create left4web-inc-cache), apply
migrations/*.sql to Postgres, and set secrets with npx wrangler secret put (see .dev.vars.example
for the list).
Biome handles linting and formatting: npm run lint / npm run lint:fix.