この記事について

この記事の内容および実装は Claude Code を使用して開発・記述し、人間が検証・修正しています。

記事内でも触れているとおり、本 blog に コメント機能を追加 しました。GitHub のアカウントがあれば投稿できますので、ぜひお試しください。

はじめに

この blog は Hugo + PaperMod テーマで運営しているが、長い間コメント機能がなかった。

静的サイトジェネレーターの宿命として、コメントのようなインタラクティブな機能は外部サービスに頼るしかない。以前の記事 (Hugo で blog サイトの作成: 経緯編) でも少し触れたが、無償で使えるコメントサービス、例えば Disqus は無料プランだと広告が出たりと、微妙なものしかなかった。

最近 giscus というサービスを知り、改めてコメント機能を実装することにした。

giscus は GitHub Discussions をバックエンドに利用したコメントシステムである。

  • 完全無料・広告なし — GitHub のサービスを直接利用するため、中間サービス層がない
  • スパムに強い — コメント投稿には GitHub アカウントが必要なため、ボットや匿名スパムが入り込みにくい
  • オープンソースgithub.com/giscus/giscus
  • ダークモード対応 — テーマのカラースキームに追従可能
  • 多言語対応 — 日本語 UI にも対応

GitHub アカウントの保有が前提になるため、技術系ブログとの相性が特によい。

全体の構成

この実装で変更・追加するのは以下のとおり。

種別 対象 内容
GitHub blog-comments リポジトリ(新規作成) Discussions のバックエンド
Hugo フォルダ layouts/partials/extend_footer.html(新規作成) giscus スクリプトの埋め込み
Hugo フォルダ assets/css/extended/custom.css(追記) コメント欄の幅調整

blog の GitHub ソースリポジトリには手を入れない。

実装イメージ

giscus 実装イメージ

GitHub CLI の準備

GitHub CLI (gh コマンド) をインストールしていない場合は下記の手順で使えるようにしておく。

  • インストール

    OS ごとにインストール方法が異なる。

    # Windows (winget)
    winget install GitHub.cli
    
    # macOS (Homebrew)
    brew install gh
    
    # Linux (apt: Ubuntu / Debian 系)
    sudo apt install gh
    
    # Linux (dnf: Rocky / AlmaLinux / CentOS / RHEL 系)
    sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
    sudo dnf install gh
    

    その他のインストール方法は GitHub CLI の公式ページ を参照。

  • GitHub アカウントでログイン

    インストール後、以下のコマンドで GitHub アカウントと紐付ける。

    gh auth login
    

    対話形式で進む。GitHub.comHTTPSLogin with a web browser を選択すると、ブラウザが開いて認証が完了する。

  • ログイン確認

    gh auth status
    

    Logged in to github.com と表示されれば準備完了だ。

実装手順

1. GitHub にコメント専用リポジトリを作成

gh repo create blog-comments --public --description "Comments for your blog" --add-readme
gh api repos/自分のGitHubユーザー名/blog-comments --method PATCH --field has_discussions=true

2. GitHub リポジトリに giscus アプリをインストール

  1. github.com/apps/giscus を開く
  2. Install ボタンをクリック
  3. インストール先のアカウントを選択する(Organization ではなく個人アカウントを選ぶ)
  4. Only select repositories を選び、blog-comments のみを指定して Install をクリック
リポジトリは絞って許可する
All repositories を選ぶとアカウント内のすべてのリポジトリへのアクセスを許可することになる。blog-comments のみに絞ること。

3. GitHub から giscus 用の設定パラメータを取得

GraphQL でクエリを投げて調査する。

gh api graphql -f query='
{
  repository(owner: "自分のGitHubユーザー名", name: "blog-comments") {
    id
    discussionCategories(first: 10) {
      nodes { id name }
    }
  }
}'

出力例:

{
  "data": {
    "repository": {
      "id": "R_kgDORxxxxxxxx",
      "discussionCategories": {
        "nodes": [
          { "id": "DIC_kwDORxxxxxxxxxxxxxxxx", "name": "Announcements" },
          { "id": "DIC_kwDORxxxxxxxxxxxxxxxx", "name": "General" },
          ...
        ]
      }
    }
  }
}

