靜態網站有個先天的缺口:沒有後端,就沒有地方存留言。這篇記錄我怎麼幫這個 Hexo 部落格加上留言功能——為什麼在幾個方案中選了 giscus(把留言存進 GitHub Discussions)、它的取捨在哪,以及從零到上線的完整設定步驟,包含主題沒內建支援時要怎麼自己接。
一、靜態網站的留言困境
Hexo 產生的是純 HTML,部署到 Cloudflare Pages 之後就是一堆靜態檔案。留言卻是動態的——需要接收、儲存、讀取,這三件事都需要一個「活著的」後端。
所以所有靜態網站的留言方案,本質上都在回答同一個問題:這個後端由誰來當? 主流答案有三種:
| 方案 | 後端是誰 | 代價 |
|---|---|---|
| Disqus | 第三方商業服務 | 免費版有廣告、追蹤使用者、載入慢、資料在別人手上 |
| Twikoo / Waline | 你自己部署(Vercel + MongoDB 之類) | 要維護、要顧資料庫、免費層有額度限制 |
| giscus | GitHub Discussions | 訪客必須有 GitHub 帳號 |
二、為什麼我選 giscus
giscus 的核心概念很妙:把 GitHub Discussions 當成留言資料庫。每篇文章對應一則 discussion,訪客在你網站上留的言,其實是發到你指定 repo 的 Discussions 裡。
選它的四個理由:
1. 零後端、零維護、零成本。 這是決定性的。Twikoo 或 Waline 雖然功能更全,但代價是我得多養一個 Vercel 專案和一個 MongoDB Atlas 資料庫——兩個都有免費額度,也都有可能在某天突然出問題、額度用盡、或服務條款變更。giscus 的「後端」是 GitHub,它掛掉的機率比我的部落格本身還低。
2. 資料在自己手上,而且格式開放。 留言就是 GitHub Discussions,你隨時能用網頁看、用 API 匯出、用 gh CLI 操作。相較之下 Disqus 的資料躺在別人的系統裡,匯出還要看它心情。
3. 不追蹤、無廣告。 Disqus 免費版會在你的頁面上塞廣告和追蹤腳本——對一個講技術的部落格來說,這是形象問題也是效能問題。
4. 讀者群契合。 這是最現實的判斷:我寫的是 RAG、Claude Code、Hexo 這類主題,會看到這裡並想留言的人,幾乎必然有 GitHub 帳號。giscus 最大的缺點(要求 GitHub 登入)在我的場景裡幾乎不構成阻力。
什麼時候不該選 giscus
反過來說,如果你的部落格寫的是美食、旅遊、生活記錄,讀者裡有 GitHub 帳號的可能不到一成——那 giscus 的登入門檻會直接讓留言區永遠是空的。那種情況請選 Twikoo 或 Waline,它們支援匿名留言。 工具沒有絕對的好壞,只有適不適合你的讀者。
三、設定教學
步驟 1:準備一個公開 repo 放留言
重點:這個 repo 必須是 public。 giscus 靠訪客的 GitHub 身分去讀寫 Discussions,private repo 一般訪客根本看不到。
如果你的部落格原始碼是私有的(我的就是),開一個獨立的公開 repo 專門放留言是更好的做法——原始碼保持私密,留言公開可讀,兩邊互不影響:
1 | gh repo create <你的帳號>/blog-comments --public \ |
步驟 2:開啟 Discussions 功能
Repo 頁面 → Settings → 往下找到 Features → 勾選 Discussions。
或用 CLI 一行搞定:
1 | gh api -X PATCH repos/<你的帳號>/blog-comments -f has_discussions=true |
步驟 3:安裝 giscus App
到 github.com/apps/giscus 點 Install,授權範圍只選剛才那個 repo(不要給整個帳號權限,最小權限原則)。
這步不能用 CLI 代勞,必須在瀏覽器完成——它是 GitHub App 的授權流程。
步驟 4:取得設定參數
到 giscus.app 填入 repo 名稱,網頁會自動幫你產生設定並顯示一段 <script>。你需要裡面四個值:data-repo、data-repo-id、data-category、data-category-id。
如果你跟我一樣偏好在終端機解決,用 GraphQL API 直接查:
1 | gh api graphql -f query=' |
回傳的 repository.id(R_kgDO... 開頭)就是 repo_id,選一個分類(我用 Announcements,因為它預設只有維護者能開新討論串,比較不會被灌垃圾)的 id(DIC_kwDO... 開頭)就是 category_id。
步驟 5:接進 Hexo 主題
接下來分兩種情況。
情況 A:主題已內建 giscus 支援(NexT、Butterfly、Fluid 等多數熱門主題都有)——直接在主題設定檔填入參數就好:
1 | giscus: |
情況 B:主題沒有支援(我的 FlatPaper 只內建 Twikoo 和 Artalk)——自己加一段 partial。在主題的 layout/_partial/ 底下找到留言相關的檔案,加入 giscus 的分支:
1 | <div class="giscus"></div> |
幾個參數值得說明:
data-mapping="pathname":用文章路徑對應 discussion。比url好——之後換網域,留言不會因為網址變了而全部對不上。data-strict="1":嚴格比對,避免路徑相近的文章共用同一則討論串。data-lang="zh-TW":留言區介面用繁體中文。data-loading="lazy":捲到留言區才載入,不拖慢首屏。
步驟 6(進階):讓留言區跟著網站切換深淺色
giscus 是嵌在 iframe 裡的,你網站的深色模式切換影響不到它。結果就是:網站切成深色,留言區還是刺眼的白色。
解法是用 postMessage 通知 iframe 換主題。我的做法是監聽網站根元素的 class 變化(我的主題用 .dark-mode 表示深色),變動時把新主題送進去:
1 | (function () { |
第二個監聽器容易被忽略但很重要:iframe 是非同步載入的,如果只在切換時同步,第一次載入的主題會是錯的。
四、幾個實務細節
留言區只在文章頁出現。 首頁、分類頁、標籤頁不需要留言。多數主題的留言 partial 都有 is_post() 之類的判斷,自己接的話記得加上,否則首頁會被塞一堆 iframe。
單篇關閉留言:在該篇文章的 front-matter 加 comments: false 即可。
第一則留言才會建立討論串。 部署完成後你看到的是空的留言框——這是正常的。giscus 採 lazy 建立,等第一個人留言時才會在 Discussions 開對應的討論串。想自己測試的話,去自己的文章底下留一則就看得到了。
通知會進 GitHub。 有人留言時,你會收到 GitHub 的通知(跟 issue 一樣)。想關掉就到那個 repo 的 Watch 設定調整。
小結
| giscus | Twikoo / Waline | Disqus | |
|---|---|---|---|
| 後端維護 | 無 | 要 | 無 |
| 成本 | 免費 | 免費額度內 | 免費(有廣告) |
| 匿名留言 | ✕ | ✅ | ✅ |
| 資料掌控 | ✅(GitHub) | ✅(自己的 DB) | ✕ |
| 隱私 | 無追蹤 | 無追蹤 | 有追蹤 |
我的判斷準則很簡單:技術部落格選 giscus,生活型部落格選 Twikoo。 讀者是誰,決定了哪個門檻可以接受。
如果你正好也在用 Hexo 架站,這個部落格的建站流程也記錄過——從零到自動部署到 Cloudflare Pages。歡迎在下面留言(正好可以順便測試 giscus 有沒有裝好 🙂)。
留言