개발자라면 누구나 생각해보는 웹사이트 만들기. 개발자는 개인 블로그 사이트를 만들어 보려 한다.

정적인 블로그를 직접 운영하다 보면 한 번은 막히는 지점이 있습니다. 글은 파일로 써서 올리면 그만인데, 방문자가 남기는 것은 어디에 둘지가 애매합니다. 그 대표가 댓글입니다.

네이버 블로그나 티스토리, 구글의 Blogger처럼 블로그를 만들어 주는 서비스에서는 이런 고민을 할 일이 없습니다. 글을 저장하는 곳이 댓글도 함께 저장해 주니까요. 글쓴이는 댓글 기능을 켜기만 하면 됩니다.

직접 만든 블로그는 이야기가 다릅니다. 이 블로그는 글을 빌드할 때 모든 페이지를 미리 만들어 두고, 방문자에게는 만들어진 파일만 보여 줍니다. 서버도 데이터베이스도 없습니다. 빠르고 관리할 것이 적은 대신, 누군가 남긴 글을 받아 저장해 둘 곳이 처음부터 없습니다.

댓글 때문에 서버를 세워야 할까

가장 먼저 떠오르는 방법은 댓글용 서버와 DB를 직접 세우는 것입니다. 댓글을 받는 API를 하나 만들고, 데이터베이스에 저장하고, 글마다 불러와 보여 주면 됩니다.

그런데 따져 보면 손이 가는 일이 꽤 많습니다.

  • 서버와 DB를 늘 켜 두어야 하고, 그 비용이 매달 나갑니다.
  • 스팸과 악성 입력을 막아야 합니다.
  • 백업과 보안 업데이트를 챙겨야 합니다.
  • 댓글을 지우거나 고칠 관리 화면도 따로 만들어야 합니다.

하루에 댓글이 몇 개 달릴지도 모르는 블로그에 이걸 모두 들이는 건 낭비라고 생각했습니다. 나중에 블로그가 커지고 댓글로 하고 싶은 일이 생기면 그때는 직접 만들 생각도 있습니다. 하지만 지금은 아닙니다.

'개발자' 모자에 해진 후드티를 입은 새끼 고양이가 텅 빈 지갑을 펼친 채 눈물을 글썽이는 모습
AI 때문에 구독할 돈이 없는 가난한 개발자

찾아본 후보들

서버 없이 댓글을 붙이는 방법은 생각보다 여러 가지가 있었습니다.

Disqus는 가장 오래되고 널리 쓰이는 댓글 서비스입니다. 스크립트 한 줄이면 붙고 익명 댓글도 받을 수 있습니다. 다만 무료 요금제는 광고로 운영되어 댓글창 위에 광고가 붙고, 광고를 빼려면 유료 요금제를 써야 합니다. 광고 회사와 방문자 정보를 나누는 구조라는 점도 마음에 걸렸습니다.

Remark42, Isso 같은 오픈소스 댓글 서버도 있습니다. 익명 댓글이 되고 데이터가 온전히 내 것이라는 점이 좋습니다. 하지만 결국 어딘가에 서버를 띄워야 해서, 처음 고민으로 다시 돌아갑니다.

Firebase나 Supabase 같은 서버리스 DB로 직접 만드는 방법도 있습니다. 서버를 켜 둘 필요는 없지만, 로그인과 스팸 방지와 관리 화면을 전부 직접 만들어야 합니다.

utterances는 GitHub 저장소의 이슈(Issues)에 댓글을 저장합니다. 글 하나에 이슈 하나가 생기고, 댓글은 그 이슈의 댓글이 됩니다. 서버도 광고도 없습니다.

그리고 utterances에서 영감을 받아, 이슈 대신 GitHub Discussions를 쓰는 Giscus가 있었습니다. 이 블로그에는 Giscus를 쓰기로 했습니다.

방법댓글이 저장되는 곳내 서버익명 댓글광고
DisqusDisqus 서버필요 없음설정하면 됨무료 요금제에 붙음
Remark42 · Isso내가 띄운 서버필요됨없음
Firebase · Supabase서버리스 DB필요 없음만들기 나름없음
utterancesGitHub Issues필요 없음안 됨없음
GiscusGitHub Discussions필요 없음안 됨없음

Giscus는 어떻게 동작할까

