1906 文字
10 分
Astro + FuwariブログにGiscusコメント機能を実装した全手順
この記事について

Claude(Anthropic)との共同編集により作成されました。

要約
  • 昨日の告知記事の続き。Astro + Fuwari に Giscus を組み込んだ実装の全手順
  • 既存の licenseConfig パターンに乗っかり、4ファイル変更で完結
  • Astro特有の難所:<script>data-* をテンプレート変数で直接埋める方式はGiscusと相性が悪い。divdataset → クライアントJSで <script> 動的生成というパターンが必要
  • giscus.app 側の事前準備(Discussions有効化・カテゴリ作成・IDの取得)も合わせて記録

昨日(2026-05-03)、ゆるディープにコメント機能が追加されたことを告知した。あの記事の末尾で「実装の詳細は明日書く」と予告した。今回がその記事だ。

Giscus^1 は GitHub Stars が 11.6k を超えており、静的サイト向けコメントソリューションとしての評価が高い。絵文字リアクションも付けられるのが地味にうれしいポイントだ。


実装前のコードベース調査#

作業を始める前に、Claudeと一緒にコードベースを調査して、どこに何があるかを把握した。

挿入位置の特定#

記事ページは src/pages/posts/[...slug].astro の1ファイルで完結している。コメント欄を挿入する自然な位置は記事本文の後、prev/nextナビの前だ。

記事本文(Markdown)
ライセンス表示(licenseConfig.enable で条件付きレンダリング)
↓ ← ここにコメント欄を挿入
prev/next ナビゲーション

既存パターンの確認#

Fuwariの設定は src/config.ts に named export でまとめられている。注目したのは licenseConfigenable: boolean パターンだ。

export const licenseConfig: LicenseConfig = {
enable: true,
name: "CC BY-NC-SA 4.0",
url: "...",
}

この構造にそのまま乗っかれると判断した。コメント設定も commentConfig.enable で on/off できる同じインターフェースにすれば、ページ側の条件付きレンダリングも統一できる。

その他の確認事項#

確認ポイント結果対応
テーマカラーfixed: true(ダーク固定)MutationObserver 不要、theme は固定値で OK
CSP設定netlify.toml / astro.config.mjs に設定なしframe-src 制限なし、追加対応不要
i18nKey.comments = "コメント"ja.ts に既存Giscus は自前の UI を持つため今回は使わず

実装:4ファイルの変更#

1. src/types/config.ts — CommentConfig 型定義#

export type CommentConfig = {
enable: boolean
provider: "giscus"
giscus: {
repo: `${string}/${string}`
repoId: string
category: string
categoryId: string
mapping: "pathname" | "url" | "title" | "og:title" | "specific" | "number"
theme: string
lang: string
reactionsEnabled: "0" | "1"
inputPosition: "top" | "bottom"
}
}

provider フィールドを持たせた。将来 Utterances に切り替えたくなっても provider で分岐するだけでよい。型レベルで拡張の余地を残しておく設計だ。

2. src/config.ts — commentConfig 設定値#

export const commentConfig: CommentConfig = {
enable: false, // ID取得後にtrueへ
provider: "giscus",
giscus: {
repo: "hiranorm/yurudeep",
repoId: "", // giscus.appで取得
category: "Comments",
categoryId: "", // giscus.appで取得
mapping: "pathname",
theme: "noborder_dark",
lang: "ja",
reactionsEnabled: "1",
inputPosition: "bottom",
},
}

repoIdcategoryId は外部サービス(giscus.app)で生成するため事前に用意できない。enable: false でコードを先に入れておき、IDが揃ったら記入して true にするという流れにした。この状態でもビルドには影響しない。

3. src/components/misc/Comments.astro — 新規コンポーネント#

ここが今回のハマりどころだった。

Giscusのセットアップは、公式が提供する client.js<script> タグとして埋め込む形が基本だ^1。そのスクリプトは data-repodata-theme などの data-* 属性を読み取って動作する。

問題はAstroとの組み合わせにある。Astroのフロントマターで定義した変数は {variable} として展開できるが、Giscusの client.jsページのHTMLに埋め込まれた <script> タグ自身の data-* 属性を読む仕組みになっている。Astroがバンドルするスクリプトは <head>type="module" で挿入されるため、フロントマターの変数を <script>data-* に直接流し込む方式では噛み合わない。

採用したのは次のパターンだ。

