Hugo + Oink 如何接入 Giscus 评论系统
Hugo 生成的是静态 HTML,本身没有接收评论的服务器、数据库或用户系统。要给 Hugo + Oink 网站增加评论,关键不是“让 Hugo 保存评论”,而是把评论能力交给 Giscus,再由 GitHub Discussions 保存数据和管理身份。
我给网站加评论时,最想解决的是一件事:不管读者打开中文还是英文、Vercel 还是 GitHub Pages,都能在同一个地方聊天。下面就看看这些工具是怎么配合的,一条留言又是怎么出现在静态博客里的。

四个组件分别负责什么
| 组件 | 职责 |
|---|---|
| Hugo | 读取 Markdown 和配置,生成博客页面 |
| Oink | 提供页面模板,决定何时、在什么位置输出评论容器 |
| Giscus | 在页面中显示评论界面,并与 GitHub 通信 |
| GitHub Discussions | 保存讨论、回复、用户身份和表态 |
可以把它们理解为:
评论数据始终保存在 GitHub Discussions。博客页面只是嵌入一个查看和发表 Discussion 内容的入口。Giscus 官方说明也明确指出,它使用 GitHub Discussions 搜索 API 查找页面对应的讨论,并在首次评论或表态时创建尚不存在的讨论。
为什么静态网站需要外部评论后端
普通评论系统至少要完成下面这条链路:
这通常意味着自己维护后端服务器、数据库、登录系统、权限控制和垃圾评论处理。Hugo 只负责构建静态文件,不会持续运行一个处理这些请求的应用。
Giscus 的思路是复用 GitHub 已有的能力:
- GitHub 账户负责用户身份。
- GitHub Discussions 负责保存主讨论、回复和表态。
- Giscus 负责把当前页面映射到一条 Discussion,并提供嵌入式界面。
- 仓库维护者直接在 Discussions 中回复、置顶、锁定或删除内容。
因此网站无需保存 GitHub token,也不需要自行建立评论数据库。
Hugo 参数为什么能控制评论
Hugo 允许在站点配置或页面 front matter 中定义参数。例如:
对 Hugo 来说,这只是一个值。真正赋予它“显示评论区”含义的是 Oink 的模板:模板读取站点级和页面级评论参数,决定是否输出评论容器、加载 Giscus 脚本。
Hugo 的 front matter 文档说明,页面顶部的元数据既能描述内容,也能影响模板选择和发布结构。本站把全局 Giscus 配置放在 hugo.yml,再通过各栏目的 cascade 控制显示范围:
- 博客文章继承全局评论开关。
- 博客列表页显式设置
comments: false。 - 首页、学习和经历栏目不显示评论。
- 中英文博客文章使用相同的讨论标识。
这样不用在每篇博客里重复粘贴整段 Giscus 配置。
Giscus 配置包含什么
本站的核心配置如下:
其中最重要的四项是:
| 配置 | 用途 |
|---|---|
repo | 告诉 Giscus 去哪个公开仓库查找 Discussions |
repoId | GitHub 内部用于唯一识别仓库的公开 ID |
category | 新讨论所在的分类 |
categoryId | GitHub 内部用于唯一识别分类的公开 ID |
这些值是定位信息,不是密码或访问令牌。目标仓库还必须满足三个条件:仓库公开、启用 Discussions、安装 Giscus App。官方配置页建议使用 Announcements 类型的分类,使新 Discussion 只能由维护者和 Giscus 创建。
其余参数控制交互方式:
reactionsEnabled: 1:显示主讨论的表态。emitMetadata: 0:不向父页面周期发送 Discussion 元数据。inputPosition: bottom:输入框放在评论列表下方。loading: lazy:接近评论区时再加载 iframe。theme: auto:跟随网站深浅色模式。- 中文页面传
zh-CN,英文页面传en,只改变 Giscus 界面语言,不翻译用户评论。
页面怎样加载评论区
构建和浏览器运行分成两个阶段。
构建阶段
Hugo 构建博客页面时,Oink 先检查评论开关和必需配置。条件满足后,模板输出一个带参数的容器,形式类似:
这时还没有评论数据。HTML 只留下容器,并告诉浏览器稍后应连接哪个仓库、分类和讨论标识。
浏览器阶段
用户打开页面后,Oink 的 JavaScript 动态加载:
Giscus 随后创建 iframe。视觉上它在博客底部,技术上却是由 giscus.app 提供的独立页面:
iframe 让评论界面和博客页面彼此隔离。Oink 还会监听网站的深浅色切换,通过浏览器的 postMessage 把新主题发送给 Giscus iframe。
如果脚本或 iframe 加载失败,Oink 会结束加载状态并显示本地化错误提示,而不是让页面一直处于等待状态。
一篇文章怎样对应一条 Discussion
Giscus 必须知道“当前页面对应哪条 Discussion”。常见映射包括完整 URL、pathname、页面标题和自定义字符串。
单语言、单域名网站可以直接用:
此时不同路径通常对应不同讨论。但本站同时存在中文、英文和两个部署地址:
如果直接使用浏览器的 pathname,同一篇文章会被拆成多条 Discussion。因此本站改用:
并由 Hugo 模板根据 .Page.Path 生成统一的 data-term:
Hugo 的 Page.Path 文档说明,逻辑路径不包含文件扩展名和语言标识。本站再去掉部署域名与 GitHub Pages 基路径的影响,于是四个入口最终使用同一个标识:
标题可以翻译,域名也可以迁移,只要这个固定标识不变,评论区就仍指向同一条 Discussion。若以后修改文章 slug,则应先考虑如何保留旧标识,否则可能生成新的评论区。
评论如何创建与管理
Giscus 加载后,会用映射标识搜索指定仓库和分类中的 Discussion:
- 找到匹配项:读取并显示已有评论。
- 没找到:先显示空评论区。
- 用户首次评论或表态:Giscus Bot 自动创建对应 Discussion。
- 用户发言:通过 GitHub OAuth 授权 Giscus 代表本人发布。
- 站长管理:进入仓库的 Discussions 页面回复、锁定、置顶或删除。
访客也可以绕过博客页面,直接在 GitHub Discussion 中参与讨论。两边看到的是同一份数据。
小结
Giscus 没有把评论复制进 Hugo,而是把 GitHub Discussion 嵌入博客页面。Hugo 负责生成页面,Oink 负责判断是否加载评论并传递配置,Giscus 负责界面与通信,GitHub Discussions 负责持久保存数据和身份。
对普通单站点博客,pathname 往往够用;对本站这种双语、双部署结构,更关键的是设计一个与语言、域名无关的稳定标识。当前方案用 specific + /blog/<slug>/,让同一篇文章始终共用一个评论区。