1. 背景
博客原先托管在 GitHub Pages 上,但从中国大陆访问时经常加载失败。排查后发现两个主要原因:
- GitHub Pages 的 IP 在国内不稳定,DNS 污染和连接超时频繁发生
- 外部 CDN 资源(如
cdn.jsdelivr.net)在国内访问不稳定
本文记录了从问题排查、迁移到 Cloudflare Pages、实现双域名共存,以及替换访问量统计服务的完整过程。
2. 排查博客中的外部依赖
2.1 全面扫描模板文件
对 layouts/ 目录下所有 .html 文件进行正则搜索,查找所有外部 URL 引用:
grep -rn "https://" layouts/ --include="*.html"
2.2 外部依赖清单
| 资源 | 来源 | 位置 | 是否影响渲染 |
|---|---|---|---|
| KaTeX(CSS/JS) | cdn.jsdelivr.net | extend_head.html | 是 |
| Mermaid 图表库 | cdn.jsdelivr.net | baseof.html | 是 |
| Twikoo 评论 | cdn.staticfile.org | extend_footer.html | 否,未启用 |
| 不蒜子访问量 | 本地 /js/busuanzi.pure.mini.js | extend_head.html | 否,本地资源 |
| Font Awesome | 本地 /css/font-awesome.min.css | extend_head.html | 否,本地资源 |
| jQuery | 本地 /js/jquery-3.7.1.min.js | extend_head.html | 否,本地资源 |
2.3 结论
本地资源虽然不受 CDN 影响,但它们都托管在 GitHub Pages 上。GitHub Pages 本身打不开,这些资源自然也无法加载。因此,替换个别 CDN 源无法从根本上解决问题,需要更换整个托管平台。
3. 方案选择与迁移
3.1 方案对比
| 方案 | 成本 | 效果 | 难度 |
|---|---|---|---|
| 绑定自定义域名 + Cloudflare CDN 代理 | 域名费 | 好 | 简单 |
| Cloudflare Pages 替代 GitHub Pages | 免费 | 最好 | 中等 |
| 部署到 Vercel | 免费 | 好 | 中等 |
| 同步部署到 Gitee Pages | 免费 | 一般(需备案) | 复杂 |
最终选择了 Cloudflare Pages,自带全球 CDN(含亚太节点),国内访问速度有明显改善。
3.2 创建 Cloudflare Pages 项目
- 注册 Cloudflare 账户:https://dash.cloudflare.com/sign-up
- 左侧菜单 → Workers & Pages → Create → Pages → Connect to Git
- 授权 GitHub,选择源码仓库
- 配置构建参数:
| 设置项 | 值 |
|---|---|
| Production branch | main |
| Framework preset | Hugo |
| Build command | git submodule update --init --recursive && hugo --gc --minify |
| Build output directory | public |
环境变量 HUGO_VERSION | 0.147.9 |
环境变量 NODE_VERSION | 18 |
- 点击 Save and Deploy,等待 2-5 分钟
- 构建成功后得到免费域名:
<项目名>.pages.dev
注意:Build command 中必须加
git submodule update --init --recursive,因为 PaperMod 主题是通过 git submodule 引用的。缺少这一步,Cloudflare Pages 构建时会找不到主题文件而报错。
4. 实现双域名共存
迁移完成后,希望 GitHub Pages 和 Cloudflare Pages 都能正常访问。但遇到了一个问题:在 pages.dev 上点击站内链接,页面会跳转到 github.io。
4.1 问题原因
Hugo 模板中大量使用了以下函数,它们会根据 baseURL 拼接完整域名:
| 函数 | 生成结果 |
|---|---|
.Permalink | https://xxx.github.io/posts/xxx/ |
| absURL | https://xxx.github.io/img/xxx.jpg |
| absLangURL | https://xxx.github.io/zh/xxx/ |
所有链接都指向 github.io,从 pages.dev 访问时点击链接必然跳转。
4.2 尝试 relativeURLs: true
最初尝试在 config.yml 中设置:
baseURL: https://xxx.github.io/
relativeURLs: true
canonifyURLs: false
relativeURLs: true 的设计意图是让 Hugo 生成相对路径链接。但在 Hugo 0.147.9 中,这个配置只对部分内容生效,模板中使用 | absURL、.Permalink、| absLangURL 生成的链接不受其影响,仍然输出绝对 URL。即使在 localhost:1313 本地开发时,点击页面链接也会跳转到 github.io。
4.3 最终方案:baseURL: /
将 baseURL 设置为根路径 /:
baseURL: /
relativeURLs: true
canonifyURLs: false
这样 Hugo 生成的所有链接都变成 /posts/xxx/、/img/xxx.jpg 这样的根路径形式。浏览器会自动在当前域名下解析这些路径,无论从 github.io、pages.dev 还是 localhost 访问,链接都不会跨域跳转。
4.4 数据流向
迁移后的架构如下:
flowchart TD
A["源码仓库"] -->|"git push"| B["GitHub"]
B -->|"触发 GitHub Action"| C["GitHub Action 构建"]
B -->|"Cloudflare 检测到变更"| D["Cloudflare Pages 构建"]
C -->|"推送静态文件"| E["xxx.github.io"]
D -->|"部署到全球 CDN"| F["xxx.pages.dev"]
E -->|"国内访问慢"| G["中国大陆用户"]
F -->|"亚太节点加速"| G
两条构建通道完全独立,都从同一个源码仓库触发,生成的内容完全一致。
5. baseURL: / 的影响分析
5.1 正常工作的部分
| 方面 | 状态 | 说明 |
|---|---|---|
github.io 访问 | ✅ | 所有链接都是根路径 |
pages.dev 访问 | ✅ | 所有链接都是根路径 |
| 站内页面跳转 | ✅ | 不会跨域跳转 |
| 图片加载 | ✅ | 当前域名加载 |
| 搜索功能 | ✅ | 正常工作 |
| 暗色/亮色主题 | ✅ | 不涉及外部 URL |
| 数学公式(KaTeX) | ✅ | 从 jsdelivr CDN 加载,与域名无关 |
5.2 有轻微影响的部分
| 方面 | 影响 | 严重程度 |
|---|---|---|
| RSS 订阅源 | <link> 和 <guid> 变成相对路径,大部分阅读器兼容 | 轻微 |
| 社交分享图片 | og:image 等变成相对路径,社交平台爬虫可能无法加载预览图 | 轻微 |
| sitemap.xml | 链接变成相对路径,搜索引擎能正常处理 | 无影响 |
对于个人博客来说,这些影响完全可以接受。
6. 替换访问量统计服务:不蒜子 → Vercount
迁移后需要确认双域名下的访问量统计。原先使用的是不蒜子(busuanzi),但存在以下问题:
- 不蒜子以访问域名为 key 分别计数,双域名下数据不互通
- 不蒜子使用 Referrer 方法统计,在部分浏览器和移动端上不准确
- 不蒜子的后端服务稳定性一般
6.1 选择 Vercount
Vercount 是一个基于 Go + Redis 的开源网站流量计数器,由 Next.js 提供后台。相比不蒜子,它有以下优势:
| 特性 | 不蒜子 | Vercount |
|---|---|---|
| 统计方法 | Referrer(过时) | POST 请求(准确) |
| 移动端兼容 | 一般 | 好 |
| 数据同步 | 无 | 自动同步不蒜子历史数据 |
| 后端架构 | 较旧 | Go + Redis |
| 兼容性 | - | 兼容不蒜子的 span 标签 |
6.2 集成方法
第一步:添加脚本
在 layouts/partials/extend_head.html 中,将不蒜子的本地脚本:
<script src="/js/busuanzi.pure.mini.js"></script>
替换为 Vercount 的远程脚本:
<script defer src="https://events.vercount.one/js"></script>
第二步:替换 span ID
在 layouts/partials/footer.html 中,将不蒜子的 span 标签:
<span id="busuanzi_container">
<span class="fa fa-user"></span> <span id="busuanzi_value_site_uv"></span>
<span class="fa fa-eye"></span> <span id="busuanzi_value_site_pv"></span>
</span>
替换为 Vercount 的 span 标签:
<span id="vercount_container">
<span class="fa fa-user"></span> <span id="vercount_value_site_uv">Loading</span>
<span class="fa fa-eye"></span> <span id="vercount_value_site_pv">Loading</span>
</span>
第三步:删除不蒜子本地文件
确认 Vercount 正常工作后,删除 static/js/busuanzi.pure.mini.js。
注意:Vercount 会自动同步不蒜子的历史数据,首次访问时即可看到旧数据被同步过来。
附:本博客坚持采用Canary Deployment进行发布更新与Bug修复,之前的操作是本地实践Canary,如今可以尝试使用此种双域名进行一些很炫酷但不知道稳不稳定的小功能的前端 JS 动态加载,进行真·Canary分流。可供参考:金丝雀发布的使用方法