GitHub 저장소에는 코드 말고도 Discussions라는 게시판을 켤 수 있습니다. 이슈가 "고쳐야 할 일"을 적는 곳이라면, Discussions는 질문과 공지와 잡담을 나누는 포럼입니다. 글타래를 만들고, 그 아래 댓글과 답글을 달 수 있습니다.

Giscus는 이 게시판을 댓글 저장소로 씁니다. 블로그 글 하나가 Discussions 글타래 하나와 짝을 이루고, 방문자가 남긴 댓글은 그 글타래의 댓글로 저장됩니다.

글을 열면 이런 순서로 일이 일어납니다.

  1. 글 아래에 붙여 둔 giscus 스크립트가 댓글창(iframe)을 만듭니다.
  2. 댓글창은 GitHub Discussions를 검색해서 이 글과 짝인 글타래를 찾습니다. 무엇으로 짝을 찾을지는 미리 정해 둡니다. 글 주소(pathname), 전체 URL, 글 제목 등을 고를 수 있습니다.
  3. 글타래가 있으면 그 댓글을 불러와 보여 줍니다. 로그인하지 않은 방문자도 댓글을 읽을 수 있습니다.
  4. 글타래가 없으면 아직 아무도 댓글을 달지 않은 글입니다. 첫 댓글이 달리는 순간 giscus 봇이 글타래를 자동으로 만듭니다.

댓글을 쓰는 길은 두 가지입니다. 댓글창에서 "GitHub으로 로그인"을 누르고 giscus 앱에 권한을 주면 블로그 안에서 바로 쓸 수 있습니다. giscus에 권한을 주기 싫다면 GitHub의 그 글타래로 가서 직접 써도 됩니다. 어느 쪽이든 같은 글타래에 쌓이므로 블로그에서 똑같이 보입니다.

그 밖에도 꽤 많은 것을 해 줍니다.

  • 댓글은 GitHub 방식 마크다운으로 씁니다. 코드 블록도 그대로 됩니다.
  • 답글, 이모지 반응, 정렬을 지원하고, 새 댓글이 달리면 새로 고치지 않아도 반영됩니다.
  • 한국어를 포함해 여러 언어로 댓글창 글자를 바꿀 수 있습니다.
  • 기본 테마가 여럿 있고, 직접 만든 CSS 파일도 테마로 쓸 수 있습니다.
  • 무료이고, 광고와 추적이 없습니다. MIT 라이선스 오픈소스라 원하면 giscus 자체를 직접 띄워 쓸 수도 있습니다.

댓글 데이터는 처음부터 끝까지 GitHub에 있습니다. 블로그를 다른 곳으로 옮기거나 다시 만들어도, 같은 저장소를 가리키기만 하면 댓글은 그대로 따라옵니다.

Giscus 더 많은 정보 확인하기 giscus.app 공식 사이트

아쉬운 점도 있습니다

가장 큰 단점은 댓글을 쓰려면 GitHub 계정이 있어야 한다는 것입니다. 익명 댓글은 받을 수 없습니다. 다만 2025년 7월부터 GitHub도 구글 계정으로 가입하고 로그인할 수 있게 되어서, 예전만큼 큰 장애물은 아닙니다. 구글 계정 하나로 몇 번 누르면 끝납니다.

그 밖에 알아 둘 것들입니다.

  • 댓글 저장소는 공개여야 합니다. Discussions에 쌓인 댓글은 GitHub에서도 누구나 볼 수 있습니다. 블로그 코드를 비공개로 두고 싶다면 댓글만 담을 공개 저장소를 따로 만들면 됩니다(아래에서 이렇게 합니다).
  • GitHub와 giscus.app에 기댑니다. 둘 중 하나가 멈추면 그동안은 댓글창도 멈춥니다. 이게 걱정되면 giscus를 직접 띄워 쓰는 방법이 있습니다.
  • 댓글은 iframe 안에 있습니다. 검색엔진이 댓글을 본문의 일부로 읽어 갈 거라고 기대하기는 어렵습니다.
  • 개발과 거리가 먼 독자에게는 낯설 수 있습니다. "GitHub으로 로그인"이라는 버튼 자체가 문턱처럼 느껴질 수 있습니다. 개발 이야기에는 문제가 없지만, 여행이나 카페 이야기처럼 다른 주제의 글에서는 계속 지켜보려고 합니다.
안경 쓴 남자가 GitHub 로고가 그려진 검은 공을 손바닥에 올려 이쪽으로 내밀며 씩 웃는 모습
필요하신가요?