repositoryid と、Announcements カテゴリの id を控えておく。

4. Hugo プロジェクトに layouts/partials/extend_footer.html を作成

自分のGitHubユーザー名R_kgDORxxxxxxxxDIC_kwDORxxxxxxxxxxxxxxxx は手順 3 で取得した実際の値に置き換える。

{{- if and .IsPage (ne (.Param "comments") false) }}
<div class="giscus-frame-container">
<script id="giscus-cfg"
        src="https://giscus.app/client.js"
        data-repo="自分のGitHubユーザー名/blog-comments"
        data-repo-id="R_kgDORxxxxxxxx"
        data-category="Announcements"
        data-category-id="DIC_kwDORxxxxxxxxxxxxxxxx"
        data-mapping="title"
        data-strict="1"
        data-reactions-enabled="1"
        data-emit-metadata="0"
        data-input-position="bottom"
        data-theme="light"
        data-lang="{{ .Language.Lang }}"
        crossorigin="anonymous"
        async>
</script>
</div>
<script>
(function () {
    var getTheme = function () {
        return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light';
    };
    var cfg = document.getElementById('giscus-cfg');
    if (cfg) cfg.dataset.theme = getTheme();

    function postTheme(theme) {
        var f = document.querySelector('iframe.giscus-frame');
        if (f) f.contentWindow.postMessage(
            { giscus: { setConfig: { theme: theme } } },
            'https://giscus.app'
        );
    }
    window.addEventListener('message', function onReady(e) {
        if (e.origin !== 'https://giscus.app') return;
        if (e.data && e.data.giscus) {
            postTheme(getTheme());
            window.removeEventListener('message', onReady);
        }
    });
    new MutationObserver(function () {
        postTheme(getTheme());
    }).observe(document.documentElement, { attributeFilter: ['data-theme'] });
}());
</script>
{{- end }}

5. Hugo プロジェクトの assets/css/extended/custom.css に幅調整スタイルを追加

assets/css/extended/custom.css に以下を追記する。

.giscus-frame-container {
  max-width: calc(var(--main-width) + var(--gap) * 2);
  margin: 0 auto;
  padding: 0 var(--gap);
}

6. Hugo ローカルサーバーで動作確認

hugo server

記事ページ末尾にコメント欄が表示されれば成功。hugo server のローカル環境でも giscus はそのまま動作確認できる。

実装の解説

コメント専用リポジトリを分ける理由

giscus を利用するリポジトリは public である必要がある。ブログのソースリポジトリが private の場合、コメント専用の別リポジトリを public で用意するのがわかりやすくて安心。

owner/blog             ← private のまま(ブログソース)
owner/blog-comments    ← public(Discussions のみ使用)

blog-comments リポジトリはコメントの入れ物として使うだけなので、README 1行あれば十分。ソースコードは一切置かなくてよい。

Announcements カテゴリを選ぶ理由

Discussions には複数のカテゴリがあるが、Announcements を推奨する。Announcements は管理者のみがトップレベルの Discussion を作成でき、一般ユーザーはリプライのみ可能なため、GitHub 上から直接スパム Discussion を立てられる心配がない。giscus アプリ自体はページへの初回コメント時に自動でスレッドを作成してくれる。

extend_footer.html の各パラメータ

パラメータ 理由
data-mapping title ページタイトルで Discussion を紐付け。pathname だとローカルと本番で別スレッドになる
data-strict 1 タイトルの完全一致。誤って別記事のスレッドに混入しない
data-theme light 初期値。JS で起動直後に上書きするため実質的な値は JS 側が決める
data-lang {{ .Language.Lang }} Hugo の多言語機能と連携。日本語ページは ja、英語ページは en の UI で表示

最初の {{- if and .IsPage (ne (.Param "comments") false) }} により、記事ページ以外(トップ・タグ一覧など)にはコメント欄が表示されない。また各記事 .md の Front Matter に comments: false を追加すると個別に非表示にできる。

# コメント欄を非表示にしたい記事の frontmatter
comments: false

PaperMod: コメント欄の幅が全幅になる問題

そのまま実装すると、コメント欄がブラウザ幅いっぱいに広がって見づらくなる。

