域名搬完之后,我把静态博客放上了 Pages
作者 yilihjy发布于 阅读约 5 分钟
上一篇写的是域名迁移:从 Namecheap 转到 Cloudflare,$18.68 一年。域名搬完之后,下一步是站点托管。
这个博客是 Astro 生成的静态站:没有登录,没有数据库,没有按请求执行的代码;构建出来是 HTML、CSS、JS 和图片。
Cloudflare 的 Pages 和 Workers 都能托管站点。最后我用了 Pages。
为什么是 Pages,不是 Worker
Pages 和 Workers 的区别在请求进来之后有没有代码要跑。Pages 面向静态站;Worker 面向按请求执行的代码。这个站没有那段代码。
创建页的第一个入口是 Worker。这个站是静态站,Worker 用不上。
Worker 的 Static Assets 也能托管构建产物。代价是多一套资源配置,以及重定向、响应头、预览、回滚和自定义域的处理;这些在 Pages 里是默认行为。
| Pages | Worker | |
|---|---|---|
| 适合 | 静态站、前端 | 按请求执行的代码 |
| 部署 | GitHub Actions | GitHub Actions |
| 预览 | 每次部署一个预览 URL | 版本预览 |
| 回滚 | 控制台选上一个部署 | 版本回滚 |
| 重定向、响应头 | Pages 的约定 | 自己实现 |
| 免费额度 | 静态请求、带宽不限 | 100k 请求/天 |
| 复杂度 | 低 | 高 |
以后要加评论、登录、动态 API,或者给 CMS 做认证入口,再单独加 Worker。博客的静态页仍然放在 Pages。
第一次部署
仓库是一个 pnpm workspace,博客在 apps/blog,构建产物在 apps/blog/dist。部署放在 GitHub Actions 里:主分支有 push 时,先装依赖,再跑一遍博客的校验,最后把 dist 传到 Pages。
校验不是只跑构建。产物断言、体积预算、内容校验都在里面;部署前再跑一遍,Pages 只接收已经通过校验的 dist。这样部署失败时,问题在构建或断言,不在「文件传了一半」。
Actions 需要两个凭据:只带 Pages 权限的 API token 和 account id。它们放在仓库的 secrets 里,不写进代码。
Cloudflare 里是一个 Pages 项目,生产分支是主分支。项目第一次通过 Direct Upload 建:在控制台上传一次 dist,项目就存在了;之后每次部署由 Actions 覆盖。控制台那次只用于创建项目。
部署失败或者需要回退时,Pages 控制台里选上一个成功部署即可。回滚不依赖 CI,也不依赖重新构建;每次部署都是一个完整的产物。
没有用 Cloudflare 的 Git 集成:它会消耗账号的 500 次/月构建额度,而构建在 Actions 里已经做过,CF 再构建一遍是重复;Actions 的校验门禁也不能跳过。
Pages 给每个项目一个 *.pages.dev 形式的默认域名,指向生产部署。每次部署还会生成一个 <hash>.*.pages.dev 形式的预览 URL:* 是项目名,<hash> 是这次部署的标识。预览 URL 和自定义域是两回事;前者用来在合并前看这次构建,后者是正式地址。
当前架构
当前的架构是这样的:访客经 Cloudflare 边缘到 Pages;自定义域直接到 Pages;默认域名先经过 Access;Actions 负责构建和上传。
flowchart TD
accTitle: 当前网站架构
accDescr: 访客通过 Cloudflare 边缘访问站点;自定义域直接到 Pages;默认域名先经过 Access;GitHub Actions 构建并上传静态产物。
visitor["访客"] -->|HTTPS| edge["Cloudflare 边缘\nHSTS / nosniff"]
edge -->|自定义域 apex / www| pages["Cloudflare Pages\n静态产物"]
edge -->|默认域名 *.pages.dev| access["Cloudflare Access\n登录"]
access -->|通过| pages
actions["GitHub Actions\nmain push → 构建 + 校验"] -->|上传 dist| pages
pages -->|返回页面| edge
edge -->|www 301 到 apex| visitor
免费额度
这套东西的边际成本接近 0。免费额度分几项。
| 项目 | 免费额度 | 我的用法 |
|---|---|---|
| Pages 静态请求、带宽 | 不限 | 全部 |
| Pages Git 构建 | 500 次/月 | 不用(Direct Upload) |
| GitHub Actions | 2000 分钟/月 | 远低于上限 |
| Cloudflare Access | 50 seats | 1 |
| DNS、SSL、HSTS | 免费 | 全部 |
| Worker | 100k 请求/天 | 不用 |
| 域名 | 年费 | 一年一次 |
Pages 的静态请求和带宽不计费,这是选它而不是 Worker 的另一个理由。Worker 的免费额度按请求算,静态站放在 Worker 上,访问量会直接消耗 100k/天的额度;Pages 没有这个问题。
Actions 的 2000 分钟是私有仓的额度。这个仓库只在主分支和 PR 上跑检查和部署,开发分支不跑;实际用量远低于上限。
Access 的 50 seats 是用户数,不是访问量。给默认域名加一层登录,自己用 1 个 seat;被拦住的访客不占。
唯一固定的成本是域名年费,上一篇算过;其余都在免费额度里。
apex 和 HSTS
apex 是裸域,example.com,不带 www;自定义域是绑到 Pages 项目的域名,不是默认的 *.pages.dev。apex 自定义域就是把裸域绑到 Pages。
apex 有一条指向 Pages 的 DNS 记录;Cloudflare 做 CNAME flattening;Pages 里 apex 是 Custom domain。www 有一条指向 apex 的 DNS 记录,再加 301 到 apex;路径和查询串都保留。
SSL 用 Full (strict),Always Use HTTPS 开着。
HSTS 是 HTTP Strict Transport Security 的响应头。浏览器收到之后,在 max-age 内强制用 HTTPS,不再先走 HTTP;它防的是链路劫持和 SSL stripping。第一次访问仍然要靠 301,只有 preload 才会写进浏览器内置名单。includeSubDomains 把规则扩展到子域;preload 更硬,也更难回退。
HSTS 用 30 天,没有开 includeSubDomains,也没有开 preload。先短一点,确认没有子域还在 HTTP 上,再考虑加长。preload 的代价是长期的:域名进了浏览器名单,移除要等版本更新;个人博客没有必要。
Access 与安全
Pages 的默认域名是项目入口,也是预览入口。我给默认域名加了 Access,要求登录;正式的自定义域保持公开。
只保护默认域名,是因为它和自定义域在 Zero Trust 里分开管理。默认域名是部署入口,自定义域是读者访问的地址。默认域名关起来,预览和未合并的内容不会漏出去;自定义域仍然公开。
Access 的免费额度是 50 个用户。这里只用 1 个:自己的邮箱。被拦住的访客不占席位。
HSTS 的取舍是短 max-age 优先;可回退性比一次拉满更重要。
X-Content-Type-Options: nosniff 也开了。它让浏览器只认响应里的 Content-Type,不再猜文件类型;对静态站没有副作用。
证书是 TLS 1.3,apex 和 www 都有效。
验证
部署完,我先看 DNS:apex 和 www 都解析到 Cloudflare,NS 也对。然后看 HTTP:apex 是 200,www 301 到 apex,默认域名 302 到 Access 登录,http:// 301 到 HTTPS,不存在的路径是 404。最后看安全头,strict-transport-security 和 x-content-type-options 都在。
默认域名只作为部署和预览入口,不出现在站点的公开链接里。
边界
域名管地址,Pages 管静态产物,构建和校验交给 Actions,默认域名的访问控制交给 Access。
静态站用这套组合即可。以后要加动态能力,再加 Worker;静态站不必为「以后可能会有的需求」先背上一套运行时。
成本上,除了域名年费,其余都落在免费额度里。真正花时间的是构建、校验和维护,不是账单。
下一步,如果这个博客真的需要评论、登录或者动态 API,再考虑 Worker。现在没有。
参考
- Cloudflare Pages 的限制
- Direct Upload
- Pages 自定义域
- Workers 定价
- GitHub Actions 计费
- Cloudflare Access
- HSTS
- CNAME flattening
注:文中数字按 2026 年 10 月的情况算。