07 · 博客增强(可选)
核心 6 步走完后博客已经可用。本篇是可选增强——评论系统、访问统计、自定义域名等。一旦博客有了"互动属性",会从"单方输出"变成"双向交流"。
一、评论系统(Giscus,强烈推荐)⭐
为什么选 Giscus
| 方案 | 后端 | 免费 | 中文注释 | 数据归属 |
|---|---|---|---|---|
| Giscus ✅ | GitHub Discussions | ✅ 完全免费 | ✅ | 你自己的 GitHub 仓库 |
| Utterances | GitHub Issues | ✅ 免费 | ✅ | 你的 GitHub Issues |
| Disqus | 自家服务 | 免费但有广告 | ✅ | 在 Disqus 那边 |
| Waline | 自建 + LeanCloud | 免费 | ✅ | LeanCloud(要备案,有限制) |
| 自建(Comment, Cusdis 等) | 自己搭后端 | 要服务器 | ✅ | 自己 |
Giscus 是博客评论的标准答案:不要数据库、不要服务器、读写直接走 GitHub 账号登录、所有评论数据托管在你自己的 Discussions 里——意味着永远不会被"评论平台关闭"绑架。
Giscus 的工作流程
访问者打开博客文章
↓
页面加载 giscus.app/client.js
↓
读取你配置的 GitHub 仓库的 Discussions
↓
按 mapping 规则(pathname/title 等)找到对应讨论串
↓
显示评论 + 留言框
↓
访问者点击 "Sign in with GitHub" → 留言
↓
评论作为 Discussion comment 发到 GitHub前置条件
1. GitHub 仓库必须公开
Discussions 功能依赖仓库公开。你的源仓库(如 mmqqdd/botc-learning-notes)已经是公开的就 OK。
2. 启用 Discussions
GitHub 仓库页 → Settings → General → 滚动到 "Features" → 勾选 Discussions
3. 安装 giscus app
https://github.com/apps/giscus → Install → 选择仓库授权
自助生成配置
按页面引导填写:
| 字段 | 选什么 |
|---|---|
| 仓库 | 填 your-username/your-repo(必须已启用 Discussions + 装了 giscus app) |
| 页面 ↔️ discussion 映射关系 | 推荐 pathname(每个 URL 一个讨论串) |
| Discussion 分类 | 推荐 Announcements(受限分类,避免 spam) |
| 特性 | 推荐勾选"启用反应",其他默认 |
| 主题 | 选 首选颜色方案(让 Giscus 跟随系统暗色模式) |
页面会自动生成 <script> 配置代码,里面有 data-repo-id、data-category-id 等,复制下来备用。
VitePress 集成(核心)
VitePress 默认主题里没有评论系统,要通过自定义主题 + 槽位(slot)注入。
Step 1:创建组件文件
.vitepress/theme/Giscus.vue:
<script setup lang="ts">
import { ref, watch, onMounted, nextTick } from 'vue'
import { useData, useRoute } from 'vitepress'
const { isDark } = useData()
const route = useRoute()
const container = ref<HTMLDivElement | null>(null)
// [REPLACE] 把下面这些值改成 giscus.app 给你生成的
const GISCUS_CONFIG = {
repo: 'your-username/your-repo',
repoId: 'R_kgDOxxxxx',
category: 'Announcements',
categoryId: 'DIC_kwDOxxxxx',
mapping: 'pathname',
strict: '0',
reactionsEnabled: '1',
emitMetadata: '0',
inputPosition: 'top',
lang: 'zh-CN',
loading: 'lazy',
} as const
function renderGiscus() {
if (!container.value) return
container.value.innerHTML = ''
const script = document.createElement('script')
script.src = 'https://giscus.app/client.js'
script.crossOrigin = 'anonymous'
script.async = true
const attrs: Record<string, string> = {
'data-repo': GISCUS_CONFIG.repo,
'data-repo-id': GISCUS_CONFIG.repoId,
'data-category': GISCUS_CONFIG.category,
'data-category-id': GISCUS_CONFIG.categoryId,
'data-mapping': GISCUS_CONFIG.mapping,
'data-strict': GISCUS_CONFIG.strict,
'data-reactions-enabled': GISCUS_CONFIG.reactionsEnabled,
'data-emit-metadata': GISCUS_CONFIG.emitMetadata,
'data-input-position': GISCUS_CONFIG.inputPosition,
'data-theme': isDark.value ? 'dark' : 'light',
'data-lang': GISCUS_CONFIG.lang,
'data-loading': GISCUS_CONFIG.loading,
}
for (const [key, value] of Object.entries(attrs)) {
script.setAttribute(key, value)
}
container.value.appendChild(script)
}
// 跟随 VitePress 的明暗主题切换
watch(isDark, (dark) => {
const iframe = document.querySelector<HTMLIFrameElement>('iframe.giscus-frame')
iframe?.contentWindow?.postMessage(
{ giscus: { setConfig: { theme: dark ? 'dark' : 'light' } } },
'https://giscus.app',
)
})
// SPA 路由切换时重新渲染(每篇文章独立讨论串)
watch(
() => route.path,
() => {
nextTick(() => renderGiscus())
},
)
onMounted(() => {
renderGiscus()
})
</script>
<template>
<div class="giscus-wrapper">
<h3 class="giscus-title">💬 评论与建议</h3>
<p class="giscus-hint">
读完有想法?欢迎在下面留言。评论使用 GitHub 账号登录,会同步到本仓库的 Discussions。
</p>
<div ref="container" class="giscus" />
</div>
</template>
<style scoped>
.giscus-wrapper {
margin-top: 48px;
padding-top: 24px;
border-top: 1px solid var(--vp-c-divider);
}
.giscus-title {
font-size: 18px;
font-weight: 600;
margin: 0 0 8px;
}
.giscus-hint {
font-size: 14px;
color: var(--vp-c-text-2);
margin: 0 0 20px;
}
</style>Step 2:扩展默认主题
.vitepress/theme/index.ts:
import DefaultTheme from 'vitepress/theme'
import { h } from 'vue'
import type { Theme } from 'vitepress'
import Giscus from './Giscus.vue'
const theme: Theme = {
extends: DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
// 评论组件插入到每篇文档末尾
'doc-after': () => h(Giscus),
})
},
}
export default themeStep 3:跑起来验证
npm run dev打开任意一篇文章,滚动到底部应该能看到评论框。
关键代码解读
为什么用 doc-after 槽位
VitePress 默认主题暴露了多个槽位(slot),文档页可用的有:
| Slot | 位置 |
|---|---|
doc-top | 文档正文上方 |
doc-before | 正文之前 |
doc-after ✅ | 正文之后(评论的最佳位置) |
doc-footer-before | 文档 footer 之前 |
doc-after 是评论组件的最佳位置——读者看完文章自然就到这里。
为什么要 watch(route.path) 重新渲染
VitePress 是 SPA。当用户从文章 A 切到文章 B:
- URL 变了,但 Giscus 的
<iframe>还是老的(保持 A 的评论) - 必须监听路由变化、重新执行
renderGiscus()才能切换到 B 的评论
不加这一段会导致"换文章评论不变"的诡异行为。
为什么要 watch(isDark) 发 postMessage
Giscus 是嵌入的 iframe,不会自动跟随主页的暗色模式。
通过 postMessage 给 iframe 发 setConfig 命令,可以动态切换 Giscus 主题——让评论框和博客本身的暗/亮模式保持一致。
不加这一段,用户切到暗色模式时,评论框还是白底,体验割裂。
常见坑
坑 1:评论框不显示
- 检查仓库是公开的且启用了 Discussions
- 检查
giscus app已安装到该仓库 - 检查浏览器控制台有没有 4xx/5xx 错误(可能是 repoId 或 categoryId 写错了)
坑 2:换文章评论不变
→ 没监听 route.path 变化(参考上面的代码)
坑 3:暗色模式切换评论框还是亮色
→ 没监听 isDark + 发 postMessage(参考上面的代码)
坑 4:本地 dev 看到评论但部署后看不到
→ 通常是 Cloudflare Pages / Vercel 的 build 没拷贝 .vitepress/theme/ 目录。确认 .gitignore 没把 theme 目录排除。
坑 5:Discussion 分类错误
giscus.app 自助生成时如果选了一个不限制写入的分类(如 General),任何 GitHub 用户都能直接 New Discussion 制造垃圾内容。推荐用 Announcements 这种受限分类——只允许评论,不允许新建话题。
收益与代价
收益:
- ✅ 文章下面立刻有了"反馈渠道"
- ✅ 反应(emoji)功能让读者无压力表达喜好
- ✅ 所有评论数据沉淀在你自己的 GitHub Discussions
- ✅ 评论历史和代码版本一起备份
代价:
- ⚠️ 评论需要 GitHub 账号登录(部分非技术读者会被劝退)
- ⚠️ 国内访问 giscus.app 偶发抽风(但比 Vercel 强)
二、访问统计(推荐)
待补充。可选方案:Cloudflare Web Analytics(免费、隐私友好)、Plausible、Umami(自建)。
三、自定义域名
参见 05-deploy.md 第八章。
四、自定义 CSS / 主题
VitePress 默认主题足够用。需要小改时可在 .vitepress/theme/index.ts 引入自定义 CSS:
import './custom.css'待补充:常用自定义场景(字号、字体、颜色等)。
五、SEO 优化
待补充。VitePress 默认有
<title>/<meta description>,进阶可加 sitemap、Open Graph 等。
总结
"功能" 不是越多越好——
每加一个功能都要问:
是不是真的能让"读者 → 创作者"的反馈环短一点?
评论系统能短,所以加;
访问统计某种程度能短,可以加;
留言板、聊天室、订阅 push……大概率不能短,慎加。