leetcode-stats-card
v1.0.1
Published
A self-contained React card that shows a LeetCode user's solved stats and contest-rating history graph. Just pass a username.
Maintainers
Readme
leetcode-stats-card
A self-contained React card that displays a LeetCode
user's problem-solving stats and contest-rating history. Just pass a username — it
ships its own styles, and handles caching, rate-limit retry, and singleflight dedupe
so you don't hit 429 Too Many Requests. Mirrors the design of
codeforces-stats-card.
Features
- 🔢 Attempted / solved / acceptance (acceptance = problems solved ÷ problems attempted)
- 📊 Solved-by-difficulty (Easy/Medium/Hard with official LeetCode colors)
- 🎖️ Current & max contest rating with global top %
- 📈 Contest-rating graph with per-contest markers and hover tooltip
- 🎨 Zero CSS imports – self-injecting styles
- 📦 No runtime deps beyond React
- 🧠 Client cache (memory + localStorage, 5 min), request dedupe, retry on 429
How it avoids 429 (and how leetcard.jacoblin.cool worked)
leetcard is a Cloudflare Worker that:
- Queries LeetCode GraphQL server-side (no CORS, proper UA)
- Caches globally for 300s (
caches.open("leetcode")) - Sets
Cache-Control: public, max-age=300on SVG – browsers/CDN cache 5 min - So 1000 visitors = 1 request to LeetCode per 5 min
Our first version used alfa-leetcode-api.onrender.com directly from browser:
- 3 req/load, no cache, shared 120 req/hour, shared IP → 429
Fix: Self-host a proxy (like leetcard) + client cache.
Quick Start – Choose your proxy
By default, the card uses the public shared proxy (https://alfa-leetcode-api.onrender.com, 120 req/hour, may 429). For production, you should provide your own proxy via apiBase prop.
Option 1: Deploy your own Cloudflare Worker (recommended, global cache, free)
Same strategy as leetcard:
cd worker
npx wrangler login
npx wrangler deploy
# -> https://leetcode-stats-card.<your>.workers.devThen:
<LeetcodeCard username="Ryan-Westfall" apiBase="https://leetcode-stats-card.<your>.workers.dev" />See worker/README.md for details.
Option 2: Self-host on your website (Netlify/Vercel/Vite)
If your site is on Netlify, copy examples/netlify/functions/leetcode.js to your site's netlify/functions/leetcode.js and add to netlify.toml:
[[redirects]]
from = "/api/leetcode"
to = "/.netlify/functions/leetcode"
status = 200
force = true
[[redirects]]
from = "/api/leetcode/*"
to = "/.netlify/functions/leetcode?username=:splat"
status = 200
force = trueThen:
<LeetcodeCard username="Ryan-Westfall" apiBase="/api/leetcode" />Same for Vercel (examples/vercel/api/leetcode.js → api/leetcode.js) and Vite local dev (middleware in examples/).
Viewer IP does NOT count against LeetCode – only your serverless function's IP does, and it's cached 5 min globally.
Option 3: Public shared proxy (demos only, 429 risk)
Default, no setup:
<LeetcodeCard username="Ryan-Westfall" />
// uses https://alfa-leetcode-api.onrender.com- 120 req/hour per IP, shared upstream → may 429
- Client cache helps, but not for first load or many visitors
No auth needed – public LeetCode data is unauthenticated, just needs proper headers server-side.
Want fallback chain?
<LeetcodeCard
username="Ryan-Westfall"
apiBase="/api/leetcode"
fallbackApiBases={[
"https://your-worker.workers.dev",
"https://alfa-leetcode-api.onrender.com"
]}
/>See examples/README.md for all platforms.
Install
npm install leetcode-stats-card
# or
bun add leetcode-stats-card
# or from GitHub
bun add github:Ryan-Westfall/leetcode-stats-cardReact 17+ peer dep.
Usage
import { LeetcodeCard } from 'leetcode-stats-card';
export default function App() {
return <LeetcodeCard username="Ryan-Westfall" apiBase="/api/leetcode" />;
}Designed to fill container – give it a sized wrapper (grid/flex cell).
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| username | string | required | LeetCode username. Alias handle. |
| handle | string | — | Alias for username. |
| title | string | 'LeetCode' | Heading |
| showRank | boolean | true | Show top % / ranking |
| apiBase | string | 'https://alfa-leetcode-api.onrender.com' | Proxy URL. For production, pass your own (/api/leetcode or worker URL). |
| fallbackApiBases | string[] | [] | Fallback list if primary fails |
| cacheTTL | number | 300000 | Client cache TTL ms |
| className | string | '' | Extra classes |
| style | CSSProperties | — | Inline styles |
Styling
Injections to <head> under #lcsc-styles, scoped .lcsc-*. Override via CSS variables:
.lcsc-card {
--lcsc-line: #ff375f;
--lcsc-bg: #fbfbfb;
}Default line is LeetCode orange #FFA116 to distinguish from Codeforces blue.
Data source
Self-hosted proxy queries LeetCode GraphQL:
query getUserData($username:String!){
allQuestionsCount{difficulty count}
matchedUser(username:$username){
submitStatsGlobal{acSubmissionNum{difficulty count submissions} totalSubmissionNum{difficulty count submissions}}
profile{ranking}
}
userContestRanking(username:$username){attendedContestsCount rating globalRanking totalParticipants topPercentage}
userContestRankingHistory(username:$username){attended trendDirection problemsSolved totalProblems rating ranking contest{title startTime}}
}No key, no auth.
License
MIT © Ryan Westfall
