Skip to content

Repository files navigation

Gittinder

GitWhisper

GitTinder mascot

GitHub × Tinder — a dating profile for any GitHub account, rated 0–99.

Next.js 16 React 19 TypeScript Tailwind CSS 4 Satori Redis MIT

Preview · Features · Quick Start · URLs · Architecture · Scoring · The Card · Deploy · Tech Stack · Structure · License


GitTinder turns any GitHub account into a Tinder-style dating profile, read straight from real GitHub stats — no self-reporting, no surveys. A match score, a tier, a generated bio, passion tags, a vibe and the six traits that explain it all. You can also test the chemistry between two accounts (/vs/a/b) and share your profile as a live card image (/<user>.png) that re-matches itself as your stats change.

There's no sign-up wall: "Match your own GitHub" signs you in with a single OAuth click and drops you straight on your own card (no session, no stored data), you can search by real name or hit Surprise me, and the whole thing flips into a dark mode that the cards follow too.

Everything runs on the GitHub GraphQL API (contributionsCollection — the only endpoint that returns real per-year commit / PR / review / issue / calendar data), so the "personality" is anchored to actual numbers, never invented.

Preview

GitTinder card GitTinder card GitTinder card


Live cards, generated server-side from real GitHub data.

Features

Feature Description
Match score A 0–99 rating — a weighted blend of six dating traits
Six traits SPARK, CHAT, STYLE, LOYAL, CARE, ENERGY — each scouted from real numbers
Tiers RED FLAG → GREEN FLAG → KEEPER → A CATCH → HEAD-TURNER → THE ONE
Generated bio Lines like "My repos are famous. My DMs are not." — every one anchored to a real figure
Passion tags Open source, Star magnet, Polyglot… each with the real number that earned it
Vibe An archetype read off your trait shape — The Influencer, The Social Butterfly, The Polyglot…
The gags "Age" is your years on GitHub, "height" is your star count, online/away/offline reads your contribution calendar
Pair chemistry /vs/a/b — shared languages + radar complementarity + match scores into one 0–99 chemistry score with its own tiers (MATCH MADE IN MERGE → SWIPE LEFT)
Live card image /<user>.png — a shareable card that regenerates as your stats change
OG unfurls Every profile and every pair unfurls with its exact rating on Twitter / Slack / Discord
Raw JSON API /api/card/<user> returns the full scored profile
Live scout counter The home page shows an all-time "profiles matched" tally (Redis)
Match your own GitHub A one-click OAuth button that reads your login, discards the token and lands you on your own card — no session, no stored data
Search by name Type a real name instead of a username — suggestions come live from GitHub's user search (10 min cache)
Surprise me A random profile from a hand-picked pool when you can't decide who to match with
Dark mode A paper-and-ink dark palette across the app, synced with your system preference; the card image follows via ?theme=dark
Tokenless demo With no token configured, the baked sample profiles (torvalds, ThePrimeagen, pewdiepie-archdaemon, t3dotgg) still resolve so the app stays explorable

Quick Start

npm install
npm run dev       # http://localhost:3000
npm run build && npm start   # production
npm run lint      # eslint
npm test          # vitest

Environment:

Variable Required Notes
GITHUB_TOKEN ✓ (or GITHUB_TOKENS) a GitHub token for the GraphQL API (goes from ~60 unauthenticated req/hr to ~5,000)
GITHUB_TOKENS (alternative) a comma-separated pool — each scout is hash-sharded to one token, with a single failover retry on rate limits
REDIS_URL optional read-through cache (2h TTL) + the scout counter. Without it, every scout hits GitHub live
GITHUB_OAUTH_CLIENT_ID / GITHUB_OAUTH_CLIENT_SECRET GitHub OAuth App credentials — enable "Match your own GitHub". Without them the button is hidden

URLs

gittinder.com/<username> the full scout report
gittinder.com/<username>.png your card, as a live image (append ?theme=dark for the dark palette)
gittinder.com/api/card/<username> the raw profile JSON
gittinder.com/vs/a/b the chemistry report between two accounts

Drop your card in a profile README, a portfolio, anywhere — it re-matches itself as your stats change:

[![My GitTinder card](https://git-tinder-mu.vercel.app/YOUR_USERNAME.png)](https://git-tinder-mu.vercel.app/YOUR_USERNAME)

Architecture

graph TD
    Home["/ — scout form"] --> Scout
    Report["/<user>"] --> Scout
    API["/api/card/<user>"] --> Scout

    subgraph Scout["lib/scout.ts"]
        Cache[("Redis cache · 2h TTL")]
        Inflight["single-flight build"]
        Signals["lib/github/signals.ts"]
    end

    Scout --> Cache
    Cache --> Inflight
    Inflight --> Signals
    Signals --> GH[("GitHub GraphQL API")]

    subgraph Engine["lib/dating/engine.ts"]
        Traits["six traits → z-score → tension → spike"]
        Match["match 0–99 + tier"]
        Bio["bio · passions · vibe · gags"]
    end

    Signals --> Traits
    Traits --> Match
    Traits --> Bio
    Match --> Card["/<user>.png + OG 1200×630 (Satori)"]
    Bio --> Card
    Traits --> Report
    Match --> Report
    Bio --> Report
    Match --> JSON["api/card JSON"]

    subgraph Chemistry["lib/dating/compat.ts"]
        VS["/vs/a/b — shared langs + radar fit"]
    end
    Traits --> VS
    Match --> VS
Loading

Cache behavior: profiles are cached in Redis with a 2h TTL — best-effort, so a miss or an outage just falls through to a live fetch. Concurrent scouts of the same login collapse onto one in-flight build (no duplicate GitHub calls). Only successful builds are cached; scout errors propagate unchanged.

Scoring

The six traits

A scout pulls real numbers from GitHub's GraphQL API and maps them onto six dating traits:

Trait Scouted from
SPARK Instant chemistry Star power across your repos
CHAT Conversation game PRs opened + followers earned
STYLE Fashion sense How many stacks you switch up
LOYAL Loyalty Years of consistent contributions
CARE Care Reviews and issues you actually tend to
ENERGY Energy Recent activity and staying power

The match score

Your match (0–99) is the headline — a weighted blend of the six, with the social/visual traits carrying the profile the way a dating app weights a first impression:

match = 0.25·SPARK + 0.20·CHAT + 0.20·ENERGY + 0.15·STYLE + 0.10·CARE + 0.10·LOYAL

The raw traits cap at 88 — the 90s are a legacy gate, bought with years and sustained influence, so one heroic year won't crown you THE ONE. Every match walks out in a tier:

RED FLAG GREEN FLAG KEEPER A CATCH HEAD-TURNER THE ONE

The scoring rulebook

The six traits are drawn through a small pipeline: raw estimates from the signals, z-scored against your own shape, tension-penalised (nobody is elite at everything — spark fights loyal, style fights care, energy fights loyal), spiked around a magnitude center, and pulled toward a shared "attractive core" so the six read as one coherent character. The match is the weighted blend plus a legacy bonus for years and influence. Your per-year history sits behind the numbers — the lifetime contributions, active_years and "member since" all come from real per-year contribution windows.

The personality layer

Beyond the score, the algorithm writes a character out of the same numbers — and every line is anchored to a real figure, so nothing is invented (the humor is in the framing):

  • Bio — generated lines like "My repos are famous. My DMs are not." or "Loves merge requests. Hates conflicts."
  • Passion tagsOpen source, Merge requests, Star magnet, In-demand, Polyglot… each with the real number that earned it.
  • A vibe — the archetype read off your trait shape: The Influencer, The Social Butterfly, The Polyglot, The Long-Hauler, The Reviewer, The Weekend Warrior, or simply The Catch when the shape is strong and balanced.
  • The gags — "age" is your years on GitHub, "height" is your star count mapped to feet (6'2" in repos), online/away/offline reads off your contribution calendar, and a ✓ badge means enough followers or stars to look legit.

Pair chemistry (/vs)

Two profiles, one score: shared languages (Jaccard overlap) + how close the two radars are + how well their edges cover different ground + the average of their match scores:

chemistry = 0.30·shared languages + 0.25·radar similarity
          + 0.20·complementary edges + 0.25·on-paper charm   (clamped to 1–99)

With its own tiers: MATCH MADE IN MERGE (90+) → SPARKS FLYING (78) → GOOD VIBES (64) → COFFEE DATE (48) → IT'S COMPLICATED (34) → SWIPE LEFT (0).

The Card

One layout, three surfaces: the live <DatingCard/> in the app, the embeddable /<user>.png and the OG image are all the same design — photo up top, match score top-left, tier pill top-right, bio and language "interests" over a gradient plate. The PNG is re-created server-side with Satori (next/og) so it scales 1:1 with the in-app card and caches at the CDN — no object store to keep in sync. A failed scout renders a small branded hint instead of a broken image. The card has a light and dark palette: the app version follows the theme toggle, and the PNG reads a ?theme=dark query string — so your README badge can match your profile page.

Deploy

Standard Next.js on Vercel — the repo is a single app with app/ file-convention routes, static OG images and serverless-friendly routes (dynamic = "force-dynamic" on the per-user pages).

# Push, then in the Vercel dashboard set the environment variables:
GITHUB_TOKEN=your_token          # or GITHUB_TOKENS=token1,token2
REDIS_URL=redis://your-redis     # optional
GITHUB_OAUTH_CLIENT_ID=...       # optional — enables "Match your own GitHub"
GITHUB_OAUTH_CLIENT_SECRET=...

To enable the one-click sign-in, create a GitHub OAuth App (Settings → Developer settings → OAuth Apps) with a callback URL of https://your-app.vercel.app/api/auth/callback, then set the client ID and secret above.

No vercel.json needed — everything lives in next.config.ts.

Tech Stack

Component Technology
Framework Next.js 16 (App Router, Turbopack)
UI React 19 · Tailwind CSS 4 · lucide-react
Language TypeScript 5
Image rendering Satori (next/og) + sharp
Cache & analytics Redis via ioredis (optional)
Fonts Bebas Neue + DINPro Condensed (bundled locally)
Tests Vitest 4 (+ Playwright for browser flows)
Lint ESLint 9 (eslint-config-next)
Deploy Vercel

Project Structure

gittinder/
├── app/
│   ├── page.tsx                    # home — scout form + sample deck + live counter
│   ├── [username]/                 # canonical scout route (/<user>, re-exports /u/<user>)
│   │   ├── page.tsx
│   │   └── opengraph-image.tsx     # per-profile OG unfurl
│   ├── u/[username]/               # profile report (older shared links keep working)
│   │   ├── page.tsx                # server: scout → metadata + report
│   │   └── ProfileRoute.tsx        # client seam into ProfileView
│   ├── vs/                         # pair chemistry
│   │   ├── page.tsx                # /vs (picker) + /vs/a/b (report)
│   │   ├── loading.tsx             # "matching…" while the pair scouts
│   │   └── opengraph-image.tsx     # pair unfurl
│   ├── api/card/[username]/route.ts    # raw profile JSON
│   ├── api/card-image/[username]/route.tsx
│   ├── api/search/route.ts         # by-name user search (10 min cache)
│   ├── api/auth/                   # one-click OAuth sign-in
│   │   ├── login/route.ts          # starts the GitHub flow
│   │   └── callback/route.ts       # exchanges code → /<login> (no session)
│   ├── robots.ts · sitemap.ts · not-found.tsx · error.tsx
│   └── fonts/                      # Bebas Neue + DINPro
├── components/
│   ├── DatingCard.tsx              # the card (one layout, three surfaces)
│   ├── ProfileView.tsx             # the scout report
│   ├── SampleDeck.tsx · SwipeDeck.tsx · TraitRadar.tsx · MatchOverlay.tsx
│   ├── CompatView.tsx · CompatPicker.tsx   # the /vs report + picker
│   ├── ThemeToggle.tsx · CardShare.tsx · EmbedSnippet.tsx
│   └── ScoutForm.tsx · LoadingScreen.tsx · AppShell.tsx · Background.tsx
├── hooks/
│   └── useTheme.ts                 # dark mode, synced with the system preference
├── lib/
│   ├── scout.ts                    # username → profile (Redis read-through + single-flight)
│   ├── analytics.ts                # scout counter (Redis)
│   ├── redis.ts · puns.ts · radar.ts · format.ts · text.ts
│   ├── dating/                     # the matchmaking engine
│   │   ├── engine.ts               # buildProfile — signals → traits → match → bio
│   │   ├── constants.ts            # weights, tiers, accents, scoring constants
│   │   ├── compat.ts               # pair chemistry (/vs)
│   │   └── bio.ts · theme.ts · types.ts
│   ├── github/                     # GitHub GraphQL client + signals + token pool
│   │   ├── client.ts · signals.ts · tokens.ts · languages.ts · stars.ts · samples.ts
│   │   ├── oauth.ts                # OAuth URLs, token exchange, config guard
│   │   └── search.ts               # by-name user search
│   └── og/                         # Satori re-renders of the card + pair report
│       ├── renderCard.tsx · renderCompat.tsx · card.tsx
├── cards/                          # sample card images used in this README
├── tests/                          # vitest suites (engine, compat, puns, …)
├── next.config.ts · app/globals.css · LICENSE (MIT)
└── README.md

License

MIT — use it, fork it, match it.


Built with Next.js · React · TypeScript · Tailwind · Satori · Redis · sharp

gittinder.com  ·  is it a match?

About

Dating profile from your GitHub ("tall, 6'2 repo, loves merge requests, hates conflicts").

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages