この記事の内容および実装は 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 ソースリポジトリには手を入れない。
実装イメージ
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.com→HTTPS→Login with a web browserを選択すると、ブラウザが開いて認証が完了する。 -
ログイン確認
gh auth statusLogged 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 アプリをインストール
- github.com/apps/giscus を開く
- Install ボタンをクリック
- インストール先のアカウントを選択する(Organization ではなく個人アカウントを選ぶ)
- Only select repositories を選び、
blog-commentsのみを指定して Install をクリック
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" },
...
]
}
}
}
}
repository の id と、Announcements カテゴリの id を控えておく。
4. Hugo プロジェクトに layouts/partials/extend_footer.html を作成
自分のGitHubユーザー名・R_kgDORxxxxxxxx・DIC_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 側のコードを触る必要はない。
不適切なコメントを削除する
github.com/OWNER/blog-comments→ Discussions タブを開く- 該当記事のスレッドを探す(スレッド名 = 記事タイトル)
- 削除したいコメントの
…メニュー → Delete を選択
スレッドごと (記事1本分まるごと) 削除したい場合は、Discussion 右側の … → Delete discussion で消せる。
コメントを一時停止する
サイト全体を停止する場合は、blog-comments リポジトリの Settings → Features → Discussions のチェックを外すだけでよい。全記事のコメント欄が非表示になる。再開時は再度チェックを入れる。
特定の記事だけ停止する場合は、該当スレッドを GitHub 上でロックする。
- 該当スレッドを開く
- 右側サイドバー → Lock conversation を選択
- 既存コメントを残したまま新規投稿だけ受け付けなくなる
推奨: 通知を有効にする
blog-comments リポジトリの Watch を All Activity か Discussions に設定しておくと、コメント投稿のたびにメール通知が届く。
推奨: スパム報告を活用する
問題のあるコメントは削除するだけでなく、GitHub の Report content 機能でスパム報告しておくと、GitHub 全体のスパム検出精度向上に貢献できる。コメントの … メニューから報告できる。
推奨: Interaction limits で緊急ブロック
スパムが集中する事態になった場合は、リポジトリの Settings → Moderation → Interaction 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 固有の問題の調査と修正まで、ほぼすべてをターミナル上で完結できた。