The project cards show a live language dot, star count and last-push time straight from GitHub. There is no database and no backend service — just a static site with an hourly refresh.
Raw repo lists look bad
Fetching a user's repositories and rendering them directly gives you ugly names, coursework, forks and no thumbnails. So the display source of truth is a curated overlay in the repo:
type Project = {
slug: string;
repo: string | null; // "owner/name" — keyed for the sync
name: string; // the overlay wins for everything shown
tagline: string;
tech: string[];
};The overlay owns the name, tagline, tech list and ordering. GitHub only supplies the numbers.
The sync
For each project with a repo, one request to the GitHub API, cached with
Next's ISR:
const res = await fetch(`https://api.github.com/repos/${repo}`, {
headers,
next: { revalidate: 3600, tags: ["github"] },
});A GITHUB_TOKEN lifts the anonymous rate limit; without it the sync still
works. A push webhook can hit /api/revalidate to bust the github tag sooner
than the hourly window.
The part that matters: graceful failure
fetchRepoStats never throws. Any non-200 response, any network error, returns
null:
try {
const res = await fetch(url, { headers, next: { revalidate: 3600 } });
if (!res.ok) return null;
// ...parse and return stats
} catch {
return null;
}When it returns null, the card simply renders without its stats rail. The page
is built from the overlay regardless of whether GitHub is reachable. I tested it
with a deliberately invalid token: the site renders in full, the rail is just
absent. GitHub being down is not the site's problem.