요구 사항
댓글 시스템에 원한 건 세 가지다. 서버를 두지 않을 것, 댓글 데이터가 내 GitHub에 남을 것, 새 댓글이 달리면 알림이 올 것. Disqus는 광고와 추적이 싫고, 직접 DB를 운영하기엔 정적 블로그의 취지와 어긋난다. 남은 선택지는 GitHub Issue를 댓글 저장소로 쓰는 utterances였다.
구조
흐름은 두 저장소를 오간다.
- 방문자가 포스트 하단의 utterances 위젯으로 댓글을 쓴다. GitHub 계정 로그인이 필요하다.
- utterances가 공개 저장소
kese/kese-comments에 포스트별 Issue를 만들고, 댓글을 이슈 댓글로 저장한다. issue_comment.created이벤트가 kese-comments의 GitHub Actions 워크플로우를 깨운다.- 워크플로우가 이슈 제목(
/blog/<slug>/)에서 slug를 뽑고, 비공개kese/kesekr에서 포스트와content/authors.yaml의 작성자 매핑을 읽는다. - Gemini 키가 설정되어 있으면 댓글 분류·독성 레벨(0-5)·추천 답변을 생성하고, Level 4 이상은 댓글을 즉시 삭제한다.
- 결과는 비공개 kesekr에 알림 Issue로 쌓인다. 같은 포스트의 알림 Issue가 열려 있으면 새로 만들지 않고 댓글로 합친다.
올리브영에서 가져온 것
이 자동화는 oy-techblog/tech-blog-comment를 이식한 것이다. 원본은 조직 블로그용이라 작성자를 member.yaml에서 찾고 별도 조직 저장소에 알림을 둔다. 개인 블로그로 옮기며 작성자 매핑은 content/authors.yaml로 줄였고, 알림 대상은 비공개 블로그 저장소 자체로 향하게 했다. 원본의 workflow가 step-level bot 체크로는 실제 잡을 건너뛰지 못하는 문제가 있어 job-level 조건으로 고쳤다.
공개 범위도 이 과정에서 자연스럽게 갈렸다. 댓글 원문은 공개 kese-comments에, 알림·AI 분석·월별 메트릭은 비공개 kesekr에 남는다.
왜 비공개가 아니라 공개 저장소인가
utterances는 로그인하지 않은 방문자도 댓글을 읽을 수 있어야 해서 저장소가 반드시 공개다. 처음엔 비공개 저장소와 승인 큐를 Netlify Functions로 만들었다. 그런데 댓글 데이터는 어차피 공개 글 아래에 달리는 공개 텍스트고, 승인이 필요한 건 표시가 아니라 대응이었다. 그래서 기본을 utterances로 바꿨다.
다만 승인 큐 설계가 완전히 버린 건 아니다. 함수 백엔드는 GATSBY_COMMENTS_PROVIDER=functions로 그대로 살아 있어서, 나중에 '댓글이 달려도 바로 공개하지 않는' 운영이 필요해지면 되돌릴 수 있다. 트레이드오프를 둘 다 보유한 채 기본값만 바꾼 셈이다.

댓글
GitHub 계정으로 로그인해 댓글을 남길 수 있습니다. 댓글은 kese-comments 저장소 이슈로 공개 저장됩니다.