GitHub에 푸시하면 자동으로 업데이트되는 포트폴리오를 만들었습니다
요약
GitHub에 새로운 레포지토리를 푸시하면 AI가 README를 분석하여 자동으로 포트폴리오를 업데이트하는 시스템 구축 방법을 소개합니다. Next.js의 캐싱 기능과 LLM을 활용하여 별도의 데이터베이스 없이도 동적인 포트폴리오를 유지하는 아키텍처를 다룹니다.
핵심 포인트
- GitHub API를 활용한 레포지토리 데이터 자동 추출
- LLM(Groq/Llama)을 이용한 프로젝트 요약 자동 생성
- Next.js revalidate를 활용한 서버리스 데이터 동기화
- 정적 템플릿의 유지보수 문제를 해결하는 자동화 워크플로우
모든 개발자 포트폴리오는 동일한 문제를 가지고 있습니다. 한 번 만들고 나면 흥분된 상태로 두었다가, 시간이 지나면서 서서히 방치됩니다. 다음 1년 동안 5개의 새로운 프로젝트를 출시하더라도, 당신의 포트폴리오는 여전히 출시 당일의 똑같은 레포지토리(repo) 3개만을 보여주고 있을 것입니다.
그래서 저는 스스로를 수정하는 포트폴리오를 만들었습니다. GitHub에 새로운 레포지토리를 푸시하면, 단순히 GitHub의 원문 설명(blurb)이 아니라 AI가 작성한 설명과 함께 제 포트폴리오에 자동으로 나타납니다. LinkedIn을 연결하면 경력과 학력 정보도 자동으로 채워집니다.
이 글은 이것이 어떻게 작동하는지, 왜 제가 이러한 선택을 했는지, 그리고 여러분이 어떻게 자신만의 버전을 만들 수 있는지에 대한 전체적인 분석입니다.
제가 봐왔던 모든 포트폴리오의 문제점
대부분의 포트폴리오 템플릿은 다음 두 가지 범주 중 하나에 속합니다:
- 정적 템플릿 (Static templates) — 프로젝트를 JSON 파일이나 JSX에 직접 하드코딩합니다. 새로운 것을 출시하는 순간, 당신은 다시 코드 에디터로 돌아가 배열(array)을 업데이트해야 합니다.
- 이미 존재하는 GitHub 추출 도구들 — 몇몇 프로젝트는 레포지토리를 자동으로 나열해주지만, 다듬어지지 않은 GitHub의 원문 설명만을 그대로 쏟아냅니다. 맥락도 없고, 기술 스택(tech stack) 분석도 없으며, 프로젝트를 실제로 매력적으로 보여줄 수 있는 요소가 전혀 없습니다.
저는 그 중간 형태를 원했습니다. 자동적이면서도, 스스로 좋은 설명을 작성할 수 있을 만큼 똑똑한 형태 말입니다.
아키텍처 (The architecture)
핵심 아이디어는 간단합니다:
GitHub API → 레포지토리 + README 가져오기
↓
AI (Groq/Llama) → 각 README를 읽음 → 깔끔한 2문장 요약 작성
...
여기에는 데이터베이스가 필요하지 않습니다. 포트폴리오는 페이지가 로드될 때마다 GitHub에서 최신 데이터를 가져오며 (Next.js의 revalidate를 통해 1시간 동안 캐싱됨), 따라서 유지 관리해야 할 동기화 작업(sync job), 크론(cron), 또는 웹훅(webhook) 서버가 없습니다.
1단계 — GitHub 데이터 가져오기
GitHub REST API는 공개 레포지토리 데이터를 위해 OAuth를 요구하지 않습니다. public_repo 권한(scope)이 있는 개인 액세스 토큰(personal access token)이면 충분합니다:
const res = await fetch(
`https://api.github.com/users/${process.env.GITHUB_USERNAME}/repos?sort=updated`,
{
...
그 단 한 번의 호출로 저장소 이름(repo name), 설명(description), 언어(language), 스타(stars), 포크(forks), 그리고 URL을 가져올 수 있습니다. 여기서 revalidate 옵션이 아주 중요한 역할을 합니다. Next.js의 App Router는 이 fetch를 에지(edge)에서 캐싱하고 매시간 자동으로 새로고침하므로, 별도의 동기화 프로세스 없이도 페이지 속도를 빠르게 유지할 수 있습니다.
2단계 — GitHub보다 더 나은 설명을 작성하는 AI 만들기
GitHub의 저장소 description 필드는 저장소를 생성할 때 입력한 한 줄짜리 문구일 뿐입니다. 그래서 내용이 오래되었거나 그냥 "my project lol" 같은 식인 경우가 많습니다. 저는 포트폴리오가 각 프로젝트가 무엇을 하는지 실제로 설명하기를 원했습니다.
모든 저장소에 대해, 저는 원본 README를 가져와 LLM(대규모 언어 모델)에 전달합니다:
async function getAIDescription(repo) {
const readmeRes = await fetch(
`https://api.github.com/repos/${username}/${repo.name}/readme`,
...
OpenAI나 Claude 대신 Groq을 선택한 이유
Groq으로 결정하기 전까지 몇 가지 제공업체를 시도해 보았습니다:
| 제공업체 | 무료 티어 (Free Tier) | 속도 | 사용하지 않은 이유 |
|---|---|---|---|
| OpenAI | 체험 크레딧만 제공 | 빠름 | 결국 카드가 필요함 |
| ... |
Groq의 무료 티어는 진정으로 무료입니다. 가입 시 신용카드가 필요하지 않으며, 추론(inference) 속도는 제가 사용해 본 호스팅된 LLM 중 가장 빠릅니다. "이 README를 두 문장으로 요약해줘"와 같은 작업에는 프런티어 모델(frontier model)이 필요하지 않습니다. Llama 3.3 70B로도 충분히 잘 처리되며, 응답이 1초도 안 되어 돌아옵니다.
3단계 — LinkedIn API를 건드리지 않고 LinkedIn 동기화하기
이 부분이 가장 까다로웠습니다. LinkedIn의 공식 API는 폐쇄적입니다. 프로필 데이터를 프로그래밍 방식으로 읽으려면 파트너 권한이 필요한데, 이는 사이드 프로젝트에서는 현실적이지 않습니다.
우회 방법: LinkedIn은 모든 사용자가 계정 설정에서 자신의 데이터를 ZIP 파일로 내보내기(export) 할 수 있도록 허용합니다. 그 ZIP 파일 안에는 전체 경력 사항이 담긴 CSV 파일들(Positions.csv, Education.csv, Skills.csv)이 들어 있습니다.
따라서 API 연동 대신, 다음과 같은 간단한 업로드 흐름을 구축했습니다:
사용자가 LinkedIn 데이터를 내보내기 함 (LinkedIn 공식 기능)
↓
내 사이트에 ZIP 파일 업로드
...
ZIP 파일을 열고 나면 파싱(Parsing) 자체는 간단합니다:
import JSZip from "jszip";
const zip = await JSZip.loadAsync(buffer);
...
OAuth도, API 키도, 속도 제한(Rate limits)도 없습니다. LinkedIn의 공식 내보내기 도구를 통해 오직 본인이 내보낸 데이터만 처리하므로 완전히 규정을 준수합니다.
4단계 — 디자인
기능적으로 완성된 것도 중요하지만, 포트폴리오는 사람들이 기억할 만한 모습이어야 합니다. 저는 사이버펑크/글래스모피즘 (Glassmorphism) 미학을 선택했습니다. 어두운 배경, 보라색에서 청록색으로 이어지는 빛나는 그라데이션, 배경의 파티클 네트워크 (Particle network) 애니메이션, 그리고 히어로 섹션(Hero section)의 애니메이션 스타일 캐릭터 일러스트를 적용했습니다.
템플릿 느낌을 줄여주는 몇 가지 디테일은 다음과 같습니다:
- 실시간 시계 (Live clock): 네비게이션 바에 배치하여 "이것은 시스템이다"라는 느낌을 줍니다.
- 커서 글로우 (Cursor glow):
mousemove를 통해 위치를 지정한 방사형 그라데이션 (Radial gradient)을 사용하여 마우스를 따라다닙니다. - Canvas 기반 파티클 네트워크 (Canvas-based particle network): 80개의 작은 파티클이 서로 가까워지면 희미한 선으로 연결되며, 매 프레임마다 다시 그려집니다.
- 글래스모피즘 카드 (Glassmorphism cards): 낮은 불투명도의 배경과
backdrop-filter: blur(12px)를 사용하여 뒤쪽의 콘텐츠가 부드럽게 비치도록 했습니다.
개별적으로는 복잡하지 않지만, 이 요소들이 모여 페이지를 정적인 상태가 아닌 살아있는 것처럼 느껴지게 합니다.
다음에 다시 한다면 다르게 할 점
- AI 설명 캐싱 (Cache AI descriptions): 현재는 페이지를 로드할 때마다 변경되지 않은 저장소(Repo)에 대해서도 설명을 다시 생성합니다. 저장소의 마지막 업데이트 타임스탬프를 키(Key)로 사용하여 저장하면 불필요한 API 호출을 줄일 수 있습니다.
- 폴링 (Polling) 대신 웹훅 (Webhook) 추가: 매시간 단위의
revalidate창에 의존하는 대신, GitHub의 웹훅 시스템을 사용하여 푸시(Push) 시 즉시 재빌드(Rebuild)가 트리거되도록 합니다. - 더 많은 LinkedIn 필드 지원: 자격증과 추천 사항은 파싱되지만 아직 표시되지는 않습니다.
직접 시도해보세요
전체 프로젝트는 오픈 소스입니다. 클론(Clone)한 뒤, 본인의 GitHub 토큰과 Groq API 키를 넣으면 약 10분 만에 스스로 업데이트되는 포트폴리오를 가질 수 있습니다.
git clone https://github.com/Harshi-dhamu/auto-portfolio.git
cd auto-portfolio
npm install
...
전체 설정 지침 (setup instructions), 환경 변수 (environment variables), 그리고 AI 제공업체 비교 표는 README에 있습니다.
만약 이 프로젝트를 기반으로 무언가를 구축하거나 부족한 점에 대한 아이디어가 있다면, 진심으로 듣고 싶습니다 — Pull Request (PR)와 Issue는 언제나 열려 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기