Package Exports
- @nannoy/git-folio
- @nannoy/git-folio/react
Readme
@nannoy/git-folio
Embeddable GitHub stats card for developer portfolios. No API token required — works entirely with public GitHub data.
Install
npm install @nannoy/git-folioQuick Start — Vanilla JS
import { fetchGitHubStats } from "@nannoy/git-folio";
const stats = await fetchGitHubStats({ username: "torvalds" });
console.log(stats.totalStars); // number
console.log(stats.totalCommits); // number (from GitHub Search API)
console.log(stats.topLanguages); // LanguageStat[]
console.log(stats.contributionStreak); // number — consecutive days
console.log(stats.contributionCalendar); // ContributionWeek[]React Component
import { GitFolioCard } from "@nannoy/git-folio/react";
export default function Portfolio() {
return (
<GitFolioCard
username="torvalds"
theme="auto"
variant="full"
accentColor="#2dd4bf"
/>
);
}How it works (no token)
All data is fetched from public GitHub endpoints in parallel:
| Data | Source |
|---|---|
| Profile, repos, stars, forks, followers | GitHub REST API (/users/{u}, /users/{u}/repos) |
| Top languages + colors | REST repos list + bundled color map |
| Commit count | GitHub Search API (/search/commits?q=author:{u}) |
| PR count | GitHub Search API (/search/issues?q=author:{u}+type:pr) |
| Issue count | GitHub Search API (/search/issues?q=author:{u}+type:issue) |
| Contribution heatmap, streak | github-contributions-api.jogruber.de |
Rate limits (unauthenticated):
- REST API: 60 requests/hour per IP
- Search API: 10 requests/minute per IP (separate bucket)
- Contributions proxy: cached 1 hour
For server-side rendering, cache the result and revalidate on a schedule — the built-in cache option handles this: fetchGitHubStats({ username, cache: 3600 }).
React Props
| Prop | Type | Default | Description |
|---|---|---|---|
username |
string |
required | GitHub username |
theme |
'light' | 'dark' | 'auto' |
'auto' |
Color theme — auto follows system preference |
variant |
'compact' | 'full' |
'full' |
compact shows avatar, name, and 4 key stats only |
accentColor |
string |
#2dd4bf |
Hex color for accents and heatmap tint |
showContributions |
boolean |
true |
Show contribution heatmap |
showLanguages |
boolean |
true |
Show top languages bar |
className |
string |
— | Extra CSS class on the card element |
GitFolioStats type
interface GitFolioStats {
name: string;
login: string;
avatarUrl: string;
bio: string | null;
location: string | null;
websiteUrl: string | null;
followers: number;
following: number;
totalRepositories: number;
totalStars: number;
totalForks: number;
totalCommits: number | null; // null only if Search API rate-limited
totalPullRequests: number | null; // null only if Search API rate-limited
totalIssues: number | null; // null only if Search API rate-limited
totalContributions: number;
topLanguages: LanguageStat[];
contributionStreak: number;
contributionCalendar: ContributionWeek[];
}Error Handling
import { fetchGitHubStats, GitFolioError } from "@nannoy/git-folio";
try {
const stats = await fetchGitHubStats({ username });
} catch (err) {
if (err instanceof GitFolioError) {
console.error(err.code); // 'RATE_LIMITED' | 'USER_NOT_FOUND' | 'NETWORK_ERROR' | ...
console.error(err.retryAfter); // Date | undefined
}
}| Error code | Cause |
|---|---|
RATE_LIMITED |
GitHub REST API rate limit hit |
USER_NOT_FOUND |
Username doesn't exist on GitHub |
NETWORK_ERROR |
DNS / fetch failure |
HTTP_ERROR |
Non-200 GitHub API response |
PARSE_ERROR |
Unexpected response body |
Theming
CSS custom properties can be overridden on any ancestor:
.my-portfolio {
--gf-accent: #a78bfa;
--gf-accent-rgb: 167, 139, 250;
--gf-radius: 12px;
--gf-font: "Inter", sans-serif;
}