把 Halo 博客部署到 Cloudflare Workers:更快、更稳,也更安全
如果博客已经使用 Halo,为什么还要在前面增加 Cloudflare Workers?
答案不是为了替代 Halo。Halo 仍然是最舒服的写作与内容管理后台;Workers 负责把已经发布的页面变成离访客更近的静态内容。这样既保留 Halo 的编辑体验,又获得边缘网络的速度和稳定性。

一、为什么这样做
1. 全球访问更快
普通部署中,每位访客都要连接 Halo 源站。距离远、服务器带宽小或并发较高时,首屏会明显变慢。
静态化以后,HTML、CSS、JavaScript 等文件由 Cloudflare 边缘节点直接返回。多数访问不必再穿透到 Halo,网络路径更短,响应也更稳定。
2. 减轻 Halo 源站压力
Halo 需要运行 Java、数据库、主题和插件。即使访客只是阅读文章,动态渲染也会消耗连接与计算资源。
把公开页面导出成静态文件后,源站主要负责:
- 后台写作和管理;
- 生成新的公开内容;
- 评论、点赞等确实需要写入的动作;
- 为下次静态导出提供数据。
这很适合配置不高的 VPS,也能减少突发流量对后台的影响。
3. 源站故障时更有韧性
只要最近一次静态版本已经成功部署,即使 Halo 短时重启或维护,访客仍能浏览已发布的页面。动态功能可能暂时不可用,但文章本身不会跟着消失。
4. 缩小攻击面
不要把 Workers 做成“任意路径都能转发”的开放代理。公开页面静态化之后,动态代理只放行必须的路径,再叠加文章白名单、Origin 校验、请求格式检查和 IP 限流,可以显著减少源站暴露面。

二、整体架构
一个实用的分工是:
访客
├─ HTML / CSS / JS / 普通图片 → Cloudflare Workers Static Assets
└─ 评论 / 点赞 / 实时统计 → Worker 严格校验 → Halo API
管理员
└─ 写作、编辑、发布 → Halo 后台
同步任务
└─ 从 Halo 导出公开页面 → 检查 → wrangler deploy
图片可以继续由 Halo 附件系统管理,并把存储策略设为 Cloudflare R2。这样正文图片不需要塞进 Worker 包中,更新文章时也更自然。
三、准备工作
你需要:
- 一套能正常访问的 Halo 站点;
- Cloudflare 账号和一个 Worker;
- Node.js 20 或更新版本;
- Wrangler;
- 一个用于存放静态导出结果的目录,例如
dist/。
初始化项目:
mkdir halo-workers-static
cd halo-workers-static
npm init -y
npm install -D wrangler
mkdir -p src scripts dist
登录 Cloudflare:
npx wrangler login
生产环境更建议使用最小权限 API Token,并放在受限环境变量或 CI Secret 中。不要把 Token、Halo 密码或 R2 密钥提交进 Git。
四、配置 Worker 静态资源
创建 wrangler.jsonc:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "halo-static-blog",
"main": "./src/index.js",
"compatibility_date": "2026-07-20",
"vars": {
"ASSET_ORIGIN": "https://halo-origin.example.com"
},
"assets": {
"directory": "./dist",
"binding": "ASSETS",
"run_worker_first": true,
"not_found_handling": "404-page"
}
}
最小 Worker 入口可以先这样写:
export default {
async fetch(request, env) {
return env.ASSETS.fetch(request);
},
};
这时 Workers 只负责读取 dist/ 中的静态文件。后面如果需要评论和点赞,再增加明确的 API 路由,而不是把全部请求直接转发给 Halo。
五、从 Halo 导出静态页面
导出器通常要做五件事:
- 从 Halo 获取首页、文章、分类、标签、分页等公开路由;
- 下载页面依赖的同源 CSS、JavaScript 和主题资源;
- 将页面写入对应目录,如
/archives/demo/写成dist/archives/demo/index.html; - 把指向源站的内部链接改成正式域名或相对路径;
- 检查是否残留源站地址、动态模板标记、超大文件和 404 资源。
建议维护一份明确的公开路由清单,不要盲目遍历 Halo 的管理接口。静态导出完成后至少检查:
node --check src/index.js
node scripts/check.mjs
npx wrangler deploy --dry-run
只有三项都通过,再正式部署。

