As developers, we often over-engineer. We reach for React, a Node.js backend, and a PostgreSQL database before we even define the problem. For my latest project—a Counter-Strike 2 Leaderboard—I decided to go the opposite way: Lean, Mean, and Blazing Fast.
I wanted to build a platform that tracks player stats, rankings, and match history without paying a dime for hosting or managing a heavy backend.
🚀 Live Demo: ibrahimsezer.github.io/cs2-leaderboard
💻 Source Code: github.com/ibrahimsezer/cs2-leaderboard
🧐 The Challenge: Dynamic Data on a Static Budget
Leaderboards are inherently dynamic. Ratings change after every match. Usually, this requires a database. However, I wanted to leverage GitHub Pages for hosting.
The solution? Treating my repository as a database using JSON and automating the "write" process with GitHub Actions.
🛠️ The Tech Stack
- Frontend: (React + Tailwind CSS + Vite).
- Backend : Python for data parse.
- Steam WEB API
- steam documents : Check it out
- IDE : Antigravity
- Data Storage: Structured data.json files.
- Automation: GitHub Actions for scheduled data fetching.
- Styling: Custom CSS with a "Steam/Valve" inspired aesthetic.
🎨 UI/UX: Designed for Gamers
I wanted the leaderboard to feel like an extension of the CS2 game menu. This meant high-contrast typography, dark themes, and subtle gradients.
Responsive Design
Gamers check stats on their phones during match breaks. I used a mobile-first approach to ensure the tables scale beautifully on any device.
⚙️ How It Works: The "Static-Dynamic" Loop
The magic happens in three steps:
- The Data Fetcher Instead of the client (the browser) calling heavy APIs, a GitHub Action runs on a schedule (Cron job). It fetches the latest player data and updates the leaderboard.json file in the repo.
for workflow update-stats.yml
name: Update Stats & Deploy to Pages
on:
schedule:
#- cron: '0 */12 * * *'
- cron: '0 */6 * * *'
#- cron: '0 0 * * *' # Her gece çalışır
workflow_dispatch: # Manuel tetikleme
push:
branches: ["main"] # Kod değişikliği yaparsan da tetiklensin
permissions:
contents: write
pages: write # GitHub Pages'e yazma izni
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# 1. GÖREV: Verileri Güncelle ve Build Al
build-and-update:
runs-on: ubuntu-latest
steps:
- name: Depoyu Çek
uses: actions/checkout@v4
# --- PYTHON KISMI (Veri Çekme) ---
- name: Python Kurulumu
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Python Paketlerini Yükle
run: pip install requests python-dotenv
- name: İstatistik Scriptini Çalıştır
env:
API_KEY: ${{ secrets.STEAM_API_KEY }}
run: python scripts/data-parse.py
# --- REACT KISMI (Build) ---
- name: Node.js Kurulumu
uses: actions/setup-node@v4
with:
node-version: 18
- name: NPM Paketlerini Yükle
run: npm ci
- name: React Projesini Derle (Build)
run: npm run build
# --- COMMIT KISMI (Verileri Repoya Kaydet) ---
# Bu adım data.json'ı repoda da güncel tutar, böylece geçmişi görürsün.
- name: Güncel Veriyi Commit ve Push Et
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add src/data.json scripts/baseline.json src/hall_of_fame.json
# Değişiklik yoksa hata vermeden devam et, varsa pushla
git diff --quiet && git diff --staged --quiet || (git commit -m "Auto-update stats [skip ci]" && git push)
# --- ARTIFACT OLUŞTURMA ---
# Build edilen 'dist' klasörünü bir sonraki adıma aktarır
- name: Upload Pages Artifact
uses: actions/upload-pages-artifact@v3
with:
path: './dist'
# 2. GÖREV: Yayına Al (Deploy)
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build-and-update
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- Live Filtering I implemented a real-time search feature. As you type, the table filters instantly without any server-side delay. It also has a versus mode where you can compare users with each other.
- Detailed Player Cards Clicking on a player reveals a deeper dive into their stats—Win rates, ADR (Average Damage per Round), and recent match history.
🚀 Performance Benchmarks
Since we use the Steam web API, you need to close the game for the game data to synchronize. You can then retrieve the data using the data-parse.py file. The site goes live on GitHub Actions in about 1-2 minutes, and the data retrieval process takes approximately 5 to 10 seconds.
💡 What I Learned
This project actually came about after a conversation among friends. That's why I wanted to move forward quickly. I created a React project using Antigravity AI and the Steam Web API. With Vite, I was able to build the foundation of the project very quickly, and the rest was just development.
Key Takeaway: Focus on the data structure first. If your JSON is clean, your frontend logic becomes trivial.
💬 Let's Discuss!
How do you handle data updates for static sites? Do you prefer GitHub Actions, or do you think a serverless function (like Vercel/Netlify) is the better route?
If you liked it, don't forget to leave a star ! ⭐
Check out the repo and let me know your thoughts in the comments! 👇



Top comments (0)