---
import { commentConfig } from '../../config'
const { giscus: g } = commentConfig
---
<div
class="card-base p-4"
data-repo={g.repo}
data-repo-id={g.repoId}
data-category={g.category}
data-category-id={g.categoryId}
data-mapping={g.mapping}
data-theme={g.theme}
data-lang={g.lang}
data-reactions-enabled={g.reactionsEnabled}
data-input-position={g.inputPosition}
data-load-giscus="true"
></div>
<script>
const el = document.querySelector('[data-load-giscus="true"]')
if (el) {
const s = document.createElement('script')
s.src = 'https://giscus.app/client.js'
s.setAttribute('data-repo', el.dataset.repo!)
s.setAttribute('data-repo-id', el.dataset.repoId!)
s.setAttribute('data-category', el.dataset.category!)
s.setAttribute('data-category-id', el.dataset.categoryId!)
s.setAttribute('data-mapping', el.dataset.mapping!)
s.setAttribute('data-theme', el.dataset.theme!)
s.setAttribute('data-lang', el.dataset.lang!)
s.setAttribute('data-reactions-enabled', el.dataset.reactionsEnabled!)
s.setAttribute('data-input-position', el.dataset.inputPosition!)
s.setAttribute('crossorigin', 'anonymous')
s.async = true
el.appendChild(s)
}
</script>

サーバー側で config を divdata-* に展開し、クライアントJSがそれを読んで <script> タグを動的生成する——という2段構えの構成だ。

Astroのbundled scriptはDOMロード後に実行されるため、querySelector で要素を取得するタイミングに問題はない。

4. src/pages/posts/[…slug].astro — ページ組み込み#

licenseConfig と同じ1行で完結する。

{commentConfig.enable && <Comments class="mb-4 onload-animation" />}

enable: false の間はDOMにも一切レンダリングされない。


設計上の選択#

publicリポジトリへの変更#

Giscusはリポジトリが public である必要がある。private リポジトリでは訪問者がGitHub Discussionsにアクセスできないためコメントが表示されない。

yurudeepはもともと private にしていたが、ソースを秘匿することに価値がないと判断して public に変更した。Fuwariは広く使われているテーマだし、Claude Codeとの共同作業の記録もある程度オープンにしておきたい、という考えもある。コメント専用の別 public リポジトリを作る回避策もあるが、管理が煩雑になるため採用しなかった。

テーマの固定#

Giscusはダーク/ライトの動的切り替えに対応している。追従させるには MutationObserver でHTML属性の変化を監視し、Giscusのiframeに postMessage でテーマ変更を通知するコードが必要になる。

このブログは fixed: true(ダーク固定)なので、その実装はすべて省略できた。theme: "noborder_dark" を設定するだけで完結。実装をシンプルに保つうえで、ダーク固定の設定が想定外に役立った。


事前準備手順#

コードは実装済みの状態から、有効化するまでの手順を記録しておく。

1. Discussions を有効化する

https://github.com/{owner}/{repo}/settings の Features セクションで Discussions にチェックを入れる。

2. Giscusアプリをインストールする

https://github.com/apps/giscus からインストール。権限はブログリポジトリのみに限定する。

3. Discussionカテゴリを作る

リポジトリの Discussions タブ → カテゴリ管理 → Comments カテゴリを新規作成。フォーマットは Announcement を選ぶ。メンテナーのみ新スレッドを作成でき、読者はコメントのみ投稿できる形になる。記事ごとのスレッドを自分でコントロールしたいときに適切な設定だ。

4. IDを取得する

https://giscus.app^2 にアクセスして、リポジトリ名・カテゴリ・mapping: pathname を選択すると設定コードが生成される。ここから data-repo-iddata-category-id の値を控える。

5. config.tsに記入して有効化する

repoId: "R_xxxxxxxxxx",
categoryId: "DIC_xxxxxxxxxx",
enable: true,

pnpm build してNetlifyにデプロイすれば、全記事のコメント欄が一斉に有効になる。


所感#

Fuwariテーマの設計が整理されていたおかげで、既存の licenseConfig パターンにそのまま乗っかれた。型定義・設定・コンポーネント・ページ組み込みの4ファイルで完結し、pnpm astro check も0エラーだった。

実装として独特なポイントはひとつ——Astroで動的な設定をGiscusに渡す方法だった。「<script>data-* をテンプレート変数で直接埋める」という発想から入るとハマる。divdataset 経由でクライアントJSに渡し、JSが <script> を動的生成するという迂回が必要になる。仕組みを理解しておくと、他の動的スクリプト埋め込みにも同じパターンを応用できる。

カラー設定も一緒に見直した。コメント欄はコバルトブルー系にし、サイト全体のアクセントカラーも深海をイメージしたダークブルーに寄せた。「ゆるディープ(深海)」というブログ名と、見た目がようやく合ってきた気がしている。

参考文献#

  1. giscus https://github.com/giscus/giscus
  2. giscus.app https://giscus.app
Astro + FuwariブログにGiscusコメント機能を実装した全手順
https://yurudeep.com/posts/web/2026/20260504/
作者
ひらノルム
公開日
2026-05-04
ライセンス
CC BY-NC-SA 4.0