깃허브 티어, 제대로 다시 만들기 Feat. Git TIERS 2탄
1탄 그 후
1탄에서는 깃허브 기여 수로 티어를 보여 주는 Git TIERS 를 만들고, README에 붙일 수 있는 이미지 URL까지 만든 이야기를 했다.
그때 글 끝에 숙제 두 개를 남겼다.
- 티어가 바뀌어도 이미지를 다시 넣지 않아도 되게 하기
- 배경 색을 color picker로 직접 고르게 하기
둘 다 해냈다. 그런데 다시 코드를 열어 보니 숙제보다 더 급한 게 보였다. 1탄의 구조에는 꽤 큰 구멍이 있었다.
그래서 2탄은 “기능 추가”보다 “제대로 다시 만들기” 에 가까운 이야기다.
한눈에 보는 변화
| 1탄 | 지금 | |
|---|---|---|
| 티어 이미지 | base64 JPEG를 DB에 저장 | 요청할 때마다 SVG로 생성 |
| 기여 수 | 브라우저가 계산해서 저장 | 서버가 GitHub에 직접 조회 |
| 자동 갱신 | 마이페이지에서 저장해야 반영 | README만 열려도 자동 반영 |
| DB 접근 | 브라우저에서 Firestore 직접 접근 | 서버만 접근, 브라우저는 전부 차단 |
| GitHub 토큰 | DB에 저장 | DB에 저장하지 않음 |
| 사용자 키 | GitHub 아이디 | GitHub 숫자 ID |
| 설정 저장 | Save 버튼 | 바꾸면 자동 저장 |
| 새 기능 | - | 랭킹 · 회원 탈퇴 · 다국어 · 커스텀 배경색 |
| 개발 환경 | - | Next.js 16 · 테스트 180개 · GitHub Actions |
1. 티어 이미지: base64 → SVG
1탄에서는 티어 이미지를 만들어 base64로 인코딩해서 Firestore에 저장하고, /api/tier/아이디 로 요청이 오면 그걸 디코딩해서 돌려줬다.
이 방식은 문제가 많았다.
- 이미지가 JPEG라 흐릿했고
- 문서마다 이미지 문자열이 통째로 들어가 DB가 무거웠고
- 무엇보다 저장 시점의 이미지라서 숫자가 바뀌어도 그대로였다.
지금은 요청이 올 때마다 티어 · 기여 수 · 표시 옵션으로 SVG를 바로 그린다. 벡터라 어디서 봐도 선명하고, DB에는 옵션 몇 개만 남는다.
2. 이번엔 진짜 자동 갱신
1탄 업데이트 때 “URL만 넣어 두면 티어가 바뀐다”고 썼는데, 솔직히 반만 맞는 말이었다.
배지에 쓰는 기여 수는 사용자가 마이페이지에 들어와 저장 버튼을 눌러야 바뀌었다. 한 번 넣고 다시 안 들어오면 숫자는 그대로였다.
이제는 이렇게 동작한다.
- README가 열리면 GitHub가 배지 이미지를 요청한다
- 서버가 서버용 GitHub 토큰으로 그 사람의 최근 1년 기여 수를 조회한다
- 결과는 3시간 동안 캐시해 두고, 지나면 다시 조회한다
배지 응답에는 Cache-Control: s-maxage=600, stale-while-revalidate=86400 을 붙여서, 요청이 몰려도 GitHub API를 과하게 부르지 않게 했다.
숫자도 GitHub 프로필에 보이는 값과 똑같이 맞췄다.
설정 화면의 Save 버튼도 없앴다. 배경색이나 카드 모양을 바꾸면 0.7초 뒤에 알아서 저장된다. 색상 피커를 드래그하는 동안 요청이 쏟아지지 않도록 디바운스를 걸었다.
3. 보안: 1탄의 가장 큰 구멍
다시 보면서 가장 놀란 부분이다. 1탄 구조에서는 누구나 티어를 조작할 수 있었다.
- 기여 수를 브라우저가 계산해서 서버에 보냈고, 서버는 그 숫자를 그대로 저장했다
- 브라우저가 Firestore에 직접 읽고 쓰는 구조였다
- 1탄 글의 데이터 예시처럼 GitHub access token이 DB에 그대로 들어 있었다
개발자 도구로 요청 하나만 바꾸면 Iron이 Challenger가 될 수 있었던 셈이다. 😇
그래서 이렇게 바꿨다.
- 기여 수는 서버가 GitHub에 직접 물어본다. 브라우저가 보낸 숫자는 받지도 저장하지도 않는다.
- Firestore는 Firebase Admin SDK로 서버에서만 접근한다. 보안 규칙은 브라우저 접근을 전부 거부로 바꿨다.
- GitHub 토큰은 DB에 남기지 않고, 암호화된 로그인 쿠키에만 둔다.
- 사용자 문서 키를 GitHub 아이디 → 숫자 ID 로 바꿨다. 아이디는 바꿀 수 있지만 숫자 ID는 바뀌지 않으니, 아이디를 바꿔도 데이터가 끊기지 않는다. 기존 문서는 로그인할 때 자동으로 옮겨지고, 남은 문서는 일회성 스크립트로 한 번에 옮겼다.
예전에 공개돼 있던 NEXT_PUBLIC_FIREBASE_* 키들도 운영 환경에서 모두 지웠다.
4. 새로 생긴 기능
커스텀 배경색 · 다국어
1탄에서 약속한 color picker 배경색을 넣었다. 프리셋(Light · Dark) 말고도 원하는 색을 고르면 배지에 그대로 반영된다.
화면은 한국어 · 영어를 모두 지원한다.
랭킹
이번 업데이트의 메인 기능이다. 사용자들 사이의 티어 랭킹을 볼 수 있다.
랭킹은 개인정보 노출이 걱정돼서 보여 주는 정보를 최소로 줄였다.
- 상위 3명만, 아이디는 일부만(
dev***ie) 보여 준다. 가린 부분은 길이가 드러나지 않게 늘 별표 3개다. - 기여 수는 공개하지 않고 티어만 보여 준다.
- 전체 인원도 숨긴다. “10명 중 3위”와 “상위 30%“를 같이 보여 주면 역산이 되기 때문에, 순위 숫자만 보여 주고 티어 분포도 인원수 대신 비율로만 그린다.
- 가리는 작업은 서버에서 한다. 화면에서만 가리면 개발자 도구로 다 보이니까.
로그인하면 내 순위와 다음 티어까지 남은 기여 수가 보이고, 갱신 버튼으로 내 기여 수를 바로 반영할 수 있다(1분에 한 번).
전체 사용자의 기여 수는 GitHub Actions가 매일 새벽 3시에 새로 조회한다. 처음엔 Vercel Cron으로 만들었는데, Legacy 표시가 붙어 있기도 하고 실행 기록과 수동 실행 버튼이 있는 Actions로 옮겼다.
회원 탈퇴 · 개인정보 처리방침
탈퇴하면 DB의 내 문서를 지우고, GitHub에 준 앱 권한까지 해제한 뒤 로그아웃한다. 실수로 누르지 않도록 GitHub 아이디를 직접 입력해야 버튼이 켜진다.
어떤 정보를 왜 저장하는지 정리한 개인정보 처리방침도 만들었다.
5. 디자인
UI도 거의 새로 만들었다.
- 랜딩: 원형으로 돌던 티어 이미지 대신, Iron부터 Challenger까지 넘어가는 티어 카드. 티어마다 색이 번지고, 마우스를 따라 기울어진다.
- 마이페이지 · 공지 · 랭킹: 천천히 떠다니는 오로라 배경과 반투명 유리 카드. 데이터를 다 받은 뒤에 카드가 아래에서 떠오른다.
- 랭킹: 1위는 넓게, 2 · 3위와 내 순위 · 티어 분포는 타일로 나눈 벤토 그리드.
- 공지사항: 버전별 변경 기록 타임라인. 이번부터 버전을 붙이기 시작했다. 기능 추가는 가운데 숫자, 버그 · 보안 수정은 끝자리를 올린다. 지금은 v1.5.0.
움직이는 배경은 처음에 filter: blur 로 만들었는데, 화면보다 큰 요소를 매 프레임 흐리게 처리하는 게 저사양 기기에 부담이라 원형 그라데이션으로 바꿨다. 겉모습은 거의 같고 부담은 훨씬 적다.
6. 보이지 않는 곳
- Next.js 16 으로 올렸다(
middleware→proxy.ts). 인증은 next-auth v4를 그대로 쓴다. Auth.js v5가 아직 베타라서. - 패키지 매니저는 yarn 하나로 정리했다. 언젠가 npm이 섞여서 잠금 파일이 두 개가 돼 있었다.
- 배포하자마자 Vercel 빌드가 깨졌는데, firebase-admin 하위 패키지가 Node 24.12 이상을 요구해서였다.
engines로 Node 24를 고정해서 해결했다. - 테스트가 하나도 없던 프로젝트에 vitest로 180개를 붙였다. PR을 올리면 GitHub Actions가 타입 검사 · lint · 테스트를 돌린다.
- 로컬에서는 GitHub 로그인이 안 돼서, 개발 모드에서만 동작하는
?preview=아이디를 만들었다. 다른 사람 기준으로 마이페이지와 랭킹 화면을 읽기 전용으로 볼 수 있다.
마치며
1탄이 “일단 돌아가게 만들기”였다면, 2탄은 “믿고 쓸 수 있게 만들기” 였다.
티어를 조작할 수 없게 막고, 숫자가 알아서 맞춰지고, 내 정보가 어디에 어떻게 저장되는지 설명할 수 있게 됐다.
README에 아래 태그를 넣으면 끝이다. 이제 다시 들어오지 않아도 된다.
<a href="https://github.com/git-tiers/gittiers">
<img src="https://git-tiers.devwoodie.com/api/tier/내아이디" alt="Git-TIERS" />
</a>랭킹에서 만나요. 🏆
B
u
y
M
e
A
C
o
f
f
e
e
☕
️