How the Dota 2 Stats Pages Work
By Amine Amine
The Dota 2 section of this site shows win rates, counter picks, item builds and laning tips for every hero. There's no database behind it and no scraping. It's a thin layer over OpenDota's public API, plus a bit of arithmetic and a lot of caching.
The data
Four OpenDota endpoints do all the work:
heroStats: every hero, with pick and win counts per rank bracket (Herald through Immortal);heroes/{id}/matchups: one hero's record against every other hero;heroes/{id}/itemPopularity: which items get bought, and when;constants/items: names, costs and images for items.
Win rate and pick rate
OpenDota splits stats into eight rank brackets. I sum wins and picks across all of them, then divide:
let totalWins = 0;
let totalPicks = 0;
for (let i = 1; i <= 8; i++) {
totalWins += h[`${i}_win`];
totalPicks += h[`${i}_pick`];
}
const winRate = totalPicks > 0 ? (totalWins / totalPicks) * 100 : 0;
Pick rate is each hero's picks as a share of all picks. It's the same idea one level up, and it tells you whether a hero is a pocket pick or played everywhere.
Counters that mean something
A raw "win rate against X" is misleading on its own. A hero that wins 55% of all games will also win more than 50% against most opponents. So instead of raw win rate, each matchup shows the advantage: how much better or worse the hero does against that opponent than it does overall.
const advantage = winRateVsThem - heroOverallWinRate;
Matchups with fewer than 10 games are dropped, because three lucky games shouldn't make a hero a "hard counter".
The same numbers power the home page card, "best win rate today". That one also requires at least 10,000 picks, so a niche hero on a hot streak doesn't take the crown.
Caching: fast for you, polite to OpenDota
OpenDota is free and rate-limited, and these numbers change slowly. Every request goes through Next.js's fetch cache:
- hero stats, matchups and item popularity are revalidated every hour;
- item constants are revalidated every day.
Most visitors therefore get a cached page, and OpenDota sees a handful of requests an hour instead of one per visitor. The sitemap uses the same cached hero list to give search engines a page per hero.
What I'd add next
A "draft helper" already suggests heroes that counter the enemy's picks. Next, I'd like to weight it by role, since a counter that doesn't fit your team's missing position isn't much help.
Go find your hero, or press ⌘K and type their name.