PaperMod の HTML 構造では extend_footer.html<main class="main">外側に出力される。

<main class="main">   ← max-width 制限あり
  <!-- 記事コンテンツ -->
</main>
<!-- ↓ extend_footer はここ(幅制限なし)-->
<footer>
  ...extend_footer.html の内容...
</footer>

.main クラスは max-width: calc(var(--main-width) + var(--gap) * 2) で幅が制限されているが、footer はその外側にあるため giscus の iframe が全幅に広がってしまう。手順 5 で追加した .giscus-frame-container が同じ CSS 変数を使って幅を揃えている。

PaperMod: ダークモードが連動しない問題

data-theme="preferred_color_scheme" は OS のカラースキームには追従するが、PaperMod の手動トグルには反応しない。

原因は PaperMod のダークモード実装にある。一般的なテーマは <body class="dark"> のようにクラスを付与するが、PaperMod は <html> 要素の data-theme 属性を変更する方式を採っている。

// PaperMod の実装
document.querySelector("html").dataset.theme = 'dark';

そのため MutationObserver の監視対象とテーマ取得関数の参照先を document.documentElement にする必要がある。

// NG: body のクラスは変化しない
document.body.classList.contains('dark')
// OK: html の data-theme 属性を参照する
document.documentElement.dataset.theme === 'dark'

// NG: body のクラス変化を監視
observer.observe(document.body, { attributeFilter: ['class'] });
// OK: html の data-theme 変化を監視
observer.observe(document.documentElement, { attributeFilter: ['data-theme'] });

手順 4 のスクリプトブロックがこれらを含んだ最終形になっている。

コメントの運用管理

giscus のコメントは GitHub Discussions 上に保存されるため、管理はすべて blog-comments リポジトリの Discussions タブから行う。Hugo 側のコードを触る必要はない。

不適切なコメントを削除する

  1. github.com/OWNER/blog-commentsDiscussions タブを開く
  2. 該当記事のスレッドを探す(スレッド名 = 記事タイトル)
  3. 削除したいコメントの メニュー → Delete を選択

スレッドごと (記事1本分まるごと) 削除したい場合は、Discussion 右側の Delete discussion で消せる。

コメントを一時停止する

サイト全体を停止する場合は、blog-comments リポジトリの Settings → Features → Discussions のチェックを外すだけでよい。全記事のコメント欄が非表示になる。再開時は再度チェックを入れる。

特定の記事だけ停止する場合は、該当スレッドを GitHub 上でロックする。

  1. 該当スレッドを開く
  2. 右側サイドバー → Lock conversation を選択
  3. 既存コメントを残したまま新規投稿だけ受け付けなくなる

推奨: 通知を有効にする

blog-comments リポジトリの WatchAll ActivityDiscussions に設定しておくと、コメント投稿のたびにメール通知が届く。

推奨: スパム報告を活用する

問題のあるコメントは削除するだけでなく、GitHub の Report content 機能でスパム報告しておくと、GitHub 全体のスパム検出精度向上に貢献できる。コメントの メニューから報告できる。

推奨: Interaction limits で緊急ブロック

スパムが集中する事態になった場合は、リポジトリの Settings → ModerationInteraction limits から、一定期間だけ投稿を制限できる。

制限レベル 投稿できるユーザー
Limit to existing users GitHub アカウント作成から一定日数以上のユーザーのみ
Limit to prior contributors 過去にこのリポジトリへ貢献したユーザーのみ
Limit to repository collaborators コラボレーターのみ(実質停止に近い)

期間は 24時間・72時間・1週間・1ヶ月・無期限から選べる。

終わりに

giscus は Hugo のような静的サイトに後付けでコメント機能を追加するうえで、現時点で最もバランスのよい選択肢だと感じている。技術ブログの読者層には GitHub アカウントの保有率が高く、スパム耐性の高さも嬉しい。

今回の実装は Claude Code に補助してもらいながら進めた。リポジトリの作成・Discussions の有効化・パラメータ取得・Hugo のテンプレート実装・PaperMod 固有の問題の調査と修正まで、ほぼすべてをターミナル上で完結できた。

参考