DEV Community

Cover image for How I Built a High-Performance CS2 Leaderboard Using Antigravity & GitHub Actions 🚀
İbrahim SEZER
İbrahim SEZER

Posted on

How I Built a High-Performance CS2 Leaderboard Using Antigravity & GitHub Actions 🚀

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
  • 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:

  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. 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.

Versus Mode

Versus

  1. 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.

Detailed Player Cards

🚀 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)