A year-in-review for developers. Sign in with GitHub, and get a scrollable set of stat cards about your year of coding, plus a shareable image card you can post anywhere.
Live: https://githubwrapped-app.vercel.app - built by @codewithsamzy.
- Sign in with GitHub (OAuth, read-only scopes).
- Pulls your activity from GitHub's GraphQL API: the rolling last 12 months, or any single calendar year back to 2008.
- Shows animated stat cards, one idea per slide:
- Total contributions and commits
- Busiest month and busiest day of the week
- Longest contribution streak
- Top languages, as a percentage breakdown
- Most active repositories
- Stars received and new followers
- Generates a shareable image card with your headline stats, ready to download or post.
- Gives every result a public link so the card can be viewed without
signing in again:
/wrapped/<username>- the rolling last 12 months/wrapped/<username>/<year>- one calendar year, for example/wrapped/octocat/2024
- Switches years from inside the deck - the floating bar lists every year the account has existed, and the deck carries a "Year by year" slide.
| Stat | Source |
|---|---|
| Total commits / contributions | GraphQL contributionsCollection (totalCommitContributions, contributionCalendar.totalContributions) |
| Busiest month / weekday | Grouped from the daily contributionCalendar |
| Longest streak | Computed in this app: the longest run of consecutive days with at least one contribution |
| Top languages | Language byte counts per repository, summed across repos and converted to percentages |
| Top repositories | commitContributionsByRepository |
| Stars / followers | Standard user and repository fields |
Each year is cached separately (a finished year for 30 days, the current one for 24 hours).
Streaks and monthly totals depend on how "a day" is defined. This app uses UTC dates from GitHub's calendar, so numbers may differ slightly from your local-time expectations.
- Private contributions: GitHub only exposes them as a count, and only if you've enabled Include private contributions on my profile in your GitHub settings. They show up in totals and the calendar, but with no repository or language detail.
- One year per query: the GraphQL
contributionsCollectioncovers at most one year, so the app offers either the rolling last 12 months or one calendar year at a time. Calendar years are UTC, so a year wrapped here may differ by a day or two from your local timezone's idea of that year. - Language percentages measure bytes of code in repositories you own or contribute to, which is not the same as time spent.
Browser ──▶ Next.js app ──▶ GitHub OAuth ──▶ access token
│
├──▶ GitHub GraphQL API (contributions, repos, languages)
│
├──▶ stats module (streaks, months, language percentages)
│
├──▶ stat card slides (React + CSS transitions)
│
└──▶ share card route (HTML/CSS ──▶ PNG)
- Framework: Next.js (App Router) + TypeScript
- Styling: Tailwind CSS
- Auth: Auth.js with the GitHub provider
- Data: GitHub GraphQL API
- Share image:
@vercel/og(Satori) rendering HTML/CSS to PNG - Storage (optional): Postgres (Neon) to cache computed results, so public links don't re-hit the GitHub API on every view
- Clone and install
git clone <your-repo-url> cd github-wrapped npm install
- Create a GitHub OAuth app at
https://github.com/settings/developers→ New OAuth App.- Homepage URL:
http://localhost:3000 - Callback URL:
http://localhost:3000/api/auth/callback/github
- Homepage URL:
- Create your env file by copying
.env.exampleto.envand filling in:AUTH_SECRET= # generate with: npx auth secret AUTH_GITHUB_ID= AUTH_GITHUB_SECRET= DATABASE_URL= # only if you enable result caching NEXT_PUBLIC_APP_URL=http://localhost:3000 - (Optional) Bundle a font for the share card
The card renderer checks
npm run fonts
public/fontsfirst, then the font bundled with Next.js, thenCARD_FONT_URLif you set one. Nothing about rendering the PNG requires network access. - Run it
npm run dev
- Open
http://localhost:3000, click Connect GitHub, and approve.
- The app requests the minimum scope needed to read your profile and contribution data.
- Access tokens are used only to fetch your stats and are not stored beyond your session.
- Public result links expose only the aggregate stats shown on the card, never repository contents.
| Route | What it does |
|---|---|
/ |
Landing page. Signed-in visitors are built and redirected to their public page. |
/wrapped/<username> |
The scroll deck. Serves a fresh snapshot, else rebuilds from GitHub. |
/api/wrapped/<username>/card |
The 1200x630 PNG share card (next/og + Satori). |
/api/auth/[...nextauth] |
Auth.js OAuth endpoints. |
/api/cron/prune |
TTL sweep for stored snapshots. Bearer-authenticated, scheduled in vercel.json. |
Vercel is the shortest path. Two things differ from local development:
- Production needs its own callback URL. A GitHub OAuth app accepts up to 10 callback URLs, so you can add the production one to the same app you use locally (open the app, click Add callback URL). Use a separate app only if you want the two kept apart.
- Serverless instances do not share memory. Without
DATABASE_URLthe snapshot cache is per-instance and is lost on every redeploy, so public links keep rebuilding from GitHub. Set it for anything real.
On Windows, deploy.ps1 in the repo root does the linking, the environment
variables and the redeploys for you:
.\deploy.ps1 # sign in, link the project, deploy, print the URL
.\deploy.ps1 -Finish # after creating the production OAuth appDoing it by hand is equivalent:
vercel # first run links or creates the project
vercel --prodOr push to a Git host and import the repo at vercel.com/new. Note the URL you
get (https://<project>.vercel.app) or your custom domain.
GitHub Wrapped itself is deployed at https://githubwrapped-app.vercel.app.
Two more *.vercel.app aliases point at the same project:
mygithubwrapped.vercel.app and my-year-in-code.vercel.app.
At https://github.com/settings/developers, open your OAuth app and add:
- Homepage URL:
https://<your-domain> - Authorization callback URL:
https://<your-domain>/api/auth/callback/github
Keep the http://localhost:3000/api/auth/callback/github entry too, so local
and production share one app. Only make a second app if you would rather they
did not share a client secret.
Set these in Vercel -> Project -> Settings -> Environment Variables:
| Variable | Value |
|---|---|
AUTH_SECRET |
random 32-byte string (npx auth secret) |
AUTH_GITHUB_ID |
production OAuth app client ID |
AUTH_GITHUB_SECRET |
production OAuth app client secret |
NEXT_PUBLIC_APP_URL |
https://<your-domain> |
DATABASE_URL |
Postgres connection string (Neon's pooled URL works) |
GITHUB_PUBLIC_TOKEN |
read-only token, so signed-out visitors can view wrappeds |
CRON_SECRET |
random string; guards /api/cron/prune |
WRAPPED_SNAPSHOT_TTL_HOURS and CARD_FONT_URL are optional.
Without GITHUB_PUBLIC_TOKEN the landing page hides the lookup form, and
/wrapped/<username> only works for snapshots that are already cached.
vercel.json registers a daily sweep of expired snapshots at
/api/cron/prune. Once CRON_SECRET is set on the project, Vercel sends it as
a bearer token automatically, so there is nothing else to wire up.
- GitHub OAuth sign-in
- GraphQL query for contributions, repos, and languages
- Stats module (streaks, busiest month and weekday, language percentages)
- Unit tests for the streak and aggregation logic
- Animated stat card slides
- Share card image generation
- Public
/wrapped/<username>pages with caching - Rate-limit handling for GitHub API calls
- Deploy - live at https://githubwrapped-app.vercel.app
The mark lives in three places, all the same geometry:
| File | Used for |
|---|---|
public/logo.svg |
README, anywhere a file is handy |
src/app/icon.svg |
Browser favicon, via the Next.js icon convention |
src/components/LogoMark.tsx |
Inline SVG for pages, plus a div build of the same plate for the Satori routes |
src/app/apple-icon.tsx and src/app/opengraph-image.tsx render the plate at
180x180 and 1200x630. There is no binary to keep in sync; change the geometry in
LogoMark.tsx and the two files beside it and everything follows.
Built by @codewithsamzy.
Inspired by the "Wrapped" format popularized by Spotify. Not affiliated with or endorsed by GitHub or Spotify.
MIT