I've had a GitHub profile stats card for a while now, but I was never fully happy with it. The existing tools each had trade-offs that bugged me, so I ended up building my own by combining the best parts of three different projects. Here's how it went.
There are a few popular projects for adding stats to your GitHub profile README:
This is the OG. You deploy it on Vercel, it uses GitHub's GraphQL API to generate SVG cards on the fly. It's fast, well-maintained, and has tons of themes.

The catch: it can't see your private repos. The GraphQL API with a personal access token will return language data for public repos only, so if you do most of your work in private repos (like I do at Bump), your language breakdown is incomplete. It also caps at 100 repos unless you set a special env var, and even then it's only repos you own — not org repos you contribute to.
This one takes a different approach. It runs as a GitHub Action, gathers stats across every repo, and generates static SVGs that get committed back to your repo. Because it runs with your token in Actions, it can access private repos.

Under the hood it's GraphQL for almost everything. The exception is "lines of code changed," which GitHub only exposes through a REST endpoint that returns 202 "computing" responses and has to be polled aggressively — back off and GitHub evicts the computed data from its cache before you can read it, and the polling never terminates. That's a GitHub API problem, not a design flaw in the project (and, full disclosure, my own solution below polls that endpoint the exact same way). The practical consequence is real though: in my experimenting, run times were unpredictable — anywhere from 17 minutes to over an hour depending on GitHub's API mood. The bigger limitation for me was the output model: it generates a fixed layout as static SVGs, so you can't easily customize it or combine it with other cards, and your stats are only as fresh as the last Action run.
(Update: since I originally wrote this, jstrieb rewrote github-stats in Zig and now sidesteps the lines-changed API entirely by cloning repos and computing the stat locally with git — which solves the 202 problem in a way none of the API-based approaches do, mine included.)
This one does one thing well: contribution streaks. It shows your current streak, longest streak, and total contributions with a nice ring animation.

But it runs on an external Heroku instance, which means you're dependent on someone else's server staying up.
One card that shows everything:
I forked anuraghazra/github-readme-stats and built a hybrid:
Live (via Vercel, on every request):
Cached (via a daily GitHub Action):
The Action runs daily, computes everything, and writes the results to a public GitHub Gist as JSON. When someone loads the card, Vercel reads the cached stats from the gist (instant) and combines them with the live streak and language data.
The key architectural difference from jstrieb: REST is only used where GraphQL can't help (lines changed, traffic views). Everything else uses GraphQL, minimizing API calls and avoiding the rate limiting that makes the pure-REST approach unpredictable. The Action also logs progress per-repo, so when something is slow or failing you can see exactly which repo is the problem.
Here's what the combined card looks like:
Everything in one image: stats on the left, languages on the right, streaks at the bottom with ring animations for both current (fire icon) and longest (trophy icon). The language progress bar spans the full width under the title.
It also supports all the themes from the original project:
/api/combined?username=YOUR_USERNAME&theme=dark
One fun piece was calculating contribution streaks from scratch. GitHub's GraphQL API exposes contributionCalendar data per year, which gives you every day's contribution count. To get streaks across all years, the code:
contributionYears to find every year you've been activecontributionCalendar.weeks.contributionDays for all years in a single queryThis runs in two GraphQL calls total, so the streak card loads in well under a second — no external service needed.
If you want to use this yourself:
PAT_1 (GitHub token with repo scope)GH_TOKEN, GIST_ID, and USERNAME to your repo's secrets/variablesFull setup instructions are in the repo README.
Nothing's perfect:
The GitHub Action logs progress per-repo during the daily run, making it easy to spot which repos are slow or hitting API errors. There's also a debug endpoint:
/api/top-langs?debug=true&username=YOUR_USERNAME
This returns JSON with the total repo count and a per-repo language breakdown — useful for tuning your EXCLUDE_REPO list or figuring out why your language stats look off.
I've had a GitHub profile stats card for a while now, but I was never fully happy with it. The existing tools each had trade-offs that bugged me, so I ended up building my own by combining the best parts of three different projects. Here's how it went. The Problem There are a few popular projects fo...