목차
치지직 왓처(Chzzk Watcher)는 네이버 치지직의 시간별 Top 20 스트리머 순위를 자동 수집하고, 팔로우 추이·채팅 버스트·랭킹 히스토리를 분석하는 서비스다. Next.js 16 + NestJS + PostgreSQL 기반의 모노레포 구조로 운영 중이다.
시작하게된 배경
치지직이 빠르게 성장하면서 "지금 Top 20에 누가 있는지", "어제 1위가 누구였는지" 같은 정보를 찾을 수 있는 곳이 없었다. 트위치에는 비슷한 분석 서비스가 있는데 치지직은 아무것도 없다는 게 아쉬웠고, 그게 개발의 출발점이었다.
결과적으로 서비스는 만들었는데, 돌아보니 "만드는 것"에만 집중하고 "알리는 것"과 "검색되는 것"은 거의 고민하지 않았다. 이 글은 그 부분에 대한 솔직한 회고다.
실제로 만든 것들
- 시간별 Top 20 스냅샷: 매 정각 자동 수집, 시청자 수·순위·채팅 저장
- 스트리머 상세 페이지: 랭킹 히스토리, 명예의 순간, 팔로우 추이 차트
- 소속사/그룹 카탈로그: 버튜버 소속사, 그룹 단위 정렬·집계
- 채팅 버스트 감지: "ㅋㅋㅋ", "대박", "레전드" 등 반응 급증 감지
- 명예의 전당: 역대 최고 순위, 이번 주 급상승, 단골 스트리머 섹션
기능적으로는 꽤 촘촘하게 만들었다고 생각했다.
SEO를 뒤늦게 들여다봤을 때
서비스를 어느 정도 완성하고 나서 "검색하면 나올까?" 하고 처음으로 SEO를 점검했다. 결과는 복잡했다.
잘 된 것들
동적 메타데이터는 챙겼다. 스트리머 상세 페이지(/streamer/[channelId])에는 스트리머 이름, 프로필 이미지, 통계를 담은 OG 태그와 Twitter Card가 있다. 카카오톡이나 트위터에 링크를 공유하면 미리보기가 제대로 나온다.
export async function generateMetadata({ params }) {
const streamer = await fetchStreamer(params.channelId);
return {
title: `${streamer.channelName} — Chzzk Watcher`,
openGraph: {
images: [{ url: streamer.channelImageUrl }],
type: 'profile',
},
twitter: { card: 'summary', ... }
};
}
사이트맵도 동적으로 생성했다. /sitemap.ts에서 DB에 있는 스트리머·소속사·그룹 200개 이상의 페이지를 자동으로 포함시킨다. Google이 크롤링할 루트는 열어뒀다.
robots.txt도 제대로 구성했다. 실시간 기능(/chat-monitor, /live-feed)처럼 색인될 필요 없는 페이지는 크롤링을 막았다.
GA4는 초기에 붙였다. Vercel Analytics와 함께 기본 페이지뷰는 잡히고 있었다.
놓친 것들
찬찬히 보니 구멍이 꽤 있었다.
1. JSON-LD 구조화 데이터가 없었다
검색 결과에서 리치 스니펫이 나오려면 JSON-LD가 필요하다. 스트리머 상세 페이지에 Person 스키마를, 메인 페이지에 WebSite 스키마를 넣었다면 검색 결과 노출 품질이 달라졌을 거다.
<!-- 넣었어야 했던 것 -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "스트리머 이름",
"url": "https://chzzk.naver.com/...",
"image": "프로필 이미지 URL"
}
</script>
2. 목록 페이지에 OG 이미지가 없었다
스트리머 목록(/streamer), 차트(/chart), 소속사 목록(/companies)처럼 트래픽이 집중되는 페이지에 OG 이미지가 없었다. SNS에 공유하면 이미지 없이 링크만 나온다. 브랜드 이미지 하나만 넣어도 CTR이 다르다.
3. 커스텀 이벤트 트래킹을 안 했다
GA4가 붙어있어도 기본 페이지뷰만 수집됐다. "스트리머 클릭", "소속사 필터 사용", "채팅 버스트 카드 클릭" 같은 유저 행동 데이터가 없으니 어떤 기능이 실제로 쓰이는지 알 수 없었다.
// 처음부터 했어야 했던 것
trackEvent('streamer_click', { channelId, source: 'hall_of_fame' });
trackEvent('filter_applied', { type: 'company', value: companyName });
4. 기능을 만들었지만 노출 구조를 안 만들었다
팔로우 추이 차트 기능을 개발하면서 "이 기능이 어떤 키워드로 검색되어야 하나?"를 한 번도 생각하지 않았다. "치지직 스트리머 팔로워", "치지직 구독자 추이" 같은 검색어가 있을 텐데, 그 키워드를 페이지 제목과 설명에 자연스럽게 넣는 작업을 하지 않았다.
마케팅을 빠뜨린 이유
돌아보면 이유가 명확하다.
기능 완성도가 먼저였다. "이 기능이 작동하는가?"는 체크했지만 "이 기능을 유저가 발견할 수 있는가?"는 묻지 않았다. 스냅샷 수집 크론, 채팅 버스트 감지 알고리즘, WebSocket 연결 관리 같은 기술적 문제를 푸는 게 더 흥미롭기도 했다.
"SEO는 나중에"가 생각보다 훨씬 늦어졌다. 페이지가 20개가 넘어가니 하나씩 뜯어고치는 게 처음부터 잘 짜는 것보다 훨씬 번거롭다.
유저 행동 데이터가 없으니 개선 방향을 모른다. 어드민으로 DB를 직접 보거나 서버 로그를 뒤지는 방식으로 확인하다 보니 "유저가 어디서 이탈하는지" 같은 질문에 답을 못 했다.
지금이라도 챙긴 것들
최근에 다시 점검하면서 몇 가지를 챙겼다.
팔로우 추이 기능을 만들면서 SEO를 같이 고민했다. GET /streamer/:channelId/follower-history 엔드포인트를 추가하면서 페이지 설명에 "팔로우 추이"라는 표현을 명시적으로 넣었다. 작은 차이지만 의식적으로 챙겼다는 게 다르다.
추적 요청 기능에는 CTA 카피를 먼저 썼다. "아직 추적하지 않는 스트리머입니다"보다 "팔로우 성장 그래프를 보고 싶다면 추적을 요청해주세요"가 유저에게 더 명확하다. 버튼을 만들기 전에 문구를 먼저 결정했다.
다음 프로젝트에서 바꿀 것들
기능 스펙을 정할 때 "검색 키워드"도 함께 정한다. 팔로우 차트 기능을 기획할 때 "치지직 구독자 추이", "치지직 팔로워 변화" 같은 검색어를 함께 정리하고, 그 표현이 페이지 제목과 설명에 들어가도록 설계한다.
커스텀 이벤트 트래킹은 기능 개발과 동시에 한다. 새 페이지나 기능을 배포할 때 GA4 이벤트 1~2개는 항상 같이 붙인다. 나중에 몰아서 하면 안 한다.
OG 이미지는 정적 페이지에도 넣는다. Next.js의 opengraph-image 파일 컨벤션을 쓰면 목록 페이지도 쉽게 커버된다.
JSON-LD는 상세 페이지 개발 때 기본으로 포함한다. 고유 URL이 있는 페이지는 구조화 데이터 없이 배포하지 않는다.
마무리
"SEO는 나중에"라고 미뤘을 때 실제로 잃은 건 유기적 유입 기회다. 광고 예산이 없는 사이드 프로젝트에서 SEO는 사실상 유일한 무료 마케팅 채널이다.
기능을 잘 만드는 것과 그 기능이 발견되게 만드는 건 다른 일이다. 둘 다 챙겨야 한다.
Chzzk Watcher는 현재도 운영 중이다. 치지직 스트리머의 랭킹 히스토리와 팔로우 추이가 궁금하면 둘러보자.
'개인프로젝트' 카테고리의 다른 글
| 웹) chzzk-watcher 웹버전을 만들다. (0) | 2026.03.24 |
|---|---|
| (기록용) 웹서비스들을 개발하면서 느낀 개인 생각 (0) | 2026.03.22 |
| (웹) Popoll 서비스를 만들다 (0) | 2026.03.17 |
| (익스텐션) 인기방송top10을 추적하는 chzzk-watcher를 만들다. (0) | 2026.03.07 |
| 웹) 서브컬처 게임 명조에 대한 사이트를 만들다. (0) | 2026.03.03 |