六、部署到 Cloudflare Workers
确认 dist/ 内容正确后执行:
npx wrangler deploy
成功后会得到一个 workers.dev 地址。先检查:
- 首页、文章页、分类页是否返回 200;
- CSS、JavaScript 和图片是否加载;
- 手机端导航与深色模式是否正常;
- 页面中是否还出现 Halo 源站内部地址;
- 404 页面是否符合预期。
然后在 Cloudflare Dashboard 中给 Worker 添加自定义域名。切换正式域名前,可以先降低 DNS TTL,并准备随时回退到上一个 Worker 版本。
七、评论和点赞不能只靠静态文件
静态 HTML 中的评论数、点赞数只是导出时的快照。要获得实时数据,需要一个小型统计接口:
GET /static-api/post-stats?name=<post-name>
Worker 收到请求后向 Halo 查询公开文章统计,只返回经过清洗的数字:
{
"upvote": 12,
"comment": 3,
"visit": 128
}
点赞和评论写入必须更严格:
- 只允许固定的 API 路径;
- 校验
Origin; - 检查文章是否在“已发布且公开”的白名单内;
- 验证
group、plural、文章名称和请求体; - 按客户端 IP 限流;
- 响应设置
Cache-Control: no-store。
关于取消点赞
Halo 2.25.4 的公开接口中,POST /trackers/downvote 表示增加“踩”,并不会直接减少已有的 upvote。若界面只在浏览器里 -1,刷新后就会恢复,这是“假取消”。
如果产品需要真正的切换式点赞,可以在 Worker 侧用 Durable Object 持久记录取消量,展示值按:
展示点赞数 = Halo upvote − 已持久化取消量
同时仍把每次变更限制在公开文章、可信来源和速率限制之内。这样取消后的数字跨刷新仍一致。更长期、更标准的方案,是为 Halo 编写专门的取消点赞插件/API,让权威计数完全落在 Halo 内部。
八、图片为什么放在 Halo 的 Cloudflare R2 分类
本文图片就是先上传到 Halo 附件库,再选择 Cloudflare R2 存储策略和对应附件分组。这样有几个好处:
- 图片仍能在 Halo 后台统一搜索和管理;
- 正文只保存稳定的图片 URL;
- 图片不占 Worker 静态资源包大小;
- R2 适合存放大量对象,并可配合独立图片域名;
- 以后更换主题或重新导出,图片无需重复上传。
上传时建议同时设置:
- 合理的 WebP/AVIF 转换;
- 长缓存头;
- 不可猜测或避免冲突的文件名;
- 限制上传 MIME 和体积;
- 定期备份 R2 Bucket 配置与关键附件。
九、自动同步建议
稳定后可以把流程封装为:
set -euo pipefail
node scripts/export.mjs
node scripts/check.mjs
npx wrangler deploy
再由 CI 或定时任务调用。但自动化前必须保留三道保险:
- 导出失败时不替换上一版
dist/; - 检查失败时禁止部署;
- 保存上一次可用 Worker Version,便于快速回滚。
不要每次心跳都重新部署。只有 Halo 内容指纹发生变化时才同步,可以减少 Workers 请求和无意义上传。
十、这种方案适合谁
适合:
- 以公开文章阅读为主的个人博客;
- 希望保留 Halo 后台体验,但降低源站压力;
- 访问者分布较广;
- 能接受发布后经过一次导出才更新前台。
不太适合:
- 页面高度个性化、每个用户内容不同;
- 大量内容必须秒级实时更新;
- 依赖许多无法代理或静态化的插件;
- 没有能力维护导出检查与回滚流程。
总结
这套方案不是“把 Halo 搬进 Workers”,而是明确分层:
- Halo 负责创作、管理与权威内容;
- R2 负责图片等对象存储;
- Workers 负责全球静态分发;
- 少量受控 API 保留评论、点赞和实时统计;
- 导出检查、最小权限与回滚保证发布安全。
最终得到的是一个打开更快、源站更轻、故障时更有韧性,同时仍然方便写作的 Halo 博客。