3 分钟#cloudflare #tooling
把静态站丢上 Cloudflare
一个只有 assets、没有 main 的 Worker:没有请求计费,没有冷启动,没有运行时。顺便记几个每年都会忘的坑。
静态站部署这件事,我折腾过 Vercel、Netlify、GitHub Pages,最后落在 Cloudflare。
不是因为它最快——虽然它确实很快——而是因为它的部署模型足够无聊:一个只有 assets、没有 main 的 Worker。
没有脚本在跑,就没有冷启动,没有请求计费,没有"函数执行超时"。你的 dist/ 被推到边缘节点上,然后 CDN 直接吐文件。
配置短得离谱
{
"name": "dream-blog",
"compatibility_date": "2026-04-01",
"assets": {
"directory": "./dist",
"html_handling": "auto-trailing-slash",
"not_found_handling": "404-page"
}
}
三个字段值得解释:
html_handling: auto-trailing-slash:让/posts/hello-world和/posts/hello-world/指向同一个文件,不产生跳转链。not_found_handling: 404-page:用你自己的dist/404.html,而不是 Cloudflare 默认那页。- 没有
main字段:这就是它"只是个静态站"的证据。
一键部署
npm run deploy
脚本做六件事:
1/6 环境检查 node 版本、wrangler 是否可用
2/6 构建 node build.mjs
3/6 产物校验 死链、h1、占位符、RSS、配置一致性
4/6 登录状态 wrangler whoami,没登录就停下
5/6 部署 dry-run 预检 -> wrangler deploy
6/6 线上自检 curl 五条关键路径
第 3 步是关键。永远不要部署没校验过的产物——线上排错的成本是本地的一百倍。
三个每年都会忘的坑
1. workers.dev 子域要先注册
第一次部署大概率会报:
You need to register a workers.dev subdomain before publishing
这不是权限问题,是账号还没在 Workers & Pages 里走完 onboarding。去控制台点一次就行,全局唯一、全账号共用。
2. _headers 不生效往往是因为缩进
Cloudflare 的 _headers 文件对格式很敏感——每条规则下面的响应头必须缩进两个空格,规则之间空一行:
/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
/images/*
Cache-Control: public, max-age=31536000, immutable
不缩进的话整段会被静默忽略,你会在三个月后才发现图片没有缓存。
3. 没有浏览器的机器用 Token
wrangler login 需要开浏览器。CI 里只能:
export CLOUDFLARE_API_TOKEN=你的token
Token 权限要包含 Workers Scripts:Edit、Account Settings:Read、User Details:Read、Zone:Read。少了哪一项,报错信息都不会直说是权限问题。
顺手加的几个头
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: SAMEORIGIN
Permissions-Policy: geolocation=(), microphone=(), camera=()
成本为零,但能挡掉一批低级的扫描告警。
为什么不直接 GitHub Pages
GitHub Pages 也很好,尤其是配合 Actions 的自动构建。区别在于:
- Cloudflare 的免费额度对静态资源几乎无感,Pages 有 100GB/月的软限制
- Cloudflare 的
_headers能精细控制缓存与安全头,Pages 只能靠_headers支持的部分 - 我的域名解析本来就在 Cloudflare
如果你的解析不在 Cloudflare,多出来的那一步 DNS 迁移可能就不值了。部署方案要跟着域名走,不是反过来。