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

#前言

记得本站刚搭好的时候,文章倒是能看了,但总觉得少点什么。后来翻同行的博客才发现——没有评论区。一篇文章发出去就像石沉大海,读者看了之后想说什么也没地方,有种”单向输出”的感觉。

这也是 用 Hexo 搭建纯静态博客 这类方案的天然短板:生成的是一堆静态 HTML 文件,没有后端,没有数据库,自然也就没有评论功能。好在社区生态丰富,有一大把”第三方评论系统”可以给静态博客接入。

经过一番调研和折腾,本站现在跑起来的效果是:

  • 文章评论区:Twikoo,支持 QQ 头像、匿名评论、邮件通知、管理员面板
  • 留言板:hexo-butterfly-envelope 信封样式,独立页面供读者随意发言
  • 部署成本:零(Netlify + MongoDB Atlas 免费计划)
  • 主题特性:AnZhiYu 对 Twikoo 支持得最完整,评论弹幕、最新评论 widget 都能用

Hexo 博客 Twikoo 评论系统接入后的最终效果示意图,包含文章评论区和管理后台

这篇文章就来完整记录整个过程,包括为什么这么选、具体怎么部署、以及那些让我卡了大半天的坑。如果你也在 搭建 Hexo + AnZhiYu 主题博客并做 SEO 优化,这篇文章应该能帮你少走不少弯路。


#一、AnZhiYu 主题支持哪些评论系统

AnZhiYu 主题内置了 5 种评论系统的适配代码,不需要额外写集成。这 5 种分别是:

系统 后端 数据归属 主题特性支持 推荐度
Twikoo Vercel / Netlify / 腾讯云 + MongoDB ✅ 自有 全特性 ★★★★★
Waline LeanCloud / Vercel / 自建 ✅ 自有 部分(评论弹幕不可用) ★★★★☆
Valine LeanCloud ⚠️ LeanCloud 部分 ★★★☆☆
Artalk 自建 Go/PHP ✅ 自有 部分 ★★★☆☆
Giscus GitHub Discussions ⚠️ GitHub 部分 ★★★☆☆

#核心衡量标准

