当前结论

本博客目前采用 Twikoo 作为默认评论区,后端运行在 Cloudflare Worker,评论内容放在 D1,评论图片和头像放在 R2;Quartz 页面只负责加载和定制界面。访客可以使用昵称和邮箱留言,不必先登录 GitHub。GitHub 评论仍作为同一标题下的可选入口保留。

为什么从 GitHub 评论切换

最初的评论入口偏向 GitHub。它的优点是身份和垃圾评论控制比较直接,但访客必须拥有 GitHub 账号并完成登录,留言门槛比较高。Giscus 仍然适合喜欢 GitHub Discussions 的读者,不过不适合作为唯一评论方式。

Waline 的体验更接近传统博客评论区,但之前的部署依赖 Vercel,运行环境、数据库和迁移关系没有被清晰地统一管理。Artalk 可以自托管,可是放在 NAS 后再通过 Cloudflare Tunnel 访问时,速度和稳定性不够理想。综合考虑后,选择了部署资料较成熟、可以直接使用 Cloudflare 基础设施的 Twikoo。

这次调整的目标有三个:

  • 访客可以直接用昵称、邮箱和正文留言,不强制 GitHub 登录;
  • 评论数据和图片有明确的存储位置,不和静态博客文件混在一起;
  • 前端仍然保留 Quartz 的设计语言,并且能够修复 SPA 路由、头像、邮件等实际问题。

整体架构

评论系统被拆成前端、接口、数据库和对象存储四层:

访客浏览器

Quartz 静态页面(博客前端)

Twikoo 客户端脚本

Cloudflare Worker:twikoo.brmys.cn
    ├── D1:评论、回复、点赞和管理数据
    └── R2:评论图片、头像等上传文件

Quartz 和 Twikoo 后端是两个可以独立发布的部分。以后即使把博客前端从 Vercel 换到 Cloudflare Pages,也不需要迁移评论数据;只要前端继续请求同一个 Worker 地址即可。

Cloudflare 后端做了什么

后端部署的核心步骤是:

  1. 在 Cloudflare 创建 D1 数据库和 R2 存储桶;
  2. 使用 Twikoo 的 Cloudflare 部署模板创建 Worker;
  3. 初始化 Twikoo 所需的数据表,并绑定 D1;
  4. 绑定 R2,用于评论图片和头像上传;
  5. 配置博客域名的跨域访问和 Worker 自定义域名;
  6. 用健康检查、版本接口、评论读取和 CORS 请求逐项验证。

Quartz 中只需要把 Worker 地址传给组件:

Component.TwikooComments({
  envId: "https://twikoo.brmys.cn",
  lang: "zh-CN",
  version: "1.6.44",
})

这里的 envId 是公开接口地址,不是管理密码。真正需要保护的是 Twikoo 管理密码、QQ 邮箱授权码、飞书机器人 Webhook 等后端配置,它们不应该写入前端代码、文章或 Git 仓库。

Quartz 前端是如何接入的

本次没有直接把 Twikoo 的默认页面原样嵌入,而是在 Quartz 中增加了三个层次的定制:

  • TwikooComments.tsx:输出评论区容器、标题和 GitHub 评论切换按钮;
  • twikoo.inline.ts:加载客户端、处理 SPA 页面切换、头像上传、邮件配置和管理面板编辑;
  • twikoo.scss:负责评论区布局、头像裁剪、深浅色主题和管理面板样式。

评论标题使用“评论或留言”,默认显示 Twikoo。标题旁边保留 GitHub 评论按钮,只有读者主动点击时才延迟加载 Giscus。这样博客首次打开不会因为 GitHub 网络问题卡住,Twikoo 也不会和 Giscus 争夺页面空间。

这次解决的几个实际问题

1. 页面出现巨大图案或像乱码

最开始只加载了 Twikoo 的 JavaScript,没有同步加载相同版本的 CSS,浏览器就会把表单和 SVG 以未样式化状态展示出来。后来前端脚本增加了 Twikoo CSS 的 CDN 回退顺序,并在 CSS 加载后再初始化组件,解决了这个问题。

