Giscus로 블로그에 댓글 기능 추가하기
블로그를 운영하다 보면 독자와 소통하고 싶다는 생각이 들 때가 있습니다. 글에 대한 피드백을 받기도 하고 궁금한 점을 질문으로 남겨주시는 분도 계시죠. 때로는 본문보다 댓글에서 더 좋은 방법을 알게 되기도 하고요.
문제는 정적 블로그에 댓글 기능을 붙이려면 별도 서버나 데이터베이스가 필요하다는 겁니다. Disqus 같은 서비스를 쓸 수도 있지만 광고가 붙고 사용자 추적도 신경 쓰이고요.
이럴 때 쓸 만한 도구가 Giscus입니다. GitHub Discussions를 댓글 저장소로 쓰는 오픈소스 프로젝트인데요, 별도 서버 없이 댓글 시스템을 만들 수 있어요.
이번 글에서는 Giscus가 뭔지 살펴보고 Astro 블로그에 연동하는 과정을 처음부터 끝까지 따라가 보겠습니다.
Giscus란?
Giscus는 GitHub Discussions 기반의 댓글 시스템입니다. 웹페이지에 스크립트 한 줄만 넣으면 방문자가 GitHub 계정으로 로그인해서 댓글을 남길 수 있어요.
어떻게 동작하는지 간단히 설명하자면요. 페이지가 로드될 때 Giscus가 GitHub Discussions 검색 API로 해당 페이지에 매핑된 Discussion을 찾습니다. 아직 없으면? 누군가 처음 댓글을 남기는 시점에 Giscus 봇이 Discussion을 알아서 만들어줘요. 방문자는 GitHub OAuth로 인증한 뒤 댓글을 작성하게 되는 거죠.
비슷한 도구로 Utterances가 있는데 이쪽은 GitHub Issues를 씁니다. Giscus는 Discussions를 쓰기 때문에 댓글과 코드 이슈가 뒤섞이지 않아서 저장소 관리가 수월하고요. 답글 달기나 이모지 리액션, 게으른 로딩도 기본으로 지원합니다.
Giscus의 장점을 정리하면 이렇습니다.
- 무료 — GitHub Discussions에 댓글을 저장하므로 비용이 전혀 없음
- 광고/추적 없음 — Disqus와 달리 사용자 데이터를 수집하지 않음
- 오픈소스 — 소스 코드가 공개되어 있고 셀프 호스팅도 가능
- 다국어 지원 — 한국어 포함 20개 이상 언어 지원
- 테마 — 다크 모드를 비롯한 여러 테마 제공, 커스텀 CSS 적용도 가능
- 마크다운 — GitHub Flavored Markdown으로 코드 블록도 깔끔하게 작성 가능
기술 블로그 독자라면 대부분 GitHub 계정이 있으니 별도 회원가입 없이 바로 댓글을 남길 수 있다는 것도 매력적이에요.
사전 준비
Giscus를 연동하기 전에 준비할 게 있습니다.
먼저 댓글을 저장할 GitHub 저장소가 필요해요. 블로그 소스 코드가 있는 저장소를 그대로 써도 되고 댓글 전용 저장소를 따로 만들어도 됩니다. 한 가지 주의할 점은 저장소가 공개(public) 상태여야 한다는 거예요. 비공개면 방문자가 Discussion을 볼 수 없거든요.
저장소가 준비됐으면 Discussions 기능을 켜야 합니다. Settings → General에서 아래로 스크롤하면 Features 섹션이 나오는데 거기서 Discussions 체크박스를 켜주세요.
그러면 저장소 상단 탭에 Discussions가 새로 생깁니다. 여기에 Giscus가 댓글을 저장하게 돼요.
Giscus 앱 설치
다음으로 GitHub에 Giscus 앱을 설치합니다. 이 앱이 있어야 Giscus 봇이 Discussion을 자동으로 만들고 관리할 수 있어요.
github.com/apps/giscus에 접속해서 Install 버튼을 누릅니다.
설치 범위를 고르는 화면이 나오면 Only select repositories를 선택하고 댓글에 쓸 저장소만 골라주세요. 굳이 모든 저장소에 설치할 필요는 없습니다.
설치가 끝나면 Giscus 앱이 해당 저장소의 Discussion을 읽고 쓸 수 있게 됩니다.
Giscus 설정
이제 giscus.app에 접속해서 스크립트를 설정할 차례입니다. 한국어 인터페이스도 지원하니 편하게 진행하면 돼요.
저장소 입력란에 사용자명/저장소명 형식으로 넣습니다.
예를 들어 DaleSeo/blog처럼요.
그러면 Giscus가 저장소 상태를 자동으로 확인해줍니다.
페이지와 Discussion 매핑 방식을 골라야 하는데 가장 무난한 건 pathname입니다.
URL 경로를 기준으로 Discussion을 매핑하기 때문에 URL만 안 바뀌면 댓글이 안정적으로 유지돼요.
Discussion 카테고리는 Announcements 타입을 추천합니다.
저장소 관리자와 Giscus 봇만 새 Discussion을 만들 수 있어서 누군가 임의로 Discussion을 생성하는 걸 막아주거든요.
나머지 옵션은 필요에 따라 조정하면 됩니다. 리액션 활성화, 입력 창 위치(댓글 위/아래), 게으른 로딩 여부 등을 설정할 수 있어요.
설정을 마치면 페이지 하단에 <script> 태그가 생성됩니다.
data-repo, data-repo-id, data-category, data-category-id 같은 값들이 들어 있는데 이 값들을 잘 기억해두세요.
<script
src="https://giscus.app/client.js"
data-repo="사용자명/저장소명"
data-repo-id="R_..."
data-category="Announcements"
data-category-id="DIC_..."
data-mapping="pathname"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="bottom"
data-theme="preferred_color_scheme"
data-lang="ko"
data-loading="lazy"
crossorigin="anonymous"
async
></script>
Astro 컴포넌트 만들기
giscus.app에서 받은 스크립트를 Astro 컴포넌트로 감싸두면 여러 곳에서 재사용하기 좋습니다.
src/components/Comments.astro 파일을 만들어 볼게요.
---
---
<section class="comments">
<script
src="https://giscus.app/client.js"
data-repo="사용자명/저장소명"
data-repo-id="R_..."
data-category="Announcements"
data-category-id="DIC_..."
data-mapping="pathname"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="bottom"
data-theme="preferred_color_scheme"
data-lang="ko"
data-loading="lazy"
crossorigin="anonymous"
async
is:inline></script>
</section>
<style>
.comments {
margin-top: var(--spacing-12);
}
</style>
is:inline 디렉티브를 빼먹으면 안 됩니다.
Astro는 기본적으로 <script> 태그를 번들링하려 하는데 Giscus 스크립트는 외부에서 로드되는 거라 원본 그대로 HTML에 들어가야 해요.
is:inline을 붙이면 Astro가 스크립트를 건드리지 않습니다.
이제 이 컴포넌트를 블로그 포스트 레이아웃에 추가하면 됩니다.
---
import Comments from "../components/Comments.astro";
// ... 기존 import들
---
<article>
<slot />
</article>
<Comments />
이렇게만 하면 모든 블로그 포스트 하단에 댓글 영역이 나타납니다.
다크 모드 지원
블로그에 다크 모드가 있다면 Giscus 테마도 같이 전환돼야 어색하지 않겠죠?
그런데 Giscus는 <iframe> 안에서 돌아가기 때문에 부모 페이지의 CSS 클래스가 바뀌는 걸 모릅니다.
postMessage로 iframe에 테마 변경을 직접 알려줘야 해요.
---
---
<section class="comments">
<script
src="https://giscus.app/client.js"
data-repo="사용자명/저장소명"
data-repo-id="R_..."
data-category="Announcements"
data-category-id="DIC_..."
data-mapping="pathname"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="bottom"
data-theme="light"
data-lang="ko"
data-loading="lazy"
crossorigin="anonymous"
async
is:inline></script>
</section>
<script is:inline>
function getGiscusTheme() {
return document.documentElement.classList.contains("dark-mode")
? "dark"
: "light";
}
function updateGiscusTheme() {
const iframe = document.querySelector("iframe.giscus-frame");
if (!iframe) return;
iframe.contentWindow.postMessage(
{
giscus: {
setConfig: {
theme: getGiscusTheme(),
},
},
},
"https://giscus.app",
);
}
// 다크 모드 토글을 감지
const observer = new MutationObserver(() => {
updateGiscusTheme();
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
// 페이지 로드 시 초기 테마 설정
document.addEventListener("DOMContentLoaded", () => {
const initialTheme = getGiscusTheme();
const script = document.querySelector(
'script[src="https://giscus.app/client.js"]',
);
if (script) {
script.setAttribute("data-theme", initialTheme);
}
});
</script>
<style>
.comments {
margin-top: var(--spacing-12);
}
</style>
핵심은 MutationObserver예요.
<html> 태그의 클래스 변경을 감시하다가 dark-mode 클래스가 추가되거나 제거되면 updateGiscusTheme()을 호출합니다.
이 함수가 Giscus iframe에 postMessage로 새 테마를 전달하는 거죠.
data-theme을 preferred_color_scheme 대신 light로 설정한 이유가 궁금하실 수 있는데요.
preferred_color_scheme은 OS 설정 기반으로 테마가 결정되기 때문에 블로그의 다크 모드 토글과 동기화가 안 될 수 있거든요.
직접 제어하는 게 확실합니다.
React 컴포넌트로 구현하기
Astro에서 React를 쓰고 있다면 Giscus의 공식 React 컴포넌트를 쓰는 방법도 있습니다.
먼저 패키지를 설치합니다.
bun add @giscus/react
npm install @giscus/react
그 다음 React 컴포넌트를 만듭니다.
import Giscus from "@giscus/react";
import { useEffect, useState } from "react";
export default function Comments() {
const [theme, setTheme] = useState("light");
useEffect(() => {
const isDark = document.documentElement.classList.contains("dark-mode");
setTheme(isDark ? "dark" : "light");
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains("dark-mode");
setTheme(isDark ? "dark" : "light");
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
return () => observer.disconnect();
}, []);
return (
<Giscus
repo="사용자명/저장소명"
repoId="R_..."
category="Announcements"
categoryId="DIC_..."
mapping="pathname"
strict="0"
reactionsEnabled="1"
emitMetadata="0"
inputPosition="bottom"
theme={theme}
lang="ko"
loading="lazy"
/>
);
}
Astro 레이아웃에서는 client:idle 디렉티브로 불러옵니다.
---
import Comments from "../components/Comments";
---
<Comments client:idle />
client:idle을 쓰면 브라우저가 유휴 상태일 때 컴포넌트를 로드하기 때문에 페이지 초기 로딩 성능에 영향을 주지 않습니다.
client:load를 쓰면 즉시 로드되고요.
React 컴포넌트 방식의 장점은 theme prop이 바뀌면 자동으로 재렌더링되기 때문에 postMessage 없이도 다크 모드 동기화가 된다는 점입니다.
Utterances에서 마이그레이션
기존에 Utterances를 쓰고 계셨다면 Giscus로 전환하기도 어렵지 않습니다. Utterances가 GitHub Issues에 저장한 댓글을 Discussions로 변환하면 되거든요.
방법은 간단합니다. GitHub 저장소에서 변환할 Issue를 열고 오른쪽 사이드바의 Convert to discussion을 클릭하세요. 카테고리를 고르면 Issue가 Discussion으로 바뀌면서 기존 댓글도 그대로 살아있습니다.
여러 개를 한꺼번에 변환하고 싶으면 Issues 목록에서 체크박스로 선택한 뒤 상단 드롭다운에서 Convert to discussion을 고르면 일괄 변환됩니다.
변환이 끝나면 Discussion 제목과 페이지 매핑이 맞는지 꼭 확인해주세요.
Utterances에서 pathname 매핑을 썼다면 Giscus에서도 같은 방식으로 선택하면 댓글이 자연스럽게 이어져요.
댓글 관리
Giscus로 달린 댓글은 GitHub Discussions에서 직접 관리할 수 있습니다. 저장소의 Discussions 탭에 가면 페이지별로 생성된 Discussion이 보여요.
부적절한 댓글은 GitHub에서 바로 삭제하거나 숨기면 되고 Discussion을 잠금(lock) 처리해서 특정 페이지의 댓글을 아예 막을 수도 있습니다.
GitHub 알림과도 연동되기 때문에 새 댓글이 달리면 이메일이나 알림으로 바로 확인할 수 있어요. 별도 관리자 대시보드 없이 GitHub 하나로 다 처리되는 셈이죠.
마치며
Giscus를 쓰면 정적 블로그에도 서버 없이 댓글 기능을 붙일 수 있습니다. GitHub Discussions에 댓글이 저장되니 데이터 소유권도 본인에게 있고 광고나 추적 걱정도 없어요.
설정도 생각보다 간단합니다. 저장소 준비하고 Giscus 앱 설치하고 giscus.app에서 설정값 받아서 컴포넌트에 넣으면 끝이에요. 다크 모드 동기화만 좀 신경 쓰면 제법 완성도 높은 댓글 기능이 만들어집니다.
기술 블로그를 운영하고 계시다면 독자와 소통하는 창구로 Giscus를 한번 써보시는 건 어떨까요?
This work is licensed under
CC BY 4.0