Complete guide for setting up the Supabase database, configuring API keys, running the ingestion pipeline, and deploying to Vercel + Heroku.
- Node.js 18+ and npm 9+
- A Supabase project (free tier works)
- A RAWG API key (free, 20K requests/month)
- (Optional) Twitch/IGDB credentials (free, 4 req/sec) — enriches games with IGDB ratings, trailers, screenshots
- (Optional) Steam Web API key — enriches games with player counts, review stats, pricing
- Go to supabase.com and create a new project
- Note your credentials from Settings → API:
- Project URL →
NEXT_PUBLIC_SUPABASE_URL - anon (public) key →
NEXT_PUBLIC_SUPABASE_ANON_KEY - service_role (secret) key →
SUPABASE_SERVICE_ROLE_KEY
- Project URL →
- Open Supabase Dashboard → SQL Editor
- Run every SQL file in
supabase/migrationsin lexical order, starting with000_initial_schema.sql - Treat
supabase/schema.sqlas a derived reference snapshot, not the bootstrap source of truth
node scripts/apply-schema.mjscp .env.example .env.local| Variable | Where to get it |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase → Settings → API |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase → Settings → API |
SUPABASE_SERVICE_ROLE_KEY |
Supabase → Settings → API (keep secret!) |
RAWG_API_KEY |
https://rawg.io/apidocs |
| Variable | Where to get it | What it enables |
|---|---|---|
TWITCH_CLIENT_ID |
https://dev.twitch.tv/console | IGDB ratings, trailers, screenshots |
TWITCH_CLIENT_SECRET |
https://dev.twitch.tv/console | IGDB authentication |
STEAM_API_KEY |
https://steamcommunity.com/dev/apikey | Steam reviews, player counts, pricing |
CRON_SECRET |
Any random string | Protects cron endpoints from unauthorized calls |
NEXT_PUBLIC_SITE_URL |
Your deployment URL | SEO, sitemap, OG images |
SUPABASE_AVATAR_BUCKET |
Default: avatars |
Storage bucket for user avatar uploads |
ADMIN_EMAILS |
Comma-separated emails | Admin access control (parsed by src/lib/adminEmails.ts) |
DATABASE_URL |
Supabase → Settings → Database | Direct Postgres connection for Heroku scripts |
SOURCE_DATE_EPOCH |
Unix timestamp of last deploy | Reproducible sitemap lastmod dates |
VERDICT_SITEMAP_LASTMOD |
ISO date string | Explicit sitemap last-modified override |
npm install
npm run devThe app starts at http://localhost:3000.
Without Supabase configured, all API routes return empty arrays gracefully — the frontend renders but shows no data.
# Ingest ~300 curated games from RAWG (takes ~10 min)
node scripts/ingest-full-library.mjs
# Set trending/featured flags
node scripts/seed-flags.mjs
# Create 22 system-curated editorial lists
node scripts/seed-curated-lists.mjscurl -X POST http://localhost:3000/api/ingest/game \
-H "Content-Type: application/json" \
-d '{"query": "Hades"}'Expected response:
{
"success": true,
"data": {
"gameId": "uuid-here",
"slug": "hades",
"message": "Game \"Hades\" ingested successfully.",
"alreadyExisted": false
}
}Visit http://localhost:3000/game/hades to see the result.
curl -X POST http://localhost:3000/api/ingest/batch \
-H "Content-Type: application/json" \
-d '{"queries": ["Hades", "Elden Ring", "Stardew Valley", "Hollow Knight"]}'# From browser console (must be logged in as admin):
fetch('/api/admin/backfill-scores', { method: 'POST' }).then(r => r.json()).then(console.log)node scripts/backfill-mobile-listings.mjs --limit=50 --dry-run # preview
node scripts/backfill-mobile-listings.mjs # full run
node scripts/backfill-mobile-listings.mjs --android-only # Android only
node scripts/backfill-mobile-listings.mjs --ios-only # iOS only| Method | Route | Description |
|---|---|---|
| GET | /api/homepage |
Homepage aggregator (all sections in one call, ISR 60s) |
| GET | /api/games/trending |
Trending games |
| GET | /api/games/new-releases |
Newest releases |
| GET | /api/games/top-rated |
Highest-scored games |
| GET | /api/games/stats |
Site-wide statistics |
| GET | /api/games/[slug] |
Single game detail |
| GET | /api/games/[slug]/reviews |
Reviews for a game |
| GET | /api/games/[slug]/deals |
Price deals |
| GET | /api/games/[slug]/news |
Latest Steam news/patch notes |
| GET | /api/games/[slug]/achievements |
Steam achievement stats |
| GET | /api/games/[slug]/steam-reviews |
Steam player reviews (cached 24h) |
| GET | /api/games/[slug]/editorial |
Editorial reviews for a game |
| GET | /api/games/[slug]/related |
Related games (genre/tag/dev scoring) |
| GET | /api/games/[slug]/system-requirements |
PC system requirements from Steam |
| GET | /api/search?q=&platform=&genre=&sort=&page= |
Search with filters |
| GET | /api/recommendations?limit= |
Personalized recommendations |
| GET | /api/rawg/lists?type=&page=&genre= |
RAWG curated lists proxy |
| GET | /api/calendar?month=YYYY-MM |
Release calendar |
| GET | /api/compare?slugs=slug1,slug2 |
Side-by-side game comparison |
| GET | /api/developers/[slug] |
Developer hub |
| GET | /api/reviews?sort=&platform=&page= |
Global review feed |
| POST | /api/reviews |
Submit a review (auth required) |
| GET | /api/reviews/[id]/comments |
Review comments |
| POST | /api/reviews/[id]/comments |
Add a comment (auth required) |
| POST | /api/reviews/[id]/vote |
Vote on a review (auth required) |
| GET | /api/lists |
All curated lists |
| GET | /api/lists/[slug] |
Single list with games |
| GET | /api/profile/[username] |
User profile |
| GET | /api/profile/[username]/reviews |
User's reviews |
| PATCH | /api/profile/settings |
Update profile fields (auth required) |
| POST | /api/profile/settings |
Upload avatar (auth required, multipart) |
| GET | /api/library |
User library (auth required) |
| POST | /api/library |
Add/update library entry (auth required) |
| DELETE | /api/library?gameId= |
Remove from library (auth required) |
| GET | /api/library/stats |
Library statistics (auth required) |
| POST | /api/follow |
Follow/unfollow user (auth required) |
| Method | Route | Description |
|---|---|---|
| GET | /api/auth/me |
Current authenticated user |
| GET | /api/auth/callback |
Supabase OAuth callback |
| POST | /api/auth/bootstrap |
Ensure profile row exists for auth user |
| GET | /api/auth/check-username |
Validate username availability and format |
| Method | Route | Description |
|---|---|---|
| POST | /api/ingest/game |
Ingest single game by title |
| POST | /api/ingest/batch |
Ingest up to 50 games |
| Method | Route | Description |
|---|---|---|
| GET | /api/cron/discover?secret= |
Auto-discover ~320 games |
| GET | /api/cron/discover?secret=&deep=true |
Deep discovery ~700+ games |
| GET | /api/cron/refresh-trending?secret= |
Update trending/featured flags |
| GET | /api/cron/re-enrich?secret= |
Re-enrich stale game data |
| Method | Route | Description |
|---|---|---|
| GET | /api/gx/highlights |
Hero carousel highlights |
| GET | /api/gx/calendar |
Release calendar |
| GET | /api/gx/free-to-play |
Free-to-play games |
| GET | /api/gx/top-games |
PS Plus / Game Pass titles |
| GET | /api/gx/deals |
Discounted games |
| GET | /api/gx/top-liked |
Most liked games |
| GET | /api/gx/news/popular |
Trending gaming news |
| GET | /api/gx/news/feed |
Full news feed |
| Method | Route | Description |
|---|---|---|
| GET | /api/admin/stats |
Dashboard statistics |
| GET | /api/admin/games |
Paginated game list with search |
| GET | /api/admin/games/[id] |
Single game for editing |
| PATCH | /api/admin/games/[id] |
Update game fields |
| POST | /api/admin/games/[id]/ingest |
Force re-ingest (Full/RAWG/IGDB) |
| DELETE | /api/admin/games/[id]/delete |
Permanently delete a game |
| GET | /api/admin/reviews |
List all reviews |
| POST | /api/admin/reviews |
Create editorial review |
| DELETE | /api/admin/reviews |
Delete a review |
| POST | /api/admin/featured |
Toggle featured/trending flags |
| GET | /api/admin/audit |
Last 50 audit log entries |
| GET | /api/admin/users |
User list with counts |
| POST | /api/admin/backfill-scores |
Recompute v2 scores for all games |
| POST | /api/admin/backfill-header-images |
Batch backfill missing header images |
| POST | /api/admin/backfill-prices |
Batch backfill missing prices |
| GET | /api/admin/editorial-reviews |
List editorial reviews |
| POST | /api/admin/editorial-reviews |
Create/update editorial review |
| PATCH/DELETE | /api/admin/editorial-reviews/[id] |
Update or delete editorial review |
| GET | /api/admin/games/search-preview |
Preview search results before ingestion |
| POST | /api/admin/optimize-image |
Server-side image optimization |
| GET | /api/admin/provider-usage |
API provider usage and budget status |
| GET | /api/admin/scheduler-runs |
Recent scheduler job executions |
| POST | /api/admin/scheduler-runs/trigger |
Manually trigger a scheduler job |
| POST | /api/admin/seed-lists |
Seed 22 editorial curated lists |
| GET | /api/admin/seed-lists |
Available seed list definitions |
| POST | /api/admin/upload |
Upload files to Supabase Storage |
src/
├── app/
│ ├── api/ # 70+ Next.js Route Handlers
│ │ ├── auth/ # OAuth callback, bootstrap, me
│ │ ├── homepage/ # Homepage aggregator
│ │ ├── games/ # Game CRUD + deals/news/achievements/steam-reviews
│ │ ├── search/ # Multi-filter search
│ │ ├── recommendations/ # Quality-gated recommendations
│ │ ├── rawg/lists/ # RAWG curated lists proxy
│ │ ├── reviews/ # Reviews + votes + comments
│ │ ├── lists/ # Curated lists
│ │ ├── profile/ # User profiles + settings + avatar
│ │ ├── library/ # User library CRUD + stats
│ │ ├── follow/ # Follow/unfollow
│ │ ├── calendar/ # Release calendar
│ │ ├── compare/ # Game comparison
│ │ ├── developers/ # Developer hub
│ │ ├── ingest/ # Single + batch ingestion
│ │ ├── cron/ # discover, refresh-trending, re-enrich
│ │ ├── gx/ # 8 GX Corner proxy routes
│ │ └── admin/ # Stats, games, reviews, users, audit, featured, backfill, seed-lists
│ ├── admin/ # Admin dashboard pages
│ ├── game/[slug]/ # Game detail (richest page)
│ ├── search/ # Search with 5 filter types
│ ├── explore/ # RAWG curated lists (5 tabs)
│ └── ... # Calendar, reviews, lists, compare, library, profile
├── lib/
│ ├── external/ # 9 API clients
│ │ ├── rawg.ts # RAWG (games, search, curated lists)
│ │ ├── steam.ts # Steam (reviews, players, pricing)
│ │ ├── igdb.ts # IGDB/Twitch (ratings, media, PopScore)
│ │ ├── cheapshark.ts # CheapShark (price deals)
│ │ ├── wikipedia.ts # Wikipedia (summaries)
│ │ ├── howlongtobeat.ts # HLTB (playtime data)
│ │ ├── gxcorner.ts # GX Corner (8 gaming feeds)
│ │ ├── googleplay.ts # Google Play Store (mobile verification)
│ │ └── appstore.ts # Apple App Store (mobile verification)
│ ├── services/
│ │ ├── ingest.ts # 13-step multi-source ingestion pipeline
│ │ ├── homepage.ts # Homepage data aggregator (~1900 lines)
│ │ ├── search.ts # Server-side search with composite ranking
│ │ ├── calendar.ts # Calendar service (GX + DB merging)
│ │ ├── relatedGames.ts # Related games algorithm
│ │ └── gx-feeds.ts # GX feed handlers with durable cache
│ ├── supabase/ # Database client + typed schema
│ ├── db/ # Row mappers, column selections
│ ├── utils/ # Scoring, quality, trending, media readiness, public safety
│ ├── auditLog.ts # Admin audit log writer
│ ├── admin.ts # Admin access control
│ ├── api.ts # Frontend API client
│ └── types.ts # Frontend interfaces
├── components/ # 48 React components
├── hooks/ # useAuth, useTheme
scripts/ # 35 Node.js CLI scripts
supabase/ # Schema + 32 ordered migrations
SUPABASE_SERVICE_ROLE_KEYis never exposed to the client — only used insrc/lib/supabase/server.ts, imported by Route HandlersRAWG_API_KEY,STEAM_API_KEY,TWITCH_CLIENT_ID/SECRETare server-only, used insrc/lib/external/*.ts- Cron routes protected by
CRON_SECRET(query param or Bearer header) - Admin routes protected by
requireAdmin()— email-list-based access control - All tables have Row Level Security (RLS) enabled — public
SELECT,service_role-only writes - Input validation: JSON body parsing, query length (2–200), batch size (max 50), avatar MIME whitelist + 2MB limit
- Security headers: X-Frame-Options DENY, X-Content-Type-Options nosniff, HSTS, Referrer-Policy, Permissions-Policy (in
next.config.ts)
- Push to GitHub
- Import into Vercel
- Add all environment variables in Vercel → Settings → Environment Variables
- Set
NEXT_PUBLIC_SITE_URLto your production URL (e.g.,https://www.verdict.games) - Deploy —
vercel.jsonauto-detectsnextjsframework and intentionally leaves cron schedules disabled
Heroku runs no web dyno — it is scheduler-only. The Procfile has no web: entry. All scripts run as one-off dynos via Heroku Scheduler and write directly to Postgres (no Vercel API calls needed).
- Create a Heroku app and push the same codebase
- Add environment variables via
heroku config:set:DATABASE_URL(orSUPABASE_DB_URL) — direct Postgres connection stringRAWG_API_KEY— required for discovery/enrichmentTWITCH_CLIENT_ID,TWITCH_CLIENT_SECRET— optional, for IGDB enrichment
- Install Heroku Scheduler add-on:
heroku addons:create scheduler:standard - Configure scheduled jobs:
| Job | Frequency | Command |
|---|---|---|
| Refresh trending | Hourly | node scripts/heroku-refresh-trending.mjs |
| Discover games | Daily | node scripts/heroku-discover-games.mjs |
| Deep discovery | Weekly | node scripts/heroku-discover-games.mjs --deep |
| Re-enrich stale | Hourly | node scripts/heroku-re-enrich.mjs |
| Refresh curated lists | Daily | node scripts/seed-curated-lists.mjs |
All scripts use scripts/lib/ingest-pipeline.mjs for direct DB writes via the postgres tagged-template library.
The database supports games across all 11 platforms:
- PC, macOS, Linux
- PlayStation 5, PlayStation 4
- Xbox Series X|S, Xbox One
- Nintendo Switch, Nintendo Switch 2
- Android, iOS
Admin access is controlled by the ADMIN_EMAILS environment variable (comma-separated list, parsed by src/lib/adminEmails.ts). To add an admin:
- Add their Supabase Auth email to the
ADMIN_EMAILSenv var (comma-separated) - Redeploy (or update the env var in Vercel/Heroku settings)
Admins can access /admin for:
- Game management — search, edit, reingest (RAWG/IGDB/Full Pipeline), delete
- Review moderation — create editorial reviews, delete inappropriate reviews
- User management — view users with review/list/library counts
- Audit log — field-level diffs of all admin actions
- Content seeding — seed 22 editorial lists, backfill v2 scores, backfill header images/prices
- Featured/trending — manual override flags that persist through cron refreshes
- API provider usage — monitor external API usage and budgets
- Scheduler runs — view recent job executions, manually trigger jobs