2. 点设置、点赞或评论后页面变形

Quartz 使用了 SPA 导航。Twikoo 内部有一些 href="#" 的关闭、点赞、回复和设置操作,点击后会被 Quartz 当成页面锚点处理,造成组件重新渲染或样式消失。

现在会对 Twikoo 内部的 # 链接阻止默认锚点跳转,同时保留 Twikoo 自己的点击逻辑,所以这些操作不会再触发博客路由。

3. 头像显示不全

某些主题样式给头像图片设置了过大的 max-width,图片虽然加载成功,却被压缩或只显示一部分。现在头像容器和图片都使用相同的宽高,配合 object-fit: cover,头像可以稳定地显示为正方形。

4. 头像上传和评论图片上传

Twikoo 默认可以根据邮箱和昵称生成头像;如果访客点击评论表单左侧头像,则可以从本地选择图片。图片会通过 Worker 的上传接口写入 R2,浏览器只在本地保存最近使用的头像地址,发表评论时再把头像地址补充到提交数据中。

评论正文里的图片上传也走 R2,而不是依赖某个未配置的图床服务。因此看到“博主未配置图床服务”时,应该检查 Worker 的 R2 图片上传配置,而不是要求访客填写图床地址。

5. QQ 邮件通知

Twikoo 的邮件通知在 Worker 端发送。QQ 邮箱使用 SMTP 的 smtp.qq.com:465 加密连接,发送者地址和 SMTP 用户名保持一致,密码位置填写邮箱授权码。

前端管理面板已经做了简化:选择 QQ 后,主机、端口、TLS、主题和模板等不相关字段会隐藏,避免误填。Worker 端还修正了 SMTP 的发送顺序,必须先完成 MAIL FROM,再执行 RCPT TO,最后提交邮件正文,否则会出现 SMTP 503RCPT TO 失败

6. 管理面板和内容编辑

Twikoo 原有管理面板在博客主题下对比度较低,所以增加了独立的浅色/深色表面、输入框、标签、按钮和评论卡片样式,并扩大了弹窗可用区域。

管理面板的评论卡片增加了“编辑”入口。编辑内容会先经过标签、属性和 URL 的白名单清理,再提交给 Twikoo 管理接口,避免为了方便编辑而把任意 HTML 直接写回数据库。

飞书通知应该放在哪里

如果要把新评论通知到飞书,推荐使用 Twikoo 后端的 Pushoo 通道:在 Worker 的环境变量中设置 Feishu 通道和机器人 Webhook,通知由 Worker 发出,前端只负责提交评论。

飞书 Webhook 和邮箱授权码都属于后端秘密,不能放在 Quartz 的 quartz.layout.ts、浏览器 localStorage 或公开文章中。当前实现已经预留了邮件通知能力;飞书通知是否启用,只需要继续配置 Worker 端的通知变量并做一次测试,不需要重新设计评论区。

数据到底存在哪里

评论文本、回复、点赞和管理状态在 Cloudflare D1;评论图片和头像文件在 Cloudflare R2;静态博客页面仍然由 Quartz 构建后发布。GitHub 仓库不会因为 Twikoo 评论而保存评论正文,也不需要把访问令牌放进博客。

这也意味着迁移时要分开处理:迁移博客前端只需要重新部署静态站点;迁移评论区则需要备份 D1 和 R2,并根据目标平台的数据结构导入。Waline 的旧数据不会因为切换到 Twikoo 自动出现,后续如有需要,需要单独做一次字段映射和导入验证。

目前的使用建议

每次修改 Worker 或 Quartz 评论代码后,先在预览站验证以下动作:发表评论、回复、点赞、设置、头像上传、正文图片上传、管理面板编辑和邮件测试。确认无误后再发布正式博客。

当前方案的核心取舍是:用 Cloudflare 的托管能力换取较低的运维成本,用 Quartz 的少量定制代码换取更符合博客主题的界面;而 GitHub 评论作为备用入口保留,避免某一条网络链路异常时完全没有评论功能。

相关概念:博客(2026.08.19)博客评论系统(2026.08.19)