A web app that displays live seasonal leaderboards for Diablo 3, built with Next.js, React, and TypeScript. It authenticates with Blizzard's official game data API via OAuth2, then lets players browse and search rankings by region, season, hardcore mode, party composition, and leaderboard type.
A few decisions in this codebase worth calling out:
- Secrets never touch the client. Blizzard requires an OAuth2 client-credentials exchange to call the API. That exchange, and the bearer token it produces, live entirely in a
'use server'module (API_requests.js) — the client bundle never seesCLIENT_IDorCLIENT_SECRET. - UI options are derived from data, not hardcoded. Rather than shipping a static list of leaderboard types, the app fetches the current season's available leaderboards and computes the "Party" and "Leaderboard" filter options from that response. Add a new leaderboard on Blizzard's side and the UI picks it up automatically — no code change needed.
- Search is instant because it doesn't hit the network. Once a leaderboard is loaded, typing in the search box filters it in memory on every keystroke instead of re-querying the API, which keeps the interaction fast and avoids hammering a rate-limited third-party API.
- Components are kept dumb on purpose. Data-fetching and shaping is centralized in
page.tsxandAPI_requests.js; presentational components likeRankDisplayandPlayerDetailjust render props. That separation keeps the UI layer easy to test and reason about independent of the API's data shape. - Typed end-to-end. TypeScript interfaces model Blizzard's actual (fairly irregular) API response shape, and a generic
Dropdown<T>component keeps every filter's state type-safe rather than passing strings around everywhere.
Blizzard's Diablo 3 API is a read-only data feed with no rendering of its own — this app is the presentation layer on top of it, handling authentication, data shaping, and an interactive UI.
-
Server-side OAuth2 authentication.
src/modules/API_requests.jsexchangesCLIENT_ID/CLIENT_SECRETfor a bearer token via the client-credentials grant on first use, caches it in memory, and reuses it for subsequent calls. -
Three chained API calls drive the UI. The root page (
src/app/page.tsx) fetches data in a dependency chain, each step unlocking the next set of filter options:GetSeasonIndex(region)— resolves the current season number for the selected region.GetSeasonLeaderboardTypes(region, season)— lists every leaderboard available for that season (solo classes, team sizes, hardcore variants, etc.), parsed client-side to build the "Party" and "Leaderboard" dropdown options dynamically.GetSeasonalLeaderboard(region, season, leaderboard)— fetches the ranked rows of players/teams for the chosen combination of filters.
-
Client-side filtering and search. Region, season, hardcore, party, and leaderboard type are all React state, so changing any filter re-triggers the relevant fetch via
useEffect. The search box filters the already-loaded rows in memory against player name, clan, and class. -
Presentational rendering.
RankDisplayrenders one leaderboard row — hero portraits, rank styling by placement, and greater-rift clear time with a red-to-green color gradient based on speed — whilePlayerDetailrenders one player's stat line.
| Layer | Choice |
|---|---|
| Framework | Next.js 15 (App Router, Turbopack) |
| UI | React 19 |
| Language | TypeScript |
| Styling | Tailwind CSS 4 |
| Data source | Blizzard Battle.net API (Diablo 3 game data, OAuth2 client-credentials flow) |
| Linting | ESLint (eslint-config-next) |
You'll need a Battle.net API client:
- Create an account and a client at develop.battle.net/access/clients.
- Create a
.envfile in the project root with your Client ID and Client Secret:
CLIENT_ID=your_client_id
CLIENT_SECRET=your_client_secretThen:
npm install
npm run devOpen http://localhost:3000 to view it.
npm run dev— start the dev server (Turbopack)npm run build— production buildnpm run start— serve the production buildnpm run lint— run ESLint
src/
app/
page.tsx # Root route — owns filter state and orchestrates the 3-step data fetch
layout.tsx # Root layout, global styles
components/
Dropdown.tsx # Generic typed <select> wrapper used for all filters
RankDisplay.tsx # Renders one leaderboard row: portraits, rank badge, stat columns
PlayerDetail.tsx # Renders one player's name/clan/class/level/paragon line
NavBar.tsx / NavButton.tsx / Background.tsx / ScrollingFrame.tsx / Shadows.tsx # Layout & chrome
modules/
API_requests.js # Server-only Blizzard API client (OAuth2 token fetch + caching, 3 leaderboard endpoints)
RiftTime.js # Converts raw greater-rift clear time to minutes and maps it to a color gradient
stringFuncs.js # Small string-formatting helpers (title-casing labels)