GitHub 프로필에 롤 티어를 달아봤습니다
github-readme-stats 같은 오픈소스 위젯을 만든 개발기
GitHub 프로필에 들어가면 github-readme-stats 카드를 달아놓은 사람이 꽤 많습니다.
그거 보면서 "나도 뭔가 독특한 걸 달아보고 싶다"는 생각이 있었습니다.
그러다 문득 든 생각이, GitHub 활동을 롤 랭크처럼 보여주면 어떨까? 였습니다.
아이언부터 챌린저까지, 그 티어 시스템을 GitHub에 적용하는 겁니다.
아마 이 글을 보시는 분들 중에도 롤을 하시는 분들이 꽤 많을 거라 생각합니다.
커밋 수, 스타, PR 같은 수치들을 보면서 "이거 티어로 환산하면 어떨까?" 하는 생각, 해보신 적 없으신가요?
저는 그 생각이 들자마자 바로 만들기 시작했습니다.
결과물부터 보여드리겠습니다.

README에 이 한 줄이면 끝입니다.
여담이지만, 솔직히 제가 만든 거니까 롤에서도 못 가본 천상계 랭크를 줘버리고 싶었습니다.
하지만 객관적인 지표를 위해 어쩔 수 없었고... 저는 에메랄드 IV에 배치받았습니다.
현실은 냉혹합니다.
이 글에서는 이걸 어떻게 만들었는지, 과정에서 어떤 결정을 내렸고 어디서 애를 먹었는지 정리해보겠습니다.
기술 스택 선택
처음엔 Next.js로 시작했습니다
github-readme-stats가 Vercel Serverless Function 기반이라는 건 알고 있었습니다.
근데 저는 평소에 Next.js를 주로 쓰니까, 그냥 Next.js로 시작했습니다.
API Route 하나면 되는 건데 왜 Next.js를 썼냐면, 솔직히 개발하면서 테스트 페이지도 띄우고 싶었기 때문입니다.
결론부터 말하면 잘못된 선택이었습니다.
- 콜드스타트가 느렸습니다. Next.js 번들이 무겁기 때문입니다.
- SVG 하나 렌더링하는데 React가 필요 없었습니다.
- Vercel 배포 시 Edge Function 호환 문제가 계속 발생했습니다.
Hono로 갈아탔습니다
Hono는 초경량 웹 프레임워크입니다.
Cloudflare Workers, Vercel, Deno 어디서든 돌아갑니다.
바꾼 이유는 간단합니다.
API 라우트 하나 + SVG 렌더링만 하면 되는 프로젝트에 Next.js는 과했습니다.
참고로 github-readme-stats도 Next.js가 아닌 순수 Node.js Serverless Function으로 되어 있습니다.
그쪽은 2020년에 만들어져서 Hono가 없었지만, 지금 새로 만든다면 Hono가 최선이라고 판단했습니다.
마이그레이션 자체는 1시간도 안 걸렸습니다.
비즈니스 로직은 그대로 두고, 진입점만 바꾸면 됐거든요.
다만 Vercel 배포할 때 Edge Runtime에서 이슈가 있어서, 결국 Node.js Serverless Function으로 빌드하게 됐습니다.
로컬에선 Hono 서버로 개발하고, 배포는 esbuild로 번들링해서 올리는 구조입니다.
SVG 카드 렌더링
카드는 서버에서 SVG 문자열을 직접 생성합니다.
React 같은 거 안 씁니다. 템플릿 리터럴로 SVG를 조립하는 방식입니다.
export function renderTierCard({ stats, tier, breakdown, theme }) {
return `
<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
<!-- 배경, 아바타, 스탯바 등을 조립 -->
</svg>
`.trim();
}처음엔 간단할 줄 알았는데, SVG 레이아웃 잡는 게 생각보다 까다로웠습니다.
텍스트 겹침과의 전쟁
SVG에는 CSS flexbox가 없습니다.
모든 위치를 x, y 좌표로 직접 계산해야 합니다.
아바타, 유저 이름, 티어 뱃지, 스탯바 5줄... 이걸 겹치지 않게 배치하는 데만 시간을 꽤 썼습니다.
특히 처음에 CSS 애니메이션을 넣었다가, <img> 태그로 로드할 때 opacity: 0이 초기값으로 고정되면서 카드 내용이 전부 안 보이는 사고가 있었습니다. 이건 꽤 당황했습니다.
보안 처리
SVG 안에 유저 이름을 렌더링하니까, 당연히 XSS 위험이 있습니다.
GitHub 이름에 <script> 같은 걸 넣는 사람이 있을 수 있으니까요.
export function escapeXml(s: string): string {
return s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}간단하지만 빼먹으면 큰일 나는 부분입니다.
GraphQL injection 방지를 위해 쿼리 변수 바인딩도 적용했고, username 입력값 검증도 정규식으로 처리했습니다.
점수 계산 — 여기가 핵심이었습니다
처음엔 감으로 벤치마크를 잡았습니다
"커밋 500개면 높은 편 아닌가?"
이런 식으로 대충 벤치마크를 정하고, 선형 보간으로 점수를 매겼습니다.
결과는 처참했습니다.
대부분 유저가 아이언~브론즈에 몰렸고, 이걸 누가 프로필에 달고 싶겠습니까.
github-readme-stats의 공식을 분석했습니다
github-readme-stats가 쓰는 계산 공식을 뜯어봤습니다.
Log-Normal CDF라는 통계 함수를 쓰고 있었습니다.
log_normal_cdf(x) = x / (1 + x)
값이 커져도 점진적으로 감소하는 수익이 적용돼서, 변별력이 유지됩니다.
커밋 100개에서 200개로 올리는 것과, 5000개에서 5100개로 올리는 건 다른 가치를 가져야 하니까요.
그대로 쓰면 안 됐습니다
github-readme-stats는 Stars 가중치가 가장 높습니다.
오픈소스 메인테이너에게 유리한 구조입니다.
근데 대부분의 개발자는 Stars가 거의 없습니다.
회사에서 열심히 커밋하는 사람이 Stars 때문에 저티어 받으면 쓰고 싶지 않을 겁니다.
그래서 커밋 중심으로 가중치를 재설계했습니다.
225명 샘플링으로 검증했습니다
감으로 잡은 벤치마크를 검증하기 위해, GitHub API로 실제 유저 225명을 랜덤 샘플링했습니다.
팔로워 0명부터 29만 명까지 다양하게 뽑았고, 결과를 보면서 중위값과 티어 구간을 조정했습니다.
100% 객관적인 수치는 아닙니다.
하지만 CDF 기반이라는 점에서 방법론 자체는 탄탄하고, 추후 유저가 늘어나면 데이터 기반으로 보정할 계획입니다.
Vercel 배포 — 가장 애를 먹은 부분
이건 솔직히 별거 아닌 건데 시간을 많이 잡아먹었습니다.
"type": "module" 한 줄의 위력
처음에 Edge Function으로 배포하려 했습니다.
로컬에서는 잘 되는데, Vercel에 올리면 504 타임아웃이 걸렸습니다.
디버그용 최소 함수를 만들어서 테스트하고, 빌드 로그를 뒤지고, 구글링하고...
원인을 찾는 데 한참 걸렸는데, 결론은 package.json의 "type": "module" 때문이었습니다.
이 한 줄 지우니까 바로 동작했습니다.
이런 게 제일 허무합니다.
esbuild 번들링
TypeScript에서 @/ path alias를 쓰고 있어서, Vercel이 직접 빌드하면 import를 못 찾았습니다.
esbuild로 로컬에서 번들링한 후 결과물을 올리는 방식으로 해결했습니다.
디자인
티어별 뱃지 차별화
Iron부터 Diamond까지는 육각형 뱃지, Master 이상은 텍스트 기반 엘리트 엠블럼으로 차별화했습니다.
Diamond 이상 천상계에는 글로우 테두리 효과와 각 티어별 고유 장식(왕관, 보석, 쉴드)을 넣었습니다.
솔직히 롤 엠블럼처럼 화려한 비주얼은 SVG 코드만으로 한계가 있습니다.
추후 일러스트레이터 분의 기여가 있다면 더 멋진 엠블럼으로 교체하고 싶습니다.
9가지 테마
dark, tokyonight, dracula, nord, gruvbox, catppuccin, onedark, radical, light.
각 테마마다 배경, 텍스트, 아이콘, 바 등 7가지 색상을 분리해서 다채롭게 만들었습니다.
fade-in 애니메이션
github-readme-stats처럼 새로고침 시 카드가 사르륵 나타나는 효과를 넣었습니다.
아바타 → 유저 정보 → 스탯바 순서로 순차 등장합니다.
오픈소스 프로젝트답게
README 작성에 공을 들였습니다
솔직히 코드 못지않게 README에 시간을 많이 썼습니다.
github-readme-stats 같은 10만 스타 프로젝트들의 README 구조를 분석하고, 우리 프로젝트에 맞게 구성했습니다.
Quick Start 3단계, 티어별 예시 이미지, 9가지 테마 미리보기, Deploy Your Own 가이드, FAQ까지.
사용자가 README만 보고 바로 쓸 수 있도록 신경 썼습니다.
부족한 점이 있다면 피드백은 언제나 환영입니다.
테스트 & CI
Vitest로 45개 테스트를 작성했고, GitHub Actions CI로 PR마다 자동 검증됩니다.
README에 CI 뱃지도 달아놨습니다.
Dependabot & 템플릿
npm 패키지와 GitHub Actions 의존성을 매주 자동 체크하는 Dependabot을 활성화했습니다.
버그 리포트, 기능 요청 Issue 템플릿과 PR 템플릿도 만들어서 기여 품질을 유지하도록 했습니다.
정리
좋았던 점
- Hono가 정말 가벼웠습니다. API 하나짜리 프로젝트에 딱이었습니다.
- SVG 서버 렌더링은 의외로 재밌었습니다. CSS 없이 좌표로 레이아웃 잡는 게 퍼즐 같았습니다.
- CDF 기반 점수 계산이 생각보다 합리적인 결과를 냈습니다.
- 오픈소스 프로젝트를 처음부터 끝까지 혼자 셋업해본 경험이 좋았습니다.
아쉬운 점
- SVG로 디자인하는 건 한계가 있습니다. 롤 엠블럼 같은 건 코드만으로 힘듭니다.
- Next.js로 시작한 건 처음부터 Hono로 갔으면 아꼈을 시간입니다.
- Vercel 배포에서
"type": "module"이슈는 정말 원인 찾기 어려웠습니다.
사용해보기
GitHub 프로필 README에 이 한 줄 추가하면 됩니다.
[](https://github.com/chahyunwoo/github-tier)테마도 바꿀 수 있습니다.
프로젝트 GitHub: github.com/chahyunwoo/github-tier
스타도 눌러주시면 감사하겠습니다.