새벽 여섯 시간, 재방문 루프와 대시보드를 동시에 닫다
목차
자정 직후, 무엇부터 잡을지 결정하는 순간
자정을 넘기고 나서 슬랙을 끄고 터미널만 켜두면 이상하게 집중이 잘 된다. 오늘 새벽 시간도 그랬다. 큰 우선순위는 이미 전날 밤에 정해져 있었다. kpopdex에 "재방문하게 만드는 요소"가 너무 없다는 게 오랫동안 찜찜했던 부분이었고, 어드민 대시보드는 사이트가 49개로 늘어나면서 헬스 현황을 한눈에 보기가 점점 어려워지고 있었다. 두 개 모두 더 이상 미루면 기술 부채가 쌓이는 게 아니라 운영 감각 자체가 무뎌진다. 그래서 이 두 갈래를 동시에 치기로 했다.
kpopdex 재방문 기능 묶음 - Web Push, 기념일, 공유 버튼
이 시간대에서 가장 덩치가 컸던 작업이다. "사용자가 팔로우한 그룹의 컴백이 생기면 알려주는 구조"를 만들려면 세 레이어가 맞물려야 한다.
1. 브라우저 쪽 - 서비스워커 + 토글 UI
public/sw.js에 서비스워커를 올리고, PushToggle.astro 컴포넌트가 구독/해제를 담당한다. FollowingPage.astro 안에 토글을 심어서 팔로우 맥락에서 바로 켤 수 있게 했다. Push API는 브라우저 권한 다이얼로그가 있어서 UX가 애매한데, 토글을 팔로우 목록과 같은 화면에 붙이면 "내가 신경 쓰는 것들에 대한 알림"이라는 맥락이 자연스럽게 연결된다.
2. API 레이어 - 구독 엔드포인트
src/pages/api/push-subscribe.ts에서 구독 객체를 받아 저장한다. 여기서 주의했던 건 토큰과 브라우저 스토리지 사용 고지였다. 나중에 프라이버시 정책을 건드릴 것을 알고 있었기 때문에, 구독 저장 로직을 먼저 확정하고 정책 문구를 맞춰 쓰는 순서로 갔다.
3. 발송 레이어 - push_send.mjs
kpopdex/bot/push_send.mjs를 새로 만들었다. 컴백 일정이 확정되면 이 스크립트가 구독자들에게 웹 푸시를 보내는 구조다. 봇 레이어이면서 동시에 팬 서비스의 핵심이라, 메시지 포맷을 어떻게 할지 잠깐 고민했다. 너무 알림 스팸처럼 느껴지면 오히려 구독 해제를 유도하니까, 발송 조건은 보수적으로 잡았다.
기념일 카운트업은 조금 다른 맥락이다. Anniversary.astro와 FollowCountdown.astro가 데뷔 기념일, 멤버 생일을 D+N 형식으로 보여준다. "오늘이 이 그룹 데뷔 N주년입니다"라는 카드가 하나 있는 것만으로도 방문 이유가 생긴다. 공유 버튼도 같은 철학에서 나왔다. 컴백 D-day를 공유할 수 있으면 트래픽이 외부에서 들어올 수 있다. GroupPage.astro와 MemberPage.astro 양쪽에 모두 박았다.
이 기능 묶음을 마무리한 뒤 바로 PolicyPage.astro와 src/i18n/policy.ts를 열었다. 푸시 토큰과 localStorage 사용을 8개국어로 고지에 추가해야 했다. 이건 기능 개발이 먼저고 정책이 뒤따라오는 게 아니라, 실제로 스토리지를 쓰는 순간 정책도 동시에 업데이트해야 하는 것이다. 새벽에 이걸 빼먹으면 나중에 법무 검토할 때 다시 돌아오게 된다.
어드민 대시보드 - 49개 사이트를 어떻게 한 화면에 담나
솔직히 이게 더 지저분한 작업이었다. 사이트가 늘어날수록 대시보드가 "있긴 한데 쓸모없는" 상태가 되어가고 있었다. 문제는 두 가지였다.
첫째, 사이트 헬스 분류가 깨져 있었다. 25개 사이트가 미분류로 빠져 있었는데, dashboard-stats.ts에서 헬스 체크 로직이 특정 발행 타입을 인식 못 하고 있었다. opsvoro 발행을 추가하고, 나머지 24개는 정적 사이트로 분류했다. 이 수정으로 전체 49개가 발행정상 N/M, 정적, 기타, 총합 형태로 카드에 다 보이게 됐다.
둘째, KPI 그리드 정렬이 어색했다. 카드가 9개인데 균등하게 배치하면 마지막 줄이 1개만 덩그러니 남는다. 5+4 두 줄로 정렬했다. 사소하지만 대시보드처럼 매일 보는 화면은 레이아웃 어색함이 은근히 누적돼서 신경 쓰인다.
AdSense 부분이 이번에 좀 크게 바뀌었다. 계정이 2개로 늘었다. adsense-status-check.py가 처음에 hedvion.com 단일 도메인만 보고 있었는데, 첫 번째 계정 6개 사이트 전체로 확장하고, 두 번째 계정(dev@hedvion, pub-1135)까지 추가해서 총 11개 사이트를 추적하게 했다. 대시보드 카드도 두 계정을 같이 표시하도록 수정했다.
| 항목 | 변경 전 | 변경 후 |
|---|---|---|
| 사이트 헬스 분류 | 25개 미분류 | 49개 전체 분류 |
| AdSense 추적 | 1계정 1도메인 | 2계정 11사이트 |
| KPI 카드 레이아웃 | 비균등 | 5+4 정렬 |
dashboard-stats.ts에서 외부 신선도 날짜를 Date 강제 변환하는 수정도 했다. 날짜 형식이 예상과 다르게 들어오면 500이 터질 수 있는 지점이었는데, 잠재적 에러라서 실제로 발생하기 전에 잡았다. 중복 catch도 같이 정리했다. 이런 작업은 눈에 안 띄지만, 새벽에 갑자기 대시보드가 죽으면 그 원인 찾는 데 시간을 잡아먹으니까 미리 막는 게 낫다.
GSC 평균 순위 카드, 오늘 발행 요약 카드, 사이트 헬스 요약 카드를 새로 추가하면서 대시보드가 처음으로 "운영 현황을 실제로 읽을 수 있는" 화면이 됐다. 발행 카드와 사이트 헬스 카드에 클릭 링크도 달았다. 카드를 눌렀을 때 아무 일도 안 생기면 대시보드가 정적인 느낌이 되어버리는데, 연결이 있으면 자연스럽게 더 파고들게 된다.
AdSense 론칭과 SEO 국제화 - cpsrush 개시
cpsrush에 pub-1135425661100289를 올렸다. public/ads.txt, 로더 스크립트, og/twitter 메타 태그까지 한 번에 붙였다. AdSense 검토 중 상태인데, 이 단계에서 ads.txt가 정확히 있어야 검토가 진행된다. 문서도 sites-detail.md에 개시 상태를 바로 반영했다.
도구 가이드 본문을 7개 언어로 현지화한 것도 이 시간에 끼워 넣었다. ToolView.astro와 src/i18n/guides.ts인데, 기존에 영어로만 있던 가이드 본문이 es, pt, fr, de, it, ja, zh로 나간다. 검색 유입을 언어별로 분산시키는 게 목적이다. kpopdex에서 8개국어 번역을 반복하다 보니 i18n 구조가 손에 익어서 작업 자체는 빠르게 됐다.
kpopdex 번역 현황 기록
문서 커밋이 여러 개 들어와 있는데, 이건 단순 docs 정리가 아니라 상태 기록이다. 2번 그룹 요약 137개를 8개국어로 다 돌렸고 i18n 항목이 959개 생성됐다. 신규 칼럼 24개도 8개국어로 완료해서 504개가 추가됐다. 멤버 bio는 688개 중 50개 완료 상태라 아직 갈 길이 멀다.
CLAUDE.md에 정책 페이지가 legal 타입이 아니라 cms_page로 실측 정정한 것도 이 흐름에서 나왔다. 문서에 적힌 것과 실제 코드베이스가 다를 때, 실측이 단일 진실이다. 이걸 안 고쳐놓으면 나중에 다른 작업자가 엉뚱한 타입으로 쿼리 짜다가 시간을 날린다.
블로그 봇 프롬프트 튜닝 - em-dash와 카테고리 판정
scripts/bulk_seed.py에 두 가지 룰을 추가했다.
첫째는 em-dash(-)를 쓰지 말라는 규칙이다. AI가 생성한 텍스트에서 em-dash가 지나치게 자주 나오면 글에서 AI 냄새가 난다. 기술 블로그 회고체에서 em-dash는 사실 잘 쓰지 않는 표현이기도 하고, 일반 하이픈이나 쉼표로 충분히 끊을 수 있다.
둘째는 카테고리 가중 판정이다. 기존에는 dev/automation/side를 단순하게 분류했는데, dev가 흔하다 보니 automation이나 side 작업이 꽤 비중을 차지해도 dev로 묻혀버리는 경우가 생겼다. 그래서 automation이나 side 작업이 본문 한 단락 이상 차지할 만큼 비중 있으면 dev보다 그쪽을 우선 대표로 뽑도록 했다.
발행 텀도 3시간에서 6시간으로 늘렸고, 분량을 4000-6000자로 올렸다. 짧게 끝내는 글은 회고라기보다 메모에 가깝다. 배경이 뭔지, 왜 그 선택을 했는지, 어디서 막혔는지가 담겨야 나중에 돌아봤을 때 의미 있는 기록이 된다.
프롬프트에서 "시간 하드코딩 제거"도 같이 했다. 예전에는 발행 시간대를 프롬프트에 고정 문자열로 박아둔 게 있었는데, 봇이 실행되는 시각을 동적으로 넘기는 방식으로 바꿨다. 당연히 이렇게 해야 하는데 처음엔 빠르게 만들다 보니 하드코딩으로 시작했고, 이번에 청소했다.
새벽 여섯 시간이 남긴 것
돌아보면 이 시간에 상당히 많은 영역을 건드렸다. kpopdex 재방문 루프 완성, 대시보드 49개 사이트 헬스 완전 분류, AdSense 2계정 체계화, SEO 국제화, 프라이버시 정책 고지, 블로그 봇 튜닝. 이렇게 쓰면 산만해 보이지만, 실제로는 "지금 당장 사용자에게 보이는 것"과 "운영자가 매일 봐야 하는 것"을 동시에 정리한 시간이었다.
psych 테스트 자동 생성 3개가 이 시간 안에 커밋된 것도 있다. detective-solving-style, one-on-one-vs-group-friend, pcbang-type - 이건 daily 자동 파이프라인이 만들어준 것이라 별도 작업은 아니었지만, 커버 이미지까지 딸려서 올라온 걸 보면 파이프라인이 제대로 돌고 있다는 확인이기도 하다.
새벽 6시간은 짧다. 그 안에 뭘 마무리할지 초반에 결정을 잘 해야 한다. 오늘은 재방문 루프가 가장 우선이었고, 대시보드는 "이거 안 고치면 오늘 오후에 또 본다"는 부채 감각으로 끼워 넣었다. 두 가지를 다 닫았으니 아침에 커피 한 잔 하고 싶다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.