2026.07.17
개인 포트폴리오 및 음악 아카이브 플랫폼
#Next.js#TypeScript#Drizzle#Neon
프로젝트 소개
기술 블로그와 음악 디깅 아카이브를 한곳에서 운영하는 개인 풀스택 웹 플랫폼입니다. 기존 정적 블로그 서비스의 한계와 음악 평점 플랫폼의 폐쇄성을 극복하고, 마크다운 기반 콘텐츠 관리와 동적 데이터베이스 상호작용을 일관된 경험으로 통합하고자 제작하였습니다.
프로젝트 개요
| 항목 | 내용 |
|---|---|
| 기간 | 2026.07 ~ (운영 중) |
| 인원 | 1명 |
| 저장소 | github.com/keemjongjin/haengwoon |
| 서비스 | https://haengwoon.vercel.app/ |
기술 스택
| 레이어 | 사용 기술 |
|---|---|
| 프레임워크 | Next.js 16 (App Router), React 19, TypeScript |
| 스타일 | Tailwind CSS v4 |
| 데이터 | Neon (PostgreSQL), Drizzle ORM |
| 콘텐츠 | MDX (next-mdx-remote, rehype-pretty-code, Shiki) |
| 인증 | JWT (jose) — httpOnly 쿠키 |
| 외부 연동 | Spotify Web API, Spotify iFrame API, Deezer |
| 테스트 | Vitest |
| 배포 | Vercel |
주요 기능
기술 블로그
- 글 발행 — MDX 파일 +
git push로 발행, 프론트매터로 카테고리·태그·발행일 관리 - 노출 제어 — 관리자 페이지에서 글별 노출/숨김 토글(본문 파일은 그대로 두고 DB에 상태만 오버레이)
- 읽기 경험 — 자동 목차(ToC), 코드 하이라이팅(Shiki), 예상 읽기 시간
- 탐색 — 카테고리별 글 목록, 전문 검색
- 댓글 · 방명록 — Giscus(GitHub Discussions) 기반, 방명록은 고정 스레드로 매핑
- 소개 페이지 — About(자기소개), Projects(진행한 프로젝트)
음악 아카이브
- 탐색 — 앨범·아티스트 검색, 발매일/리뷰일 기준을 전환할 수 있는 아카이브(연대별 필터 포함), 차트
- 이중 평점 체계 — 직접 매긴 평점과 앨범 월드컵(맞대결)으로 산출한 Elo를 분리 운영, 둘의 괴리를 인사이트에서 시각화
- 인사이트 — 장르별 분포, 평점 분포 그래프
- 미리듣기 안정화 — Deezer 트랙 ID만 저장하고 재생 시점에 새 URL을 받아오는 프록시 구조로 15분 만료 문제 우회
- 하단 미니 재생바 — Deezer는 탐색바 없이 음량만, Spotify 전곡 재생 중엔 seek 가능한 진행바로 전환
- 수록곡 표시 — 트랙별 티어 등급 별점, 최애곡 표시
- 익명 댓글 — 4단계 스팸 방어(허니팟 → 제출 시간 검증 → IP 레이트리밋 → 금칙어 필터) 적용
- 월간 추천 LP — 매달 고른 앨범을 3D 레코드 진열대로 탐색, 앨범별 판 색·마감(블랙/컬러/투명/파스텔)·무늬(스플래터/마블/스월/픽쳐 디스크) 지정
- LP 플레이어 — 자켓에서 판이 빠져나오고 톤암이 내려앉는 재생 화면. 바늘을 끌어다 판에 올리면 재생
- 재생 파이프라인 — Spotify API로 메타데이터 수집 + iFrame API로 전곡 재생 제어(Premium 로그인 방문자는 전곡, 아니면 Deezer 30초 미리듣기로 자동 전환)
공통
- 테마 — Tech/Music 컨텍스트 × Light/Dark, 4가지 조합 전환
- 반응형 UI — 모바일에서 헤더 축소, 로고를 아이콘으로 전환, 본문 폭 통일
- 관리자 — 단일 패스워드 인증(JWT) + 통합 관리 페이지에서 글·음악·댓글 관리
아키텍처
app/ 라우트 (Tech · Music · Admin · API)
components/ common · blog · music · admin · features
content/ posts/*.mdx, projects/*.mdx ← 글 본문 원본 (git 관리)
lib/ db(schema·repo) · music(elo) · integrations(spotify·deezer)
· media(coverColor·lpDesign) · security(auth·spam)
- Repository 패턴 — DB 접근을
lib/db/repo.ts로 단일화해 라우트에서 쿼리를 직접 다루지 않는다 - 어댑터 + 목(mock) — 외부 API 키가 없어도 전체 흐름이 동작하도록 분리
- Elo 엔진 단위 테스트 — 점수 산출 로직 8개 케이스 검증
릴리즈 내역
'v1.0.0' (2026.07.17)
- 사이트 런칭, MDX 블로그 시스템 및 기본 앨범 평점 아카이브 구축
'v2.0.0' (2026.08.11)
- 월간 추천 LP 쇼케이스 추가: Music 홈 진열대 인터랙션 도입 및 메인 뷰 최적화 (첫 진입 시 빈 화면 방지 폴백 로직 포함)
- 감상 집중형 앨범 뷰 개편: 진열대 선택 시 평점/코멘트를 배제한 음원 감상 전용 인터페이스 제공
- 음원 재생 파이프라인 이원화: Deezer 30초 미리듣기와 Spotify 풀 트랙 재생 엔진 분리 및 재생바 UI 상태 분기
트러블 슈팅
1. 음원 메타데이터 불일치로 인한 미리듣기 API 전환 (iTunes → Deezer)
- 문제 상황: Spotify API가 트랙 응답에서
preview_url제공을 중단하면서 미리듣기 재생 기능이 통째로 빠지는 문제가 발생했습니다. 임시로 iTunes Search API를 도입해 곡명·아티스트명 텍스트로 미리듣기를 검색했지만, 메타데이터 표기 방식이 조금만 달라도 엉뚱한 음원이 매칭되거나 검색 결과 자체가 누락되는 문제가 빈번했습니다. - 원인 분석: iTunes Search API는 곡명·아티스트명 문자열이 완전히 같아야 정확히 매칭되는 방식이라 표기 차이에 취약했고, 여기에 비공식 요청 제한(분당 약 20회)까지 겹쳐 트랙 수가 많은 앨범은 등록 자체가 오래 걸렸습니다.
- 해결 방법:
- 매칭 정확도가 더 나은 Deezer API로 전면 교체했습니다.
- 다만 Deezer 미리듣기 URL은 발급 후 약 15분 만에 만료되는 서명형이었습니다. 그래서 URL을 그대로 저장하지 않고 트랙 ID만 저장해두었다가 재생 시점에 프록시 엔드포인트가 그때그때 유효한 URL을 새로 받아오는 구조로 설계를 바꿔, 매칭 정확도와 URL 만료 문제를 함께 해결했습니다.
2. Safari 브라우저에서 LP 바이닐이 렌더링되지 않던 렌더링 엔진 이슈
- 문제 상황: Chrome/Firefox에서는 정상 출력되던 LP 바이닐 오브젝트가 iOS 및 macOS Safari 브라우저에서 배경이 투명하게 날아가며 판 자체가 화면에서 사라지는 현상이 발생했습니다.
- 원인 분석: 바이닐의 미세한 소리골(그루브) 질감을 표현하기 위해
background단축 속성에repeating-radial-gradient단독 레이어만 지정했습니다. WebKit(Safari) 렌더링 엔진의 경우 복잡한 중첩 반복 방사형 그라디언트를 계산할 때 GPU 가속 및 렌더링 실패 시 배경 전체를 드롭해버리는 버그가 있었습니다. - 해결 방법:
- 단축 속성을 분리하여 기본 베이스 단색(
backgroundColor: #191920)을 하부에 명시적으로 레이어링했습니다. - 상단 그루브 텍스처는
backgroundImage로 독립 분리하여 그라디언트 렌더링이 실패하더라도 최소한 검은색 원반 본체는 항상 렌더링되도록 방어 코드를 작성하고, 그루브 간격 단위를 완화하여 렌더링 부하를 줄였습니다.
- 단축 속성을 분리하여 기본 베이스 단색(
- 배운 점: 시각적 효과(데코레이션 그라디언트)는 요소의 '구조적 뼈대(존재감)'와 반드시 분리되어야 하며, Progressive Enhancement(점진적 향상) 관점에서 그래픽 폴백(Fallback)을 설계해야 브라우저 파편화를 방어할 수 있음을 체감했습니다.