この記事についてClaude(Anthropic)との共同編集により作成されました。
要約
- 昨日の告知記事の続き。Astro + Fuwari に Giscus を組み込んだ実装の全手順
- 既存の
licenseConfigパターンに乗っかり、4ファイル変更で完結- Astro特有の難所:
<script>のdata-*をテンプレート変数で直接埋める方式はGiscusと相性が悪い。divのdataset→ クライアント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 でまとめられている。注目したのは licenseConfig の enable: 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 制限なし、追加対応不要 |
| i18n | Key.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", },}repoId と categoryId は外部サービス(giscus.app)で生成するため事前に用意できない。enable: false でコードを先に入れておき、IDが揃ったら記入して true にするという流れにした。この状態でもビルドには影響しない。
3. src/components/misc/Comments.astro — 新規コンポーネント
ここが今回のハマりどころだった。
Giscusのセットアップは、公式が提供する client.js を <script> タグとして埋め込む形が基本だ^1。そのスクリプトは data-repo、data-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 を div の data-* に展開し、クライアント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-id と data-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-* をテンプレート変数で直接埋める」という発想から入るとハマる。div の dataset 経由でクライアントJSに渡し、JSが <script> を動的生成するという迂回が必要になる。仕組みを理解しておくと、他の動的スクリプト埋め込みにも同じパターンを応用できる。
カラー設定も一緒に見直した。コメント欄はコバルトブルー系にし、サイト全体のアクセントカラーも深海をイメージしたダークブルーに寄せた。「ゆるディープ(深海)」というブログ名と、見た目がようやく合ってきた気がしている。
参考文献
- giscus https://github.com/giscus/giscus
- giscus.app https://giscus.app