This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A production Next.js 14 + TypeScript application that creates a public, mobile-friendly leaderboard site for Dawn of War: Definitive Edition. Live data from Relic's Community API is enriched with Steam player names and stored in Supabase for advanced features like player profiles, match history, and replay uploads.
Status: ✅ Production site deployed at https://www.dow-de.com
- Framework: Next.js 14 with TypeScript
- Database: Supabase (PostgreSQL with real-time subscriptions)
- Styling: Tailwind CSS
- APIs: Relic Community API + Steam API
- Deployment: Vercel
- Storage: Supabase Storage (replay files)
-
Leaderboards (
/)- All 37 faction-specific leaderboards (1v1-4v4)
- Combined 1v1 cross-faction rankings
- Real-time sortable tables with search
- Extended views (200/1000 players)
- Rank delta tracking (24h history)
-
Player Profiles (
/profile/[profileId])- Comprehensive stats across all factions/modes
- XP levels (calculated client-side from XP curve)
- Recent match history
- Rank history charts
- Steam profile integration
-
Replay System (Replays Tab)
- Upload
.recreplay files (50MB limit) - Download community replays
- Automatic parsing and metadata extraction
- Player matching to database profiles
- Rate limiting (20 uploads/hour per IP)
- Profanity filtering on descriptions
- Upload
-
Player Search
- Autocomplete search across all players
- Fuzzy matching via Supabase full-text search
- Direct profile navigation
- Country flag display
-
Advanced Statistics Teaser (Premium Feature Mockup)
- Modal UI showcasing upcoming premium features
- Price point survey ($2.99/month vs $4.99/month)
- Email collection for launch notification
- Lead capture in
premium_interest_leadstable - Toggleable via
NEXT_PUBLIC_ENABLE_PREMIUM_TEASERenv var - Not yet implemented - mockup only for market validation
-
Data Collection Scripts
seed:leaderboards- Initial snapshot of all leaderboardsenrich:players- Steam ID/level/XP enrichmentcrawl:concurrent- Match history collectioncrawl:watch- Progress monitoringcrawl:cleanup- Stuck job recovery
Relic Community API (https://dow-api.reliclink.com)
GetAvailableLeaderboards- 37 leaderboards (cached 24h)getLeaderBoard2- Player rankings (cached 5 min)proxysteamuserrequest- Steam name resolution
Supabase Database
players- All discovered players with enrichmentleaderboards- Metadata for 37 laddersleaderboard_snapshots- Daily rank historymatches- Match history with player performancereplays- Uploaded replay metadatareplay_player_links- Match replays to profilespremium_interest_leads- Market research for premium tier
// Ladder row (API + enrichment)
type LadderRow = {
rank: number
profileId: string
playerName: string
rating: number
wins: number
losses: number
winrate: number // (wins / (wins + losses)) * 100
streak: number
country?: string
lastMatchDate?: Date
faction?: string
level?: number
rankDelta?: number | null // 24h change
originalRank?: number // for multi-faction views
leaderboardId?: number
}
// Player (database)
type Player = {
profile_id: string
current_alias: string | null
country: string | null
steam_id64: string | null
level: number | null
xp: number | null
first_seen_at: string
last_seen_at: string
}Leaderboards
/api/leaderboards- Available leaderboards (24h cache)/api/cache/leaderboard/[id]- Top-200 ladder (5 min cache)/api/cache/leaderboard/[id]/[limit]- Extended ladder up to 1000/api/cache/combined-1v1- Combined 1v1 deduplicated by player/api/cache/combined-1v1/[limit]- Extended combined (up to 500/faction)/api/cache/combined-1v1-multi- All faction placements (players may appear multiple times)/api/cache/combined-1v1-multi/[limit]- Extended multi-entry view
Players
/api/players/search- Autocomplete search via Supabase/api/players/profile- Profile + stats + recent matches/api/cache/player/by-alias/[alias]- Player lookup by exact alias
Replays
/api/replays- GET: list replays, POST: upload replay- Handles file parsing, player matching, storage upload
Other
/api/steam/players- Current player count (cached from Supabase edge function)/api/log-favorite- Analytics logging for favorite leaderboards/api/premium-interest- Premium feature interest tracking (advanced stats teaser)
Core Logic
src/lib/relic.ts- Relic API wrapper with retry logicsrc/lib/supabase.ts- Supabase client setupsrc/lib/xp-levels.ts- XP to level calculation (fixes API bug)src/lib/rank-history.ts- Rank delta computationsrc/lib/replay-player-matching.ts- Replay parsing + player matchingsrc/lib/mapMetadata.ts- Map names/metadata
UI Components
src/app/page.tsx- Main leaderboard view (large file ~42k tokens)src/app/profile/[profileId]/page.tsx- Player profilesrc/app/_components/ReplaysTab.tsx- Replay upload/download UIsrc/components/AutocompleteSearch.tsx- Player search widgetsrc/components/ClickablePlayer.tsx- Player name links
Scripts (see scripts/)
- Data seeding, enrichment, crawling utilities
- All use
.envfor Supabase credentials
- Leaderboard list: 24h (
force-cache) - Ladder data: 5 min (
revalidate: 300for cached routes) - Player profiles: Real-time from Supabase
- Steam names: 120ms throttle between batch calls (25 IDs/chunk)
- Relic API: Soft limit ≤8 req/s (50 req/s max)
- Replay uploads: 20/hour per IP (hashed for privacy)
- Download tracking: Deduplicated by IP per replay
- Always pass
title=dow1-deto Relic API - Join stats to statGroups by
statgroup_id, not by array position - Use community endpoints only (no game endpoints requiring sessions)
- Never hardcode leaderboard IDs - fetch from
GetAvailableLeaderboards
- Name resolution: ≥90% success rate via Steam API
- Response times: Sub-second for Top-200 with enrichment
- Batch processing: Chunked to respect rate limits
- Extended views (1000 players): ~3-5 seconds
- API failures: Return 502 with error message
- Missing data: Graceful fallback to "Unknown" for unresolved players
- Responsive UI: Loading states, error messages, stale data indicators
# Development
npm run dev # Start dev server (localhost:3000)
npm run build # Production build
npm run typecheck # TypeScript validationAlways run TypeScript checks before deploying:
npm run typecheckThis catches type errors that would cause build failures on Vercel.
Required for full functionality:
# Supabase (public - for client-side)
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
# Supabase (server-side - for scripts/admin routes)
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
# Feature flags
NEXT_PUBLIC_ENABLE_PREMIUM_TEASER=false # Enable advanced statistics teaser modal- Full leaderboard system with 37 ladders
- Combined 1v1 cross-faction rankings (deduplicated + multi-entry views)
- Player profiles with comprehensive stats
- Match history tracking
- Replay upload/download system
- Player search with autocomplete
- Rank delta tracking (24h history)
- XP level calculation
- Steam integration (player count badge, profile links)
- Rate limiting and profanity filtering
- Responsive mobile design
- Country flags
- Faction logos
- Advanced statistics teaser/mockup (market validation)
- Large UI file (
page.tsx~42k tokens) - use offset/limit when reading - Supabase migrations in
supabase/migrations/ - Edge function for Steam player count (
supabase/functions/steam-player-count/) - All dates stored as ISO strings or Unix timestamps
- Profile IDs are strings (Relic API returns numbers but we stringify)
- Relic API returns
level: 1for all players (bug) - we compute from XP locally - Steam name resolution ~90% success (some profiles private/missing)
- Combined 1v1 offers two views: deduplicated (best faction) and multi-entry (all factions)
- Replay parsing requires temp file creation (Node.js buffer → file → parser)