tl;dr: I built thegitgarden.com β enter any GitHub username and watch your contributions become a pixel art garden. here's what I had to figure out along the way.
πͺ΄ how it started
I came across The Git City β a project that turns GitHub profiles into 3D pixel art buildings. loved the concept. but I wanted something that felt more... alive? more personal?
gardens grow. they change with the seasons. they reflect how much care you put in. that felt like a much better metaphor for a developer's contribution history than buildings.
so I started prototyping.
βοΈ the core problem: getting real contribution data
this was the first big decision to make β and the one that broke the Lovable prototype I started with.
GitHub's public REST API has a dirty secret: the /events endpoint only returns the last 100 public events. so even prolific developers like gaearon (React core team) would show up with tiny trees and zero butterflies, because the API thought they barely committed anything.
the real data lives behind the GitHub GraphQL API β specifically the contributionsCollection query, which returns the exact same data as the green squares on your profile:
query($login: String!) {
user(login: $login) {
contributionsCollection {
contributionCalendar {
totalContributions
weeks {
contributionDays {
contributionCount
date
}
}
}
}
}
}
but this requires an authenticated token. which means I needed a backend.
πΈ the stack
- React + Vite + TypeScript β started from a Lovable prototype, exported the code, cleaned it up
- Supabase Edge Functions β backend proxy that holds the GitHub token and makes the GraphQL query
- Supabase PostgreSQL β caches garden data so repeated visits don't burn the API rate limit
- Cloudflare Pages β hosting + Pages Functions for dynamic og:image meta tags
-
Canvas API β the actual garden is drawn with
CanvasRenderingContext2D, pixel by pixel
πΏ the "no login required" decision
I really wanted the experience to be frictionless. you type a username, you see a garden. no OAuth, no signup.
the way this works without hitting GitHub's rate limit (60 req/hour unauthenticated) is a server-side token + cache strategy:
- the Supabase Edge Function uses my GitHub Personal Access Token (5000 req/hour)
- first visit to any garden β fetches from GitHub, saves to DB with
last_synced - repeat visits within 1 hour β served instantly from Supabase, zero GitHub API calls
this is exactly how The Git City does it. login is optional β it's only needed if you want to "claim" your garden and customise it (a future feature).
π making the garden feel alive
the pixel art is drawn on an HTML Canvas using a seeded random number generator based on the username. this means:
- every garden is deterministic β your garden always looks the same
- but different from everyone else's
the garden elements map to real GitHub data:
| what you do | what grows |
|---|---|
| commit a lot | πΈ more flowers |
| maintain many repos | π³ more trees |
| keep a streak | π mushrooms |
| have followers | π¦ hedgehog / π¦ deer / π¦ fox |
| 100+ commits | π¦ butterflies appear |
| 500+ commits | π¦ birds appear |
tree colors come from your most-used programming languages. JavaScript = yellow, TypeScript = pink, Python = green, etc.
one thing I iterated on a lot: the scaling. originally tree height used a linear scale that maxed out at 500 commits β so someone with 3000 commits had identical trees to someone with 500. switched to logarithmic scaling and it felt much more natural.
π¦ the og:image problem
sharing a link on LinkedIn without an image preview is a non-starter. but GitGarden is a React SPA β LinkedIn's crawler sees the same index.html regardless of the URL.
the fix: Cloudflare Pages Functions. for any /garden/:username route, a function intercepts the request, strips the static og tags from index.html, and injects dynamic ones:
// functions/garden/[username].js
const ogImage = `https://github.com/${username}.png`;
const title = `${displayName}'s git garden πΏ`;
html = html
.replace(/<meta property="og:[^"]*"[^>]*>/g, '')
.replace('</head>', `
<meta property="og:title" content="${title}" />
<meta property="og:image" content="${ogImage}" />
</head>
`);
using the GitHub avatar as the og:image was the simplest solution β it's a real PNG that LinkedIn accepts, and it's personal to each user.
π what I'd do differently
start with the backend first. I wasted time building UI in Lovable before I understood the data layer. the GraphQL API decision should have been day one.
think about caching earlier. the stale-while-revalidate pattern (show cached data immediately, refresh in background) was an afterthought but ended up being critical for protecting the rate limit.
πͺ· try it
πΏ thegitgarden.com
the code is open source on GitHub. if you have ideas for what else should grow in the garden, there's a feature request link in the footer β I'm genuinely collecting them.
curious what your garden looks like? π±


Top comments (0)