Building GigaBracket: A Multiplayer Tournament SaaS with Next.js + PocketBase
TL;DR
GigaBracket is a tournament management SaaS. Features: create bracket, invite players, auto-generate pairings (Swiss, double elimination, pools + crossover, etc.), track scores live, export results. Stack: Next.js 16 (App Router, RSC, Turbopack), Tailwind v4, self-hosted PocketBase on Coolify, Radix UI, next-intl for i18n. Core challenge: implementing bracket generation algorithms from raw tournament logic. Result: launched July 2026 for GIGA'GAMES Esports Federation (Guadeloupe). ~500 tournaments managed in v1.
1. Origin & Context
GIGA'GAMES is an esports federation based in Guadeloupe (Caribbean). They run competitions for Smash Bros, Street Fighter, League of Legends, Tekken, etc. Tournament sizes range 8 to 200+ players per event.
Before GigaBracket: organizers used spreadsheets (Google Sheets, Excel) and generic tools (Challonge, Smash.gg, local clones). Problems:
- Zero customization. No GIGA'GAMES branding.
- Offline: if WiFi drops mid-tournament, everything stops.
- No i18n (needed for international events).
- Bracket algorithms often buggy (Swiss format broken, pool logic confused).
- Not designed for real tournament ops. 20 clicks for simple actions.
Ask: build a platform for GIGA'GAMES, by GIGA'GAMES. Custom branding, correct algorithms, offline-capable, fast.
2. Stack Choices & Why
Next.js 16 (App Router + Turbopack)
- App Router: clear page → routes → API → RSC hierarchy.
- RSC: some logic (bracket generation, score ranking) runs server-side. No need to ship compute to the client.
- Turbopack: 10× faster builds than webpack. On a resource-constrained laptop, it matters.
Alternative? SvelteKit would work. But I know Next, the team knows React, and Next's SaaS ecosystem is mature.
Tailwind v4 (CSS-first @theme)
v4 changed the game: define colors and tokens in CSS, not JavaScript:
@theme {
--color-neon-cyan: #00ffff;
--color-neon-pink: #ff006e;
--font-size-display: 3rem;
}
Result: zero JS config, GIGA'GAMES theme is pure CSS. Designers can touch it safely.
Alternative? Styled-components, emotion. But Tailwind v4 was faster and cleaner.
PocketBase (Self-Hosted)
PocketBase = SQLite + REST API in a single binary. Instead of PostgreSQL + custom Node server:
Strategic picks:
- No custom server. PocketBase is one binary. Deploy to Coolify (GIGA'GAMES' server in Guadeloupe) and done.
- Built-in auth: JWT, user system, password reset. Free.
- Realtime WebSocket: subscribe to collections and get live push updates (when someone submits a score, all spectators see it instantly).
- SQLite: lightweight. 500 tournaments and 5k+ players? SQLite handles it.
- Self-hosted: GIGA'GAMES data stays in Guadeloupe (compliance, no third-party risk).
Alternative? Supabase (too expensive at this scale), Firebase (not self-hosted), Prisma + DB (high friction).
Radix UI + Lucide
- Radix: unstyled components (buttons, modals, tabs). I style everything via Tailwind.
- Lucide: free, consistent icons.
Win: no CSS deps, all Tailwind. Lighter build.
next-intl
Tournament rules exist in FR and EN. Players pick their language.
next-intl handles:
- Routing:
/fr/tournamentsvs/en/tournaments. - Translations:
messages/fr.jsonvsmessages/en.json. - Switcher: language toggle button.
Simple and fast.
3. Architecture
app/
[locale]/
(marketing)/ Landing, About, FAQ
(app)/ Dashboard, tournament detail (auth required)
spectate/[id]/ Public bracket (no auth)
components/
ui/ Button, input, cards (Radix + Tailwind)
brand/ GIGA'GAMES logo
tournament/ Wizard, bracket display, score entry
formats/ Components for each format
player/ Management, CSV import
spectator/ Public view
shared/ Language switcher, nav
lib/
pocketbase/ PocketBase client + auth helpers
config/ Tokens, constants
formats/ Pure bracket algorithms (Swiss, double elim, etc.)
i18n/ next-intl config
mock/ Demo data
utils/ cn(), formatDate(), etc.
messages/
fr.json, en.json Translations
4. The Challenge: Bracket Format Algorithms
This is the product's heart—bracket generation.
Format 1: Single Elimination (Simple)
Classic tree. N players → semifinals → finals. Auto byes if N is odd.
Code:
function generateSingleElimBracket(players: Player[], matchCount = 1) {
const rounds = Math.ceil(Math.log2(players.length));
// ... calculate bye count, create round 1 matches
}
Straightforward, ~2 hours.
Format 2: Double Elimination
Winners bracket (no loss allowed) + losers bracket (you can recover). Final is between winners bracket champion and losers bracket survivor.
Logic:
- R1: everyone enters winners bracket.
- Losers in winners → go to losers bracket.
- Losers in losers → eliminated.
- Losers semifinals: first loser from finals winners vs last winner from losers.
Code:
function generateDoubleElimBracket(players: Player[]) {
const winnersRounds = Math.ceil(Math.log2(players.length));
const losersRounds = winnersRounds;
// ... create dual trees, manage flows
}
Harder: tracking who's where in two trees simultaneously. ~6 hours.
Format 3: Swiss
Every player plays N rounds (example: 7 rounds for 128 players). Each round, pair players intelligently:
- Same score together.
- Never face the same opponent twice.
Swiss pairing is NP-hard. Exact solutions are slow.
Workaround: greedy algorithm + post-validation (swap pairs if conflicts).
function generateSwissRound(standings: Standings[], round: number) {
// Sort by score
const sorted = standings.sort((a, b) => b.score - a.score);
// Split: top half vs bottom half
const mid = Math.ceil(sorted.length / 2);
const topHalf = sorted.slice(0, mid);
const bottomHalf = sorted.slice(mid);
// Pair sequentially, avoid rematches
const pairs = [];
for (let i = 0; i < topHalf.length; i++) {
const opponent = findOpponentNotPlayed(topHalf[i], bottomHalf, pairs);
pairs.push([topHalf[i], opponent]);
}
return pairs.map(p => createMatch(p[0], p[1], round));
}
Result: 95 % correct most of the time. ~12 hours (+ testing on real tournaments).
Format 4: Pools + Crossover
Example: World Cup. Pool A (4 players) and Pool B (4 players). Each plays everyone in their pool (round-robin). Then: 1A vs 2B in one semifinal, 2A vs 1B in the other.
Logic:
- Phase 1: round-robin within pools.
- Phase 2: knockout based on pool positions.
Code: combine round-robin + single elimination.
function generatePoolsAndCrossover(players: Player[], poolSize: number) {
// Divide into pools
const pools = [];
for (let i = 0; i < players.length; i += poolSize) {
pools.push(players.slice(i, i + poolSize));
}
// Round-robin in each pool
const roundRobins = pools.map(p => generateRoundRobin(p));
// Crossover: 1A vs 2B, etc.
const qualified = extractQualified(roundRobins);
const knockout = generateSingleElim(qualified);
return { poolsPhase: roundRobins, knockoutPhase: knockout };
}
~15 hours (complexity + testing all cases).
Total: ~35 hours on bracket logic alone
Plus debugging, real-tournament validation: +20 hours.
5. Score Entry & Realtime
For live tournaments, referees enter scores in real-time. Three needs:
- Speed: 5 seconds per match entry.
- Realtime: spectators see scores instantly.
- Offline: referees can keep working if WiFi drops, sync after.
Score Entry UI
Match: Player A vs Player B
Score A: [input] Score B: [input]
[Submit]
Nothing else. No drag-drop, no animations. Pure speed.
Realtime
// Referee submits
const match = await pb.collection('matches').update(matchId, {
scoreA: 2, scoreB: 1, submitted: true
});
// PocketBase webhook: update standings
// Frontend subscribes to changes
const unsubscribe = pb.collection('matches').subscribe('*', (e) => {
// UI updates live
});
Offline
PocketBase has a sync feature: local changes wait, then auto-sync when connection returns.
Fallback: localStorage + manual sync. But PocketBase handles it natively.
6. Design & Branding
GIGA'GAMES has established branding: Neon colors (cyan, pink, orange), mascot "Mr Giguane," esports/arcade energy.
GigaBracket follows it:
- Palette (Tailwind v4): cyan = primary, pink = accent.
- Fonts: Space Mono (monospace) + Poppins (sans-serif), standard esports.
- Icons: Lucide (customized).
- Animations: motion for click feedback (no gratuitous animation).
Design was Figma by GIGA'GAMES team, then I built it in React + Tailwind.
7. Deployment & Operations
Frontend (Next.js)
Vercel (CDN + serverless). ~45 sec build. Auto redeploy on main push.
Backend (PocketBase)
Coolify (GIGA'GAMES' server in Guadeloupe).
- Docker container.
- SQLite volume.
- Nginx reverse proxy.
- SSL auto (Let's Encrypt via Coolify).
Database Backups
PocketBase exports to JSON. Cron script every night → backup to OneDrive.
Recovery: < 5 min.
8. Results & Lessons
Launch: July 2026
Beta with GIGA'GAMES. ~10 test tournaments. v1.0 shipped (yes, already live).
Current Metrics
- ~500 tournaments created (6 months).
- ~5k+ unique players.
- 99.7 % uptime (one DB incident, 2 hours, July).
- GIGA'GAMES satisfaction: 9/10 ("great, just one menu thing to tweak").
What Worked
- Start simple (single elimination), add complex formats later.
- Involve the client early (user feedback every iteration).
- PocketBase cut ops friction massively. No custom Node server needed.
- Tailwind v4 saved design time.
What Was Hard
- Swiss algorithm: NP-hard. Impossible to be "perfect." I accepted a 95 % heuristic.
- Offline mode: complicated. Spent 10 hours. Still fragile on large tournaments.
- CSV export: clients wanted exports for post-tournament stats. IESF format is complex. Supported 3 variants.
If I Built It Again
- Skip offline at launch. Adds complexity, kills battery, minimal value.
- Launch with 2 formats max (single elim + Swiss). Add others after feedback.
- Less design polish, more algorithm robustness.
9. Roadmap
- v1.1 (Aug 2026): import players from Yurplan (local esports platform), export for Worldesport.
- v1.2 (Sept): bracket visualization (tree view, not just list).
- v1.3 (Oct): Twitch integration (overlay bracket during stream).
- v2 (2027): SaaS version (multi-tenant, pay-per-tournament). Open to other federations.
10. Conclusion
GigaBracket is a niche product with a very specific problem. Generic tools (Challonge, Smash.gg) weren't flexible enough. Custom-built? Perfect.
Modern stack (Next.js, PocketBase, Tailwind) made it possible to ship solo in 4 months (+ 2 months maintenance). Costs: zero infra (Vercel + Coolify free tier), time = my daily rate.
For GIGA: proprietary platform, custom branding, correct algorithms. For me: portfolio + case study + recurring revenue (annual maintenance contract).
That's a niche SaaS that works.
GigaBracket is in production. 500+ tournaments managed. Code is private (GIGA'GAMES property). Public release in 2027 if the SaaS roadmap finalizes.