判断选哪个系统,我主要看三点:

  1. 数据所有权:评论数据应该留在自己手里(自己的 MongoDB / 自己的 GitHub),而不是存在第三方服务商
  2. AnZhiYu 特性支持度:AnZhiYu 有几个高级特性(评论弹幕、最新评论 widget、匿名评论)只有 Twikoo 才完整支持
  3. 国内访问速度: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 的原因其实不复杂,就四个:

  1. AnZhiYu 全特性支持:评论弹幕、最新评论 widget、匿名评论、首页文章卡片评论数——这些如果你用 Waline 或 Giscus,要么不能用,要么要自己改源码

  2. 部署成本为零:Netlify 免费套餐 + MongoDB Atlas M0(512MB 免费) = $0/月,足够个人博客用到天荒地老

  3. 数据在我手里:评论存在我自己的 MongoDB Atlas 实例里,随时可以导出备份,不依赖任何平台锁定

  4. 管理面板好用: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 会自动引导你创建一个集群:

  1. 选择 M0 Free Tier(永久免费,512MB 存储空间,个人博客完全够用)
  2. 云服务商选 AWS 即可,区域尽量选离你近的(亚太区域建议 SingaporeTokyo
  3. 点击 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 allServerless
  • 点击 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 稳。

按照 Twikoo 官方 Netlify 部署文档

  1. 注册 Netlify,用 GitHub 登录最快
  2. 访问 Netlify 一键部署链接(Go 到 Twikoo 后端部署页面 找到 Netlify 一键部署按钮)
  3. 在 Site Settings → Environment Variables 添加:
    • Key: MONGODB_URI
    • Value: 上一步获取的 MongoDB 连接字符串
  4. Settings → Deployment Protection → 设置为 Disabled(必须关,否则匿名评论请求被拦)
  5. 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 部署文档

  1. 注册 Vercel,用 GitHub 登录最快
  2. 访问一键部署链接导入模板:https://vercel.com/import/project?template=https://github.com/twikoojs/twikoo/tree/main/src/server/vercel-min
  3. 输入项目名(如 my-twikoo),点 Deploy
  4. 部署完成后进入项目 Settings → Environment Variables,添加:
    • Key: MONGODB_URI
    • Value: 上一步获取的 MongoDB 连接字符串
  5. Settings → Deployment Protection → 设置为 Disabled(必须关掉!否则匿名评论请求会被拦)
  6. 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
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 开启评论 + 指定 Twikoo
comments:
use: Twikoo # ← 这里填 "Twikoo"
text: true # 显示评论按钮文字
lazyload: false # false = 页面加载即初始化;true = 滚动到评论区才加载
count: true # 文章顶部显示评论数
card_post_count: false # 首页卡片显示评论数(建议 false,有性能开销)

# 2. 填入 Twikoo 环境 ID
twikoo:
envId: https://xxx.netlify.app/.netlify/functions/twikoo # ← 你的 envId,格式因部署平台而异
region: # 腾讯云上海环境填 ap-shanghai,海外/Netlify 留空
visitor: false # true 则用 Twikoo 统计阅读数(替代不蒜子)

改完这两行,重新构建部署,打开任意一篇文章,评论区就出现了。

#4.2 AnZhiYu 是怎么加载评论的(调用链)

理解一下主题内部是怎么加载评论的,对后续排查问题很有帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_config.anzhiyu.yml
comments.use: Twikoo
twikoo.envId: <你的URL>

layout/post.pug:42
if page.comments !== false && theme.comments && comments.use
→ 渲染评论容器

includes/third-party/comments/index.pug
→ 渲染 #twikoo-wrap DOM 容器

includes/additional-js.pug:73
if commentsJsLoad
→ 按需加载评论 SDK

includes/third-party/comments/js.pug
case 'Twikoo' → twikoo.pug

includes/third-party/comments/twikoo.pug
→ 异步加载 twikoo.js CDN
→ twikoo.init({ envId, region })
→ getCommentsCount() 获取评论数

关键判断点在 post.pug:42

1
if page.comments !== false && theme.comments && comments.use

这行保证了:

  • 单篇文章可以设置 comments: false 关闭评论
  • 如果没有配置 comments.use,全站都不会加载评论 SDK
  • 分类页/标签页/首页不会加载(这些页面的 commentsJsLoad 不会被设为 true

#4.3 关闭特定页面的评论

在不需要评论的页面(分类页、标签页、关于页等)的 frontmatter 里加:

1
2
3
4
5
---
title: 分类
type: categories
comments: false
---

本站的分类页、标签页、关于页、音乐页、资源页都设置了 comments: false,避免在这些聚合页出现不必要的评论框。

#4.4 部署后必须做的事

部署成功后,访问你博客的任意一篇文章,评论区应该会显示「填写QQ邮箱就会使用QQ头像喔~。」。此时你需要:

  1. 发一条测试评论,看能不能成功提交
  2. 浏览器访问 envId URL,进入「管理面板」,设置管理员密码
  3. 配置 SMTP 邮件通知(可选,用你自己的邮箱)
  4. 配置 Akismet 反垃圾(可选,免费注册 akismet.com)
  5. 在 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。原因:

  1. 信封动画挺有意思,适合留言板这种轻松的场景
  2. 插件生成的页面包含静态文字内容(来自 message 数组),搜索引擎可索引,比纯评论区页面 SEO 略好
  3. 可以自定义 front_matter,补上 description 字段,避免 SEO 丢分

#5.2 安装与配置

1
npm install hexo-butterfly-envelope --save

然后在 _config.yml 里配置(不是 _config.anzhiyu.yml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
envelope_comment:
enable: true
custom_pic:
cover: https://npm.elemecdn.com/hexo-butterfly-envelope/lib/violet.jpg
line: https://npm.elemecdn.com/hexo-butterfly-envelope/lib/line.png
beforeimg: https://npm.elemecdn.com/hexo-butterfly-envelope/lib/before.png
afterimg: https://npm.elemecdn.com/hexo-butterfly-envelope/lib/after.png
message:
- 有什么想问的?
- 有什么想说的?
- 有什么想吐槽的?
- 哪怕是有什么想吃的,都可以告诉我哦~
bottom: 自动书记人偶竭诚为您服务!
height: 1050px
path: comments
front_matter:
title: 留言板
comments: true
top_img: false
type: envelope
description: 番茄说博客的留言板页面,欢迎在这里留下你的想法、问题、建议或吐槽,我会认真阅读每一条留言并回复。

几个关键点:

  • path: comments 控制生成的页面路径是 /comments/
  • front_matter.description 这个是 SEO 重点——插件生成的页面没有原生的 description 字段(它是 pug 渲染的),不手动补上会丢很多 SEO 分
  • comments: true 让 AnZhiYu 在这个页面也挂上 Twikoo 评论区
  • type: envelope 插件用这个字段来渲染信封动画和特定样式

#5.3 在菜单里加上入口

_config.anzhiyu.yml 的 menu 里加一行:

1
2
menu:
留言板: /comments/ || anzhiyu-icon-envelope

#六、踩坑实录

踩过的坑比你想的多,一个一个说。

#坑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.ymltwikoo.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.mdcomments: false
  • source/tags/index.mdcomments: false
  • source/about/index.mdcomments: false
  • source/music/index.mdcomments: 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.ymlenvelope_comment.front_matter.description 添加,这个值会被注入到生成的页面中:

1
2
3
envelope_comment:
front_matter:
description: 番茄说博客的留言板页面,欢迎在这里留下你的想法、问题、建议或吐槽,我会认真阅读每一条留言并回复。

#7.2 评论区的语义化

AnZhiYu 主题内部的评论模板中,评论标题使用了 <h3> 标签,评论输入框有 aria-label 属性,这些对无障碍和 SEO 都有帮助。如果你的主题版本比较老,可以考虑检查一下这些细节。

#7.3 评论数对 SEO 的间接帮助

文章页顶部的评论数(comments.count: true)虽然是一行数字,但它向搜索引擎传递了一个信号:这篇文章有社交互动。Google 虽然不直接用评论数作为排名因子,但丰富的页内元素有助于提升 dwell time(用户停留时长)。

更多关于本站 SEO 优化的细节,见 谈谈本站是如何优化SEO的


#总结

从零评论到现在的 Twikoo + 信封留言板,整个过程总结起来就:

  1. 选 Twikoo:AnZhiYu 主题支持最好,数据自有,零成本
  2. 用 Netlify 部署后端:Vercel 有 TLS 兼容问题,Netlify 一次通过
  3. 装 hexo-butterfly-envelope 搞定留言板:有动画有趣味,静动结合,SEO 也顾到了
  4. 踩过的 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。