个人博客这件事最容易犯的错,是一上来就想”我要做个什么”。
我这次反过来做:先确定”最省事的方案能长什么样”,再倒推技术选型。 结果是:
成本 ¥0(Cloudflare 侧;域名另算,见下)域名 可以完全不买(用免费的 *.pages.dev)备案 不用(原因后面讲清)服务器 不用(Pages 是静态托管)CDN Cloudflare 全球 anycast,默认就有HTTPS 自动签发,自动续期整套东西的核心就一句话:
GitHub 存源码,Cloudflare Pages 跑构建,两边一个 push 打通。
一、整条链路长什么样
几个值得注意的点:
- Cloudflare 主动拉代码,不是 GitHub 推产物。所以你的密钥不需要交给 GitHub Actions,Cloudflare 用的是它自己跟 GitHub 的 OAuth 授权。
- 部署是原子的:新构建完整成功后才切换,失败不影响线上版本。这一点比很多自建方案强 —— 你 push 了一个坏 commit,线上还是好的。
- 每次 push 都有独立的部署 URL,除了 production 还有一堆 preview 部署,24 小时后自动清理。想看草稿效果随便推。
二、免费额度到底有多少
Cloudflare Pages 免费版(Free plan)的关键数字:
| 项目 | 免费额度 | 说明 |
|---|---|---|
| 站点数量 | 无限制 | |
| 构建次数 | 500 次 / 月 | 个人博客一天推几次都绰绰有余 |
| 单次部署文件数 | 20,000 | |
| 单文件大小 | 25 MiB | 所以图片别往仓库里塞特大的原图 |
| 构建超时 | 20 分钟 | 静态博客一般几秒 |
| 带宽 | 不限流量 | Pages 不按流量计费,这是它最大的优势 |
| 自定义域名 | 100 个 / 项目 |
TIP“不限流量”这一条要重点说一下。很多静态托管(Vercel Hobby、Netlify 免费版)都有流量/构建额度限制,超了就限速或停服。Cloudflare Pages 免费版不按流量收费 —— 这也是我最后选它的主要原因之一。
关键约束:注册时不要绑信用卡。 绑卡后会进入需要验证的账户体系,一堆原本免费的功能会变成”需要开通付费”。这个博客从头到尾只在免费额度里跑。
三、为什么不用备案
这个是很多国内用户的顾虑,讲清楚原理就不神秘了。
ICP 备案的触发条件是两个同时成立:
① 网站内容/服务器 面向中国大陆境内用户提供服务② 服务器 部署在中国大陆境内(境内 IP 的机房)
备案才需要。而 Pages 的机器在 Cloudflare 的全球 anycast 网络上(境外节点)。你的域名解析过去,落到的是境外机房的 IP,不满足第 ② 条,所以不需要备案。
WARNING需要避开的坑:
- 不要开 Cloudflare 中国网络(China Network) —— 那个是企业版付费功能,明确要求域名已完成 ICP 备案。开了反而会要求你有备案。
- 如果你买的是
.cn域名并做实名认证,注意.cn域名本身在境内就有实名要求,这跟站点备案是两件事。
我的站点现在跑在 ycxdev.com 上,走的就是海外 anycast。国内访问速度取决于运营商和跨境链路,但这不是”不合规”的问题,只是延迟问题。
那域名要不要买
分两种情况:
| 用免费子域名 | 用自己买的域名 | |
|---|---|---|
| 地址 | xxx.pages.dev | ycxdev.com |
| 花钱 | ¥0 | 域名年费(.com 通常几十块/年) |
| 备案 | 不需要 | 仍然不需要(托管仍在境外) |
| 换托管商 | 只能改 Cloudflare 项目名 | 改 NS 记录,随时搬 |
| 邮件 / 其他服务 | 用不上 | 可以拿去做邮箱、Redirect 等 |
| Open Graph 观感 | 一般 | 好 |
先用 *.pages.dev 把内容跑起来,等真的有内容了再买域名。 我一开始也是纯 pages.dev,后来才换的 —— 换域名只是加一条 DNS 记录 + 在 Pages 里绑一次,站点内容一个字都不用动。
NOTE换域名之后有件事必须手动改,否则线上会静默出问题:
astro.config.mjs 的 site: → canonical / sitemap / RSS 的绝对地址public/admin/config.yml 的 site_url → CMS 里生成的链接.github/workflows/indexnow.yml → 推给搜索引擎的 host 和站点地址CF 上绑定的 Web Analytics 站点这些地方都拿”站点绝对地址”来拼 URL。改域名时漏改一个,canonical 就会指向旧域名,搜索引擎会当成两个站处理。
WARNING需要避开的坑:
- 不要开 Cloudflare 中国网络(China Network) —— 那个是企业版付费功能,明确要求域名已完成 ICP 备案。开了反而要求你有备案。
.cn/.com在境内注册商买域名要做域名实名认证,但这是”域名实名”,跟”网站 ICP 备案”是两件不同的事,别混。- 域名买了不等于站点备案了,也不等于需要备案 —— 取决于你的服务器在哪。
四、顺手白嫖的 Cloudflare 设施
Pages 只是入口。同一个账号下免费额度里还有一堆能直接用的东西,我都用上了:
静态托管 + CDN"] A --> C["Pages Functions
10万请求/天"] A --> D["KV
1000 写入/天"] A --> E["D1 (SQLite)
500万行读/天"] A --> F["Workers
10万请求/天"] A --> G["Web Analytics
无 Cookie 无采样"] A --> H["Turnstile
人机验证"] B --> C C --> D C --> G
具体我这个博客用到的:
1. Pages Functions + KV 做阅读量
纯静态站点没法记”这篇文章被看了多少次”,因为静态文件没有后端状态。用 Pages Functions(本质是 Workers)当后端,数据丢 KV:
// functions/api/views.ts (简化)export async function onRequestGet({ request, env }) { const path = new URL(request.url).searchParams.get("path"); const key = `v:${path}`; const n = parseInt((await env.VIEW_COUNTS.get(key)) ?? "0") + 1; await env.VIEW_COUNTS.put(key, String(n)); return Response.json({ views: n });}WARNINGKV 免费额度只有 1000 次写入 / 天。 一个写入对应一次自增。所以这个计数接口必须做防重 —— 我在前端用
lastCountedPath保证同一次浏览只计一次。超额了 KV 会直接拒绝写入(不会扣费,但会报错)。这也是为什么阅读量要”点开才显示”而不是放在首屏阻塞渲染。
2. Web Analytics
Cloudflare 的 Web Analytics 免费、无 Cookie、不采样,而且关键是:
它不要求站点托管在 Cloudflare。
*.pages.dev这种不属于你自己 zone 的域名也能用(拿不到 zone 级功能,但 RUM 可以)。
打开方式就两行:
<!-- Cloudflare 自动注入的 beacon --><script defer src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token":"<你的 32 位 token>"}'></script>NOTE那个 token 本身就是公开的 —— 它必须出现在前端 JS 里才能工作。所以看到别人页面上有这个 token 不构成安全问题,不用当成密钥来藏。
3. OAuth 中转 Worker
我给博客配了一个 Sveltia CMS,需要 GitHub OAuth 登录。GitHub OAuth 的回调地址必须是注册过的绝对 URL,用 localhost 开发时很麻烦。我用一个免费 Worker 做中转,把回调先落到 Worker 上再 302 回本地。
这是 Workers 免费额度(10 万请求/天)最典型的用法。
4. 用不到的(因为要绑卡)
| 产品 | 为什么跳过 |
|---|---|
| Cloudflare R2 | 需要绑卡开通 |
| Cloudflare Images | 需要绑卡 |
| Cloudflare Stream | 需要绑卡 |
| Workers Paid / Pages 付费增强 | 需要绑卡 |
| China Network | 需要备案 + 企业合同 |
五、Cloudflare Pages 部署 Astro 的几个坑
Astro 是 Cloudflare 官方推荐的框架之一(他们有专门的 Pages 集成文档),但纯静态输出用 Pages 时不需要 adapter。我踩过的坑:
坑 1:Node 版本不一致导致构建失败
Cloudflare 默认用自己固定的 Node 大版本。本地 node -v 是 20,构建机上可能是 22,某些原生依赖会挂。
解法:加 .nvmrc
20坑 2:包管理器必须一致
用 pnpm 就要让 Cloudflare 也用 pnpm。有两种做法:
# 仓库根目录加一个 corepack 声明坑 3:自定义响应头(CSP)用错文件名
Cloudflare Pages 的自定义 header 文件名必须是 _headers,放在 public/ 下,会被原样拷到 dist/ 根目录:
public/_headers → dist/_headers → 边缘节点生效/* X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-originTIPCSP 一开始我是开着的,结果被内联的 analytics beacon 和几个第三方资源全部挡掉。先上
Content-Security-Policy-Report-Only观察一段时间,确认没有违规再切成强制模式。
坑 4:构建失败时线上是完好的
这不是坑,是优点,值得单独强调:Cloudflare Pages 的部署是原子的。构建失败就停在原版本,线上不受影响。我有一次 commit 写崩了 markdown 配置,GitHub 上 CI 红了,站点还是好的 —— 改完再推就恢复了。
六、为什么源码一定放 GitHub
这一点我一开始想错过,后来发现是整个方案的地基。
Cloudflare Pages ┐ ├──→ 内容以 GitHub 仓库为唯一来源(single source of truth)Sveltia CMS ┘- Pages 只是构建产物。删了重建一条命令,不丢任何东西。
- CMS 也只是编辑器,它提交的变更最终是往仓库里 push Markdown 文件。所以 CMS 挂了不影响发布。
- 有完整历史。哪天想回退到三个月前的某个版本,
git revert一下,Cloudflare 自动重新构建。 - 迁移成本低。以后想换别的静态托管(Vercel / Netlify / 自建),换的是部署配置,内容文件一个字都不用改。
NOTE我的仓库是 Private。内容里有私人记录,而且 Cloudflare 的 GitHub 集成支持私有仓库 —— 构建机上拿的是只读的短期凭据,源码不会因为部署而泄露。
小结
- 架构就一句话:GitHub 是唯一真源,Cloudflare Pages 负责构建和 CDN,中间一个 webhook 打通。
- 成本 ¥0:不绑卡、不用服务器、不备案(域名可选,先白嫖
*.pages.dev也行)。 - 不限流量是 Pages 免费版最大的卖点,500 次构建/月对个人博客远远够用。
- 不备案的原因:备案要求”境内服务器”,Pages 在境外 anycast,两者不同时成立。
- 顺手白嫖:KV + Functions 做阅读量、Web Analytics 做统计、Workers 做 OAuth 中转、Turnstile 防机器人。
- 避开要绑卡的:R2 / Images / Stream / Workers Paid / China Network。
- 小心 Node 版本、包管理器、
_headers文件名这三个构建坑。
现在这个博客从推代码到全球可访问,全程不超过两分钟,成本为零。剩下的时间全用来写东西 —— 这才是博客该有的样子。