준비: 댓글을 담을 저장소

Giscus를 쓰려면 저장소 쪽에 세 가지가 갖춰져야 합니다. 공개 저장소일 것, 그 저장소에 Discussions가 켜져 있을 것, 그리고 giscus 앱이 설치되어 있을 것.

이 블로그 코드는 비공개 저장소에 있어서, 댓글만 담을 공개 저장소를 따로 만들었습니다. 코드는 비공개로 두고 댓글만 공개되는 구조입니다.

  1. 댓글용 공개 저장소를 만듭니다.
  2. 저장소 Settings 화면의 Features 항목에서 Discussions를 켭니다.
  3. giscus 앱을 설치하고, 접근할 저장소로 방금 만든 저장소만 고릅니다.
  4. giscus.app 설정 화면에서 저장소 이름을 넣고 옵션을 고르면, 맨 아래에 붙여 넣을 스크립트가 만들어집니다.

설정 화면에서 고른 것들과 이유입니다.

옵션고른 값이유
짝 찾기(mapping)pathname글 주소로 짝을 찾는다. 도메인을 바꿔도 주소 뒷부분이 같으면 이어진다
정확히 일치(strict)켬제목이 비슷한 다른 글타래와 잘못 짝지어지는 것을 막는다
카테고리Announcements관리자만 글타래를 새로 만들 수 있어서, 방문자가 엉뚱한 글타래를 열 수 없다
반응끔이 블로그는 화면에 이모지를 쓰지 않는다
언어ko댓글창 글자를 한국어로

pathname으로 짝을 찾는다는 건, 글 주소를 바꾸면 그 글의 댓글과 연결이 끊긴다는 뜻이기도 합니다. 글 주소는 처음 정할 때 신중하게 정하는 편이 좋습니다.

붙이기: 스크립트 한 줄에서 시작

giscus.app이 만들어 주는 것은 이런 스크립트 태그입니다. 댓글창이 들어갈 자리에 붙이면 그 자리에 댓글창이 생깁니다.

<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="1"
  data-reactions-enabled="0"
  data-input-position="bottom"
  data-theme="transparent_dark"
  data-lang="ko"
  data-loading="lazy"
  crossorigin="anonymous"
  async
></script>

data-repo-id와 data-category-id는 설정 화면이 채워 주는 값입니다. 정적 HTML 블로그라면 이것으로 끝입니다.

Next.js에서는 컴포넌트로

이 블로그는 Next.js로 만들어서, 스크립트를 직접 적는 대신 컴포넌트가 스크립트를 만들어 넣게 했습니다. 이때 신경 쓴 것이 두 가지입니다.

하나, 화면에 가까워질 때만 불러오기. giscus 스크립트는 iframe을 만들고 GitHub에 요청을 보냅니다. 글을 열자마자 불러오면 본문과 상관없는 요청이 첫 화면에 끼어듭니다. 설정의 data-loading="lazy"는 iframe만 늦게 불러오고 스크립트는 바로 받으므로, 스크립트까지 늦추려고 댓글 칸이 화면 아래 400px 안으로 들어올 때 스크립트를 넣었습니다.

const NEAR = '400px';
 
useEffect(() => {
  const container = box.current;
  if (!container) return undefined;
  const script = document.createElement('script');
 
  const observer = new IntersectionObserver(
    (entries) => {
      if (!entries.some((entry) => entry.isIntersecting)) return;
      observer.disconnect();
      script.src = 'https://giscus.app/client.js';
      script.crossOrigin = 'anonymous';
      script.async = true;
      Object.assign(script.dataset, GISCUS_CONFIG, { theme: GISCUS_THEME_URL });
      script.onerror = () => setStatus('failed');
      container.append(script);
    },
    { rootMargin: NEAR },
  );
  observer.observe(container);
 
  return () => {
    observer.disconnect();
    container.replaceChildren();
  };
}, []);

GISCUS_CONFIG에는 위 스크립트의 data- 값들을 그대로 담아 두었습니다. script.dataset에 넣으면 repoId는 data-repo-id처럼 알아서 바뀌어 붙습니다.

둘, 글을 옮겨 다닐 때 댓글창 갈아 끼우기. Next.js는 페이지를 옮길 때 화면 전체를 새로 불러오지 않습니다. 그래서 예전 블로그에서는 다른 글로 넘어가면 이전 댓글창이 남아 댓글창이 두 개가 되는 문제가 있었습니다. 이번에는 부르는 쪽에서 글 주소를 key로 넘겨, 글이 바뀌면 컴포넌트가 아예 새로 만들어지게 했습니다. 정리는 위 코드 끝의 replaceChildren() 한 줄이면 됩니다.

