ENGINEERING & ARCHITECTURE

Behind the Code

A technical deep dive into how GeoGuessr Thai League was built using Next.js 16, Supabase Realtime, and Tailwind CSS.

The Tech Stack
Next.js 16

App Router, Server Actions & SSR

Supabase

PostgreSQL & Realtime Subscriptions

React 19

Hooks, Suspense & Optimistic UI

Tailwind CSS

Utility-First Responsive Styling

Architecture & Decisions
Real-time Data Sync

ใช้ Supabase Realtime Channels เพื่อรับ PostgreSQL changes (`INSERT`, `UPDATE`) แบบ real-time โดยไม่ต้อง polling

.channel('realtime-matches')
.on('postgres_changes', { event: '*', table: 'matches' }, (payload) => updateState(payload))
.subscribe();
URL-Driven State (Deep Linking)

Modals ถูกควบคุมผ่าน URL Search Params เช่น ?match=123 หรือ ?player=John ทำให้สามารถแชร์ลิงก์ตรงไปยัง Match/Player ได้ และปุ่ม Back ของ Browser ปิด Modal ได้

const openMatchDetails = (m) => 
  router.push(`${pathname}?match=${m.id}`, { scroll: false });
Client-side Optimization

การคำนวณ Stats ต่างๆ (Leaderboard, Player Radar, First Guess Rate) ใช้ useMemo Hooks เพื่อลด Re-render และเพิ่มความเร็ว UI

const stats = useMemo(() => 
  calculatePlayerStats(playerName, matches), 
  [matches, playerName]);
JSONB for Flexibility

ข้อมูล GeoGuessr API มีความซับซ้อน แทนที่จะสร้าง 10+ tables เราเก็บ Game Payload ทั้งหมดใน JSONB column (match_details) แล้วประมวลผลฝั่ง Client

matches table
├── id: uuid
├── status: 'finished'
├── score_p1, score_p2
└── match_details: [JSONB Array]
Features Implemented
Spoiler-Free Mode
ซ่อนผลการแข่งขันสำหรับผู้ที่ยังไม่อยากรู้ผล
Player Search
ค้นหา Match ด้วยชื่อผู้เล่นใน All Matches Schedule
Top Scorers by Mode
แสดง Top 5 คะแนนเฉลี่ยสูงสุดใน 4 โหมด (Overall, Move, NM, NMPZ)
Player Avatars
แสดง Avatar ผู้เล่นจริงจาก GeoGuessr หรือ Initials fallback
Playoffs Bracket
หน้า Playoffs แสดง Double Elimination Bracket พร้อม Offline Match Highlight
Admin Dashboard
จัดการ Players, Schedule, Matches, Playoffs และ Match Sandbox Preview
Match Details Modal
แสดงรายละเอียด 3 Games พร้อม Round-by-Round Data, Maps, Scores
Countdown Timer
แสดงเวลานับถอยหลังสำหรับ Match ที่กำลังจะเกิดขึ้น
Data Flow
GeoGuessr API
Game Data Source
Supabase
PostgreSQL + Realtime
Next.js Server
SSR + API Routes
React Client
Realtime UI Updates
Database Schema
📦 Supabase PostgreSQL
│
├── 👥 players
│   ├── id (uuid, PK)
│   ├── name (text)
│   ├── group_name ('A' | 'B')
│   ├── province (text)
│   ├── avatar_url (text)
│   ├── rating, global_rank
│   └── mode_preferences, best_country, worst_country, quote
│
├── ⚔️ matches
│   ├── id (uuid, PK)
│   ├── player1_name, player2_name
│   ├── score_p1, score_p2
│   ├── status ('scheduled' | 'finished')
│   ├── match_time (timestamp)
│   ├── round_label (text) // for playoffs
│   ├── youtube_link (text)
│   └── match_details (jsonb[]) // 3 games array
│
└── 📜 legacy_matches
    ├── id, player1, player2
    └── result (สำหรับ Head-to-Head history)

Developed with 💙 by PadPrikGaengGai

GeoGuessr Thai League • Season 2 • 2026