Next.js 블로그 만들기 — giscus로 댓글 기능 추가

5 min read
giscusGitHub댓글

댓글 기능이 필요한 이유

블로그에 댓글이 없으면 일방적인 소통이 된다. 독자의 피드백을 받을 수 있고, 잘못된 내용을 지적받을 수도 있다. 하지만 댓글 시스템을 직접 만들려면 DB, 인증, 스팸 필터링 등 생각보다 해야 할 일이 많다.

정적 블로그에서 쓸 수 있는 서드파티 댓글 시스템을 비교해봤다:

서비스기반장점단점
Disqus자체기능 풍부무겁고, 광고 있음
utterancesGitHub Issues가벼움답글, 리액션 없음
giscusGitHub Discussions답글, 리액션, 다크모드, 지연 로딩GitHub 계정 필요

개발 블로그 독자 대부분은 GitHub 계정이 있으니, giscus가 가장 적합하다고 판단했다.

giscus란?

giscus는 utterances에서 영감을 받은 오픈소스 댓글 위젯이다. GitHub Discussions를 댓글 저장소로 사용한다.

핵심 특징:

  • 무료 — 오픈소스, 서버 비용 없음
  • 답글 지원 — Issues와 달리 Discussions는 스레드 형태의 답글이 가능
  • 리액션 — 👍, ❤️ 등 이모지 리액션
  • 다크모드 — 테마 전환 API 제공
  • 지연 로딩 — 뷰포트에 들어올 때 로딩

사전 설정

1. GitHub Discussions 활성화

레포 Settings → General → Features에서 Discussions를 체크한다. CLI로도 가능하다:

gh repo edit terajh/tera-log --enable-discussions

2. giscus 앱 설치

github.com/apps/giscus에서 giscus 앱을 레포에 설치한다. 이걸 해야 giscus가 Discussions에 접근할 수 있다.

3. 레포 정보 확인

giscus 설정에 필요한 repo-idcategory-id를 가져온다:

gh api graphql -f query='{
  repository(owner: "terajh", name: "tera-log") {
    id
    discussionCategories(first: 10) {
      nodes { id name }
    }
  }
}'

응답에서 repository.id와 "General" 카테고리의 id를 사용한다.

구현

Comments 컴포넌트

giscus 공식 사이트에서 생성해주는 <script> 태그를 그대로 쓸 수도 있지만, Next.js에서는 React 컴포넌트로 래핑하는 것이 더 깔끔하다.

src/components/post/Comments.tsx
'use client'
 
import { useEffect, useRef } from 'react'
import { useTheme } from 'next-themes'
 
export default function Comments() {
  const containerRef = useRef<HTMLDivElement>(null)
  const { resolvedTheme } = useTheme()
 
  useEffect(() => {
    const container = containerRef.current
    if (!container) return
 
    // 이미 로드된 경우 — 테마만 변경
    const existingIframe = container.querySelector<HTMLIFrameElement>(
      'iframe.giscus-frame'
    )
 
    if (existingIframe) {
      existingIframe.contentWindow?.postMessage(
        {
          giscus: {
            setConfig: {
              theme: resolvedTheme === 'dark'
                ? 'dark_dimmed'
                : 'light',
            },
          },
        },
        'https://giscus.app'
      )
      return
    }
 
    // 최초 로드
    const script = document.createElement('script')
    script.src = 'https://giscus.app/client.js'
    script.setAttribute('data-repo', 'terajh/tera-log')
    script.setAttribute('data-repo-id', 'YOUR_REPO_ID')
    script.setAttribute('data-category', 'General')
    script.setAttribute('data-category-id', 'YOUR_CATEGORY_ID')
    script.setAttribute('data-mapping', 'pathname')
    script.setAttribute('data-reactions-enabled', '1')
    script.setAttribute('data-input-position', 'bottom')
    script.setAttribute(
      'data-theme',
      resolvedTheme === 'dark' ? 'dark_dimmed' : 'light'
    )
    script.setAttribute('data-lang', 'ko')
    script.crossOrigin = 'anonymous'
    script.async = true
    container.appendChild(script)
  }, [resolvedTheme])
 
  return <div ref={containerRef} className="mt-16" />
}

여기서 핵심은 다크모드 전환 처리다.

다크모드 자동 전환

useEffect의 의존성 배열에 resolvedTheme이 들어있다. 테마가 바뀔 때마다:

  1. 이미 iframe이 있으면 → postMessage로 테마만 변경
  2. 아직 없으면 → 새로 <script>를 생성

giscus는 iframe 기반이기 때문에 직접 DOM을 조작할 수 없다. 대신 postMessage API로 설정을 변경할 수 있다. 이 방식 덕분에 블로그의 테마 토글 버튼을 누르면 댓글 위젯의 테마도 즉시 바뀐다.

페이지에 적용

포스트 상세 페이지의 본문 아래에 배치한다:

src/app/posts/[slug]/page.tsx
export default async function PostPage({ params }) {
  // ... post, content 로딩
  return (
    <article>
      <header>...</header>
      <PostContent>{content}</PostContent>
      <div className="mx-auto max-w-3xl px-4">
        <Comments />
      </div>
    </article>
  )
}

max-w-3xl로 본문과 동일한 폭으로 맞춰준다.

결과

댓글 기능이 정상 동작하는지 확인할 포인트:

  • GitHub 로그인 후 댓글 작성 가능
  • 작성된 댓글이 레포의 Discussions 탭에 저장됨
  • 다크/라이트 모드 전환 시 댓글 위젯도 테마 변경
  • 리액션, 답글 기능 동작

관심 있을 만한 포스트