<Comments key={slug} />

댓글 칸에는 상태를 세 가지 두었습니다. 불러오는 동안은 "댓글을 불러오고 있습니다.", 스크립트를 받지 못하면 GitHub Discussions로 가는 링크, 자바스크립트가 꺼져 있으면 같은 링크를 noscript로 보여 줍니다.

참고로 첫 댓글이 달리기 전에는 개발자 도구 콘솔에 Discussion not found 404가 찍힙니다. 짝인 글타래가 아직 없어서 나오는 정상 응답이니 놀라지 않아도 됩니다.

디자인: 사이트 색에 맞추기

기본 테마 가운데 transparent_dark가 바탕이 비쳐서 이 블로그에 가장 잘 어울렸습니다. 그래도 초록색 "GitHub으로 로그인" 버튼만은 사이트와 따로 놀았습니다.

Giscus는 data-theme에 테마 이름 대신 CSS 파일 주소를 받을 수 있습니다. GitHub 다크 테마의 색 변수를 가져와서 바탕, 글자, 선, 버튼 색만 사이트 색으로 바꾼 파일을 만들었습니다.

main {
  --color-fg-default: #eeece7;
  --color-border-default: #34383e;
  --color-canvas-default: #0000;
  --color-canvas-inset: #101216;
 
  /* 초록 대신 사이트 포인트 색 */
  --color-btn-primary-bg: #e5c58f;
  --color-btn-primary-text: #08090b;
  --color-accent-fg: #e5c58f;
}

이 파일은 giscus.app 쪽 iframe이 다른 주소에서 불러가기 때문에, 응답에 CORS 헤더를 붙여 둡니다.

// next.config.mjs
headers: async () => [
  {
    source: '/giscus-theme.css',
    headers: [{ key: 'Access-Control-Allow-Origin', value: '*' }],
  },
],

로컬에서는 테마가 안 보였던 이유

처음에는 테마 주소를 지금 열린 사이트 기준(location.origin)으로 만들었습니다. 로컬 개발 서버에서는 http://localhost:3000/giscus-theme.css가 되는 식입니다. 그런데 로컬에서는 테마가 전혀 적용되지 않았습니다.

콘솔에는 CORS 정책에 막혔다는 메시지가 찍혔는데, 헤더 문제가 아니었습니다. 메시지 끝에 "loopback 주소 공간에 접근할 권한이 없다"는 문구가 붙어 있었습니다. 크롬은 공개 사이트가 localhost 같은 로컬 주소로 요청하는 것을 막는데, giscus.app 안의 iframe이 바로 그 공개 사이트였던 것입니다.

결국 테마 주소는 개발 환경에서도 늘 운영 주소를 보게 했습니다. 대신 색을 바꾸면 한 번 배포해야 로컬에서도 바뀐 색이 보입니다.

복잡하신가요?

정리

  • 정적 블로그는 댓글을 저장할 곳이 없습니다. 그렇다고 댓글 몇 개를 위해 서버와 DB를 세우는 건 지금 규모에는 과합니다.
  • Giscus는 GitHub Discussions를 댓글 저장소로 써서, 서버 없이 무료로, 광고 없이 댓글을 붙여 줍니다.
  • GitHub 계정이 있어야 댓글을 쓸 수 있지만, 구글 계정으로도 가입되니 문턱은 낮아졌습니다.
  • 블로그 저장소가 비공개라면 댓글 전용 공개 저장소를 따로 두면 됩니다.
  • 댓글창은 가까워질 때만 불러오고, 테마 CSS로 사이트 색에 맞출 수 있습니다.

혹시 정적 블로그를 만들어 보고 있다면, 댓글이 필요해지는 순간 서버부터 세우기 전에 Giscus 같은 도구를 먼저 써 보시길 권합니다. 스크립트 한 줄로 시작해서, 필요할 때 조금씩 손보면 됩니다.

이 글 아래 댓글창이 위에 적은 설정 그대로입니다. 궁금한 점이 있으면 남겨 주세요.

이 글은 예전 블로그에 썼던 글(2025년 12월)을 지금 블로그에 맞게 고쳐 옮긴 것입니다.