本站的评论和留言模块是如何实现的

本站的评论和留言模块是如何实现的
灿烂的番茄前言
记得本站刚搭好的时候,文章倒是能看了,但总觉得少点什么。后来翻同行的博客才发现——没有评论区。一篇文章发出去就像石沉大海,读者看了之后想说什么也没地方,有种”单向输出”的感觉。
这也是 用 Hexo 搭建纯静态博客 这类方案的天然短板:生成的是一堆静态 HTML 文件,没有后端,没有数据库,自然也就没有评论功能。好在社区生态丰富,有一大把”第三方评论系统”可以给静态博客接入。
经过一番调研和折腾,本站现在跑起来的效果是:
- 文章评论区:Twikoo,支持 QQ 头像、匿名评论、邮件通知、管理员面板
- 留言板:hexo-butterfly-envelope 信封样式,独立页面供读者随意发言
- 部署成本:零(Netlify + MongoDB Atlas 免费计划)
- 主题特性:AnZhiYu 对 Twikoo 支持得最完整,评论弹幕、最新评论 widget 都能用
这篇文章就来完整记录整个过程,包括为什么这么选、具体怎么部署、以及那些让我卡了大半天的坑。如果你也在 搭建 Hexo + AnZhiYu 主题博客并做 SEO 优化,这篇文章应该能帮你少走不少弯路。
一、AnZhiYu 主题支持哪些评论系统
AnZhiYu 主题内置了 5 种评论系统的适配代码,不需要额外写集成。这 5 种分别是:
| 系统 | 后端 | 数据归属 | 主题特性支持 | 推荐度 |
|---|---|---|---|---|
| Twikoo | Vercel / Netlify / 腾讯云 + MongoDB | ✅ 自有 | ✅ 全特性 | ★★★★★ |
| Waline | LeanCloud / Vercel / 自建 | ✅ 自有 | 部分(评论弹幕不可用) | ★★★★☆ |
| Valine | LeanCloud | ⚠️ LeanCloud | 部分 | ★★★☆☆ |
| Artalk | 自建 Go/PHP | ✅ 自有 | 部分 | ★★★☆☆ |
| Giscus | GitHub Discussions | ⚠️ GitHub | 部分 | ★★★☆☆ |
核心衡量标准
判断选哪个系统,我主要看三点:
- 数据所有权:评论数据应该留在自己手里(自己的 MongoDB / 自己的 GitHub),而不是存在第三方服务商
- AnZhiYu 特性支持度:AnZhiYu 有几个高级特性(评论弹幕、最新评论 widget、匿名评论)只有 Twikoo 才完整支持
- 国内访问速度:Giscus 的 GitHub Discussions API 在国内不稳定,Waline/Valine 的 LeanCloud 限速严重
各系统的快速评价
Twikoo:AnZhiYu 主题官方文档首推。评论弹幕 comment_barrage_config、首页最新评论 sidebar、匿名评论邮箱——这三个功能在主题源码里都做了 if use == 'Twikoo' 的特殊判断。选 Twikoo 能把这些小特性全开出来,体验最好。
Waline:老牌 Valine 的重构版,支持 Markdown、邮件通知、登录。功能比 Twikoo 丰富一档,但 AnZhiYu 对它的适配不如 Twikoo 完整(弹幕不可用,最新评论 widget 需要额外处理)。
Valine:一度非常流行的”无后端评论系统”,把数据存到 LeanCloud。但 LeanCloud 2019 年就开始对免费用户限速,2025 年之后基本被社区弃用。不建议新项目选它。
Artalk:国产自托管方案,适合有服务器的人。如果你本来就跑着一台 VPS,Artalk 是一条干净的路线。
Giscus:基于 GitHub Discussions API,程序员友好,零运维。但你需要读者有 GitHub 账号才能评论,门槛太高了。而且国内 GitHub API 的网络稳定性是个问题。
二、为什么最终选了 Twikoo
说了这么多,我选 Twikoo 的原因其实不复杂,就四个:
AnZhiYu 全特性支持:评论弹幕、最新评论 widget、匿名评论、首页文章卡片评论数——这些如果你用 Waline 或 Giscus,要么不能用,要么要自己改源码
部署成本为零:Netlify 免费套餐 + MongoDB Atlas M0(512MB 免费) = $0/月,足够个人博客用到天荒地老
数据在我手里:评论存在我自己的 MongoDB Atlas 实例里,随时可以导出备份,不依赖任何平台锁定
管理面板好用:Twikoo 自带管理后台,能审核评论、配置邮件通知、开启 Akismet 反垃圾,不用写一行代码
如果你是程序员受众比较多的技术博客,想用 Giscus 让读者用 GitHub 登录评论,也完全可行。只是对于我这种受众面更宽的个人博客来说,Twikoo 的「任何人都能评论(支持匿名)」更适合。
三、Twikoo 后端部署:MongoDB 白名单引发的两小时排障
Twikoo 是一个前后端分离的系统:前端 SDK(twikoo.js)由 AnZhiYu 主题通过 CDN 自动引入;后端是云函数,需要你自己部署。部署后你会得到一个 URL(即 envId),填到主题配置里就行。
3.1 数据库:MongoDB Atlas(免费,10 分钟)
不管你用哪个 Serverless 平台跑 Twikoo,数据库都建议用 MongoDB Atlas。以下是完整步骤:
第一步:注册 MongoDB Atlas
前往 MongoDB Atlas 注册页 创建账号。支持 Google / GitHub 一键登录,注册过程不到 1 分钟。
第二步:创建免费集群
注册完成后,Atlas 会自动引导你创建一个集群:
- 选择 M0 Free Tier(永久免费,512MB 存储空间,个人博客完全够用)
- 云服务商选 AWS 即可,区域尽量选离你近的(亚太区域建议
Singapore或Tokyo) - 点击 Create Cluster,等 2-3 分钟集群部署完成
第三步:创建数据库用户
左侧菜单 → Security → Database Access → Add New Database User:
- 用户名:自己取一个(例如
twikoo-admin) - 密码:Autogenerate Secure Password 或自己设一个强密码
- 权限保持默认
Read and write to any database - 一定要记下这个用户名和密码,后面填到环境变量里
第四步:添加 IP 白名单
左侧菜单 → Security → Network Access → Add IP Address:
- 输入
0.0.0.0/0(允许任意 IP 访问) - 备注写
Allow all或Serverless - 点击 Confirm
这一步是整个部署最关键的一步。Vercel 和 Netlify 的云函数出口 IP 不固定,如果不加
0.0.0.0/0,Twikoo 会连不上数据库。本站就因为这个卡了两个小时。
第五步:获取连接字符串
左侧菜单 → Database → Connect → Connect your application → 复制连接字符串,格式如下:
1 | mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority |
把 <username> 和 <password> 换成第三步创建的用户名和密码。这个连接字符串就是后面要填的 MONGODB_URI 环境变量值,请妥善保存。
3.2 第一次尝试:Netlify(卡了两小时,全线 502)
一开始选的是 Netlify,因为网上都说它国内访问比 Vercel 稳。
- 注册 Netlify,用 GitHub 登录最快
- 访问 Netlify 一键部署链接(Go 到 Twikoo 后端部署页面 找到 Netlify 一键部署按钮)
- 在 Site Settings → Environment Variables 添加:
- Key:
MONGODB_URI - Value: 上一步获取的 MongoDB 连接字符串
- Key:
- Settings → Deployment Protection → 设置为 Disabled(必须关,否则匿名评论请求被拦)
- Deployments → 最新一次 → ⋯ → Redeploy(让环境变量生效)
然后浏览器访问 https://xxx.netlify.app/.netlify/functions/twikoo,结果——
1 | 502 Bad Gateway |
查 Netlify 的部署日志(Deploys → 最新一次 → Deploy log),没有任何报错,只显示”build succeeded”。Function 日志(Functions → twikoo → Logs)也是空的。
卡了两个小时,完全找不到原因。当时怀疑过是 Netlify 的 Node.js 版本问题、MongoDB Atlas 的数据库用户没配对、甚至 Netlify 的免费计划限制了云函数。但这些都是瞎猜,没有错误信息。
3.3 第二次尝试:Vercel 反而定位了问题(有心栽花无心插柳)
没办法,换 Vercel 试试。按 Twikoo 官方 Vercel 部署文档:
- 注册 Vercel,用 GitHub 登录最快
- 访问一键部署链接导入模板:
https://vercel.com/import/project?template=https://github.com/twikoojs/twikoo/tree/main/src/server/vercel-min - 输入项目名(如
my-twikoo),点 Deploy - 部署完成后进入项目 Settings → Environment Variables,添加:
- Key:
MONGODB_URI - Value: 上一步获取的 MongoDB 连接字符串
- Key:
- Settings → Deployment Protection → 设置为 Disabled(必须关掉!否则匿名评论请求会被拦)
- Deployments → 最新一次 → ⋯ → Redeploy(让环境变量生效)
浏览器访问 Vercel 域名,这次报错信息清楚多了:
1 | MongoDB Atlas SSL handshake failed |
而且 Vercel 的 Logs 给出了完整的错误堆栈。跟着线索查 MongoDB Atlas 配置才发现:
MongoDB Atlas 的 Network Access 默认只允许来自当前 IP 的访问。Vercel(和 Netlify)的云函数出口 IP 是不固定的,必须添加 0.0.0.0/0 允许任意 IP 访问。
登录 MongoDB Atlas → Network Access → Add IP Address → 输入 0.0.0.0/0 → Confirm。再 Redeploy 一次 Vercel,这次页面正常显示「Twikoo 云函数运行正常」。
3.4 最终方案:Netlify(这次一次通过)
找到原因后,回到 Netlify,同样的 MONGODB_URI 环境变量、同样的 MongoDB 白名单配置。再次 Trigger deploy,浏览器访问:
1 | https://xxx.netlify.app/.netlify/functions/twikoo |
显示「Twikoo 云函数运行正常」。
此时两边都跑通了。最终选择 Netlify 的原因是国内访问速度——Vercel 的 *.vercel.app 域名在国内不太稳定,而 Netlify 相对好一些。
注意:每个平台的 envId 格式不同:
- Vercel 部署:
https://my-twikoo-xxx.vercel.app - Netlify 部署:
https://xxx.netlify.app/.netlify/functions/twikoo
Netlify 的 envId 多了 /.netlify/functions/twikoo 后缀,填配置时别漏了,否则 404。
四、接入 AnZhiYu 主题
4.1 核心配置(两个字段搞定)
部署好 Twikoo 后端后,需要改 _config.anzhiyu.yml 的两个位置:
1 | # 1. 开启评论 + 指定 Twikoo |
改完这两行,重新构建部署,打开任意一篇文章,评论区就出现了。
4.2 AnZhiYu 是怎么加载评论的(调用链)
理解一下主题内部是怎么加载评论的,对后续排查问题很有帮助:
1 | _config.anzhiyu.yml |
关键判断点在 post.pug:42:
1 | if page.comments !== false && theme.comments && comments.use |
这行保证了:
- 单篇文章可以设置
comments: false关闭评论 - 如果没有配置
comments.use,全站都不会加载评论 SDK - 分类页/标签页/首页不会加载(这些页面的
commentsJsLoad不会被设为true)
4.3 关闭特定页面的评论
在不需要评论的页面(分类页、标签页、关于页等)的 frontmatter 里加:
1 | --- |
本站的分类页、标签页、关于页、音乐页、资源页都设置了 comments: false,避免在这些聚合页出现不必要的评论框。
4.4 部署后必须做的事
部署成功后,访问你博客的任意一篇文章,评论区应该会显示「填写QQ邮箱就会使用QQ头像喔~。」。此时你需要:
- 发一条测试评论,看能不能成功提交
- 浏览器访问 envId URL,进入「管理面板」,设置管理员密码
- 配置 SMTP 邮件通知(可选,用你自己的邮箱)
- 配置 Akismet 反垃圾(可选,免费注册 akismet.com)
- 在 Twikoo 管理面板的「IP 限流」里设置每 IP 每 10 分钟最多 N 条评论
五、信封留言板:方案比选与实现
评论区搞定之后,我想再加一个留言板——一个独立的页面,不需要关联到具体哪篇文章,读者进来随便说点什么。
5.1 两种方案比选
| 方案A:自定义 Page + 评论区 | 方案B:hexo-butterfly-envelope | |
|---|---|---|
| 原理 | 在 source/comments/ 新建一个空白页面,让 Twikoo 评论区挂上去 |
用 hexo-butterfly-envelope 插件生成一个信封动画页 + 评论区 |
| 样式 | 普通页面,顶部配一张图 | 信封抽出动画,少女风设计 |
| SEO | 页面可自定义 frontmatter(title/description) | ✅ 可自定义 frontmatter(title/description)+ 页面内有静态文字内容,搜索引擎可索引 |
| 实现难度 | 简单,选一个封面图就行 | 简单,装个插件配置几行 |
| 趣味性 | 中规中矩 | 有趣,像维多利亚时代的书信 |
选了方案B。原因:
- 信封动画挺有意思,适合留言板这种轻松的场景
- 插件生成的页面包含静态文字内容(来自
message数组),搜索引擎可索引,比纯评论区页面 SEO 略好 - 可以自定义
front_matter,补上description字段,避免 SEO 丢分
5.2 安装与配置
1 | npm install hexo-butterfly-envelope --save |
然后在 _config.yml 里配置(不是 _config.anzhiyu.yml):
1 | envelope_comment: |
几个关键点:
path: comments控制生成的页面路径是/comments/front_matter.description这个是 SEO 重点——插件生成的页面没有原生的 description 字段(它是 pug 渲染的),不手动补上会丢很多 SEO 分comments: true让 AnZhiYu 在这个页面也挂上 Twikoo 评论区type: envelope插件用这个字段来渲染信封动画和特定样式
5.3 在菜单里加上入口
_config.anzhiyu.yml 的 menu 里加一行:
1 | menu: |
六、踩坑实录
踩过的坑比你想的多,一个一个说。
坑1:Netlify 502 无日志,绕道 Vercel 才定位到 MongoDB 白名单
现象:Netlify 部署的 Twikoo 后端一直报 502,Function 日志完全空白,查了两小时找不到原因。
原因:MongoDB Atlas 的 Network Access 白名单默认只允许当前 IP 访问。Vercel 和 Netlify 的云函数出口 IP 都不固定,必须添加 0.0.0.0/0。
定位过程:先试 Netlify 卡着出不来 → 换 Vercel 后报「MongoDB Atlas SSL handshake failed」→ 顺着 Vercel 的详细错误堆栈定位到 MongoDB 白名单问题 → 添加 0.0.0.0/0 后两边都通了。
教训:部署 Serverless 应用 + MongoDB Atlas,第一步就去 Network Access 加 0.0.0.0/0,别等报错。另外,一个平台卡死了不妨换一个试试——不同的平台给的错误信息详细程度完全不同。
坑2:Netlify envId 格式不对,404
现象:评论区能渲染出来,但发评论时报 404。
原因:_config.anzhiyu.yml 的 twikoo.envId 填的是 https://xxx.netlify.app,少了路径。
解决:Netlify 的完整 envId 是 https://xxx.netlify.app/.netlify/functions/twikoo,中间那段路径不能省。
坑3:envelope 留言板 CSS 样式不生效
现象:信封样式的一些 CSS(如 body[data-type="envelope"])没有匹配上。
原因:AnZhiYu 主题的 layout/includes/layout.pug 里硬编码了:
1 | body(data-type="anzhiyu") |
所以不管你的页面 type 设置成什么,渲染出来的 <body> 的 data-type 始终是 "anzhiyu",导致 hexo-butterfly-envelope 的 body[data-type="envelope"] 选择器匹配不到。
影响:仅视觉微差(信封抽出高度等),不影响评论功能和 SEO。暂未修复——改动 layout.pug 涉及全站 <body> 标签,风险大于收益。
坑4:首页 swiper 不会自动更新最新文章
现象:发布新文章后,首页 swiper 轮播图永远显示那 4 篇老文章。
原因:AnZhiYu 的 sort_attr_post.js 在处理 fallback 逻辑时,使用 hexo.locals.get('posts').data 这个数组——但这个数组不是按 date 排序的。_config.yml 里的 index_generator.order_by: -date 只对首页文章列表分页生效,对 .data 无效。
解决:在 sort_attr_post.js 加上一行:
1 | posts_list = posts_list.slice().sort((a, b) => b.date - a.date); |
这个 bug 卡了本站好几天,详情见 谈谈本站是如何优化SEO的 文末的更新记录。
坑5:分类页也会出现评论区
现象:分类页 /categories/、标签页 /tags/ 等聚合页底部也出现了 Twikoo 评论框。
原因:这些页面使用的是 AnZhiYu 的 category.pug / tag.pug 模板,它们继承了 layout.pug 的默认行为。如果没显式设 comments: false,评论区会挂在底部。
解决:在对应页面的 frontmatter 加 comments: false。
本站设置的页面:
source/categories/index.md→comments: falsesource/tags/index.md→comments: falsesource/about/index.md→comments: falsesource/music/index.md→comments: false
坑6:评论数不显示
现象:文章页明明有评论,但顶部的评论数始终为 0。
原因:comments.lazyload: true 开启后,评论 SDK 在评论区进入视口时才加载,此时统计评论数的 API getCommentsCount() 还没执行。
解决:_config.anzhiyu.yml 里设 lazyload: false。代价是页面加载时要多等一个请求,但影响很小(twikoo.js 只有几十 KB)。
坑7:匿名评论没开放,读者以为要注册
现象:有读者反馈”要填邮箱才能评论,不想给”。
原因:Twikoo 默认需要填邮箱(用来获取 QQ 头像),但实际上可以不填直接评论。
解决:在 Twikoo 管理面板开启匿名评论,同时在评论区加一行提示文字(AnZhiYu 的 visitorMail.enable: true)。开启后评论框会出现「匿名评论」按钮,读者点一下就跳过邮箱步骤。
七、SEO 相关处理
给评论和留言模块做 SEO 的时候,这几个细节值得关注:
7.1 留言板页面的 description
hexo-butterfly-envelope 生成的留言板页面是 pug 模板渲染的,本身不带 description meta 标签。如果不补上,搜索引擎看到的是一个没有摘要的空页面。
通过 _config.yml 的 envelope_comment.front_matter.description 添加,这个值会被注入到生成的页面中:
1 | envelope_comment: |
7.2 评论区的语义化
AnZhiYu 主题内部的评论模板中,评论标题使用了 <h3> 标签,评论输入框有 aria-label 属性,这些对无障碍和 SEO 都有帮助。如果你的主题版本比较老,可以考虑检查一下这些细节。
7.3 评论数对 SEO 的间接帮助
文章页顶部的评论数(comments.count: true)虽然是一行数字,但它向搜索引擎传递了一个信号:这篇文章有社交互动。Google 虽然不直接用评论数作为排名因子,但丰富的页内元素有助于提升 dwell time(用户停留时长)。
更多关于本站 SEO 优化的细节,见 谈谈本站是如何优化SEO的。
总结
从零评论到现在的 Twikoo + 信封留言板,整个过程总结起来就:
- 选 Twikoo:AnZhiYu 主题支持最好,数据自有,零成本
- 用 Netlify 部署后端:Vercel 有 TLS 兼容问题,Netlify 一次通过
- 装 hexo-butterfly-envelope 搞定留言板:有动画有趣味,静动结合,SEO 也顾到了
- 踩过的 7 个坑:Netlify 502 无日志、绕道 Vercel 定位 MongoDB 白名单、envId 格式、CSS 选择器、swiper 排序、分类页评论、lazyload 评论数、匿名评论——现在你都知道了,不用再踩一遍
如果你也在给 用 Hexo + AnZhiYu 主题搭建的个人博客 配上评论和留言板,按这篇文章走下来,从头到尾一个小时左右就能搞定。有问题欢迎在 留言板 或者文章评论区留言。
本文于 2026-07-20 发布,使用的技术版本:Hexo 7、AnZhiYu 1.6.14、Twikoo 1.6.x、hexo-butterfly-envelope 1.0.x。







