静态站点生成器再好用,最后一步”上线”依然是事故高发区。构建命令填错、Node 版本不匹配、输出目录写反、自定义域一直转圈、改了内容线上不变——这些问题几乎每个第一次部署的人都会遇到一遍。
Cloudflare Pages 是目前部署静态站点性价比很高的选择:全球边缘节点、自动 HTTPS、每次提交生成一个预览链接、回滚只是一次点击。这篇文章按真实的部署顺序,把配置要点和排错路径梳理清楚。
先选对接入方式
Pages 有两种接入方式,选错了后面会很别扭。核心区别是谁来执行构建:
| 接入方式 | 构建在哪里跑 | 优点 | 适合场景 |
|---|---|---|---|
| Git 集成 | Cloudflare 云端 | 零配置,推送即部署,自动生成预览链接 | 绝大多数站点,尤其是纯静态站 |
| 直接上传(Wrangler) | 你自己的机器或 CI | 构建环境完全可控,可用任意私有依赖 | 构建脚本复杂、依赖私有包、需要在 CI 内统一发布 |
默认选 Git 集成。 它能白送两个很值钱的能力:每个分支和 PR 一个独立预览链接,以及生产环境一键回滚。只有当你的构建流程确实跑不进云端环境(例如需要内网依赖、构建耗时要几十分钟),才考虑用 Wrangler 本地推送产物。
Wrangler 上传的命令也很简单:
# 安装并登录(浏览器完成授权)
npm i -D wrangler@latest
npx wrangler login
# 本地先构建,再上传产物目录
npm run build
npx wrangler pages deploy dist --project-name=my-site
# 只想先看看效果,部署到预览环境
npx wrangler pages deploy dist --project-name=my-site --branch=preview
把构建配置锁死
构建配置里只有三个字段是必填的,但每个字段填错都会导致失败:
| 框架 | 构建命令 | 输出目录 | 注意点 |
|---|---|---|---|
| Astro | npm run build | dist | 纯静态无需适配器;用了 SSR 适配器则不适用 Pages 静态模式 |
| Vite | npm run build | dist | 要注意 base 配置,部署在子路径时必须同步 |
| Next.js | npm run build && npm run export | out | 需在配置中开启静态导出 |
| Hugo | hugo --minify | public | 云端镜像需指定 Hugo 版本,扩展版需手动声明 |
除了这三个字段,还有两件事值得提前做:
第一,锁定 Node 版本。 云端默认的 Node 版本不一定和本地一致,版本漂移会带来”本地能跑线上报错”的经典问题。用环境变量显式指定:
NODE_VERSION = 22.11.0
如果不想改平台配置,也可以在 package.json 里加 engines 字段作为约束,双保险。
第二,依赖安装命令的选择。 有 package-lock.json 时用 npm ci,它会严格按锁文件安装且速度更快;没有锁文件才用 npm install。没有锁文件的项目在 CI 里安装是不确定性的主要来源,建议本地生成后提交到仓库。
{
"scripts": {
"build": "astro build"
},
"engines": {
"node": ">=22"
}
}
环境变量与密钥的正确用法
构建期需要的变量(如 API Key、站点 URL)在 Pages 的 Settings → Environment variables 里配置,并且可以分别为生产环境和预览环境设置不同的值。
关键约定有两条:
- 框架对变量名前缀有要求。 例如 Astro 的公开变量需要
PUBLIC_前缀才能被打包进客户端代码,Vite 需要VITE_前缀。直接写API_URL在客户端读不到,这是最常见的”本地有值线上 undefined”。 - 敏感值只能用于构建期。 静态站点没有服务端,任何写进产物的值都会被用户看到。需要私密调用的接口,应该用 Pages Functions 或边缘函数做一层代理,而不是把密钥打进前端 bundle。
# 本地用 .env 模拟,注意 .env 不要提交
PUBLIC_SITE_URL=https://example.com
API_TOKEN=xxxx # 仅构建期使用,不要暴露给客户端
自定义域与 DNS 解析
在 Custom domains 里添加域名后,推荐做法是把域名的 NS 记录托管到 Cloudflare,这样证书会自动签发和续期,通常几分钟内生效。
如果 DNS 保留在原服务商,也可以手动添加 CNAME 记录指向你的 *.pages.dev 域名,再在面板中选择高级证书手动验证。代价是证书续期需要自己盯,适合确实不方便换 NS 的情况。
几个实践中容易踩的点:
- 根域(example.com)用 CNAME 存在争议,托管到 Cloudflare 后会自动使用扁平化处理,更省心。
- 先加
www再决定跳转方向,把其中一个设为规范版本,另一个做 301 跳转,避免两个域名同时被 indexing 造成权重分散。 - 验证失败先看 DNS 传播,用
dig确认记录已生效再点重试,反复点击重试不会加速。
# 确认解析是否已生效
dig example.com +short
dig www.example.com CNAME +short
# 确认证书与响应头
curl -I https://example.com
缓存机制:为什么改了看不到
这是 Pages 上最高频的困惑。要理解一件事:每次部署的产物文件名带哈希,静态资源基本不用担心缓存问题;真正容易出问题的是 HTML 文档本身。
处理顺序建议如下:
- 修改后先在 Deployments 列表确认这次构建状态是成功,而不是看首页有没有变。
- 确认最新版本上标记的是”生产”,Pages 会保留部署历史,可以点选任意一条回滚。
- 如果确认已发布仍看到旧内容,再考虑缓存。用
curl -I看响应头里的cf-cache-status,HIT表示命中边缘缓存。 - 需要立即使全站缓存失效时,用 API 触发一次清除比在面板里等更可控。
curl -sS -X POST \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
全站清除是”核弹选项”,会让整站回源、短期增加源站压力。日常更推荐用带哈希的文件名 + 合理的 HTML 缓存头,让新版本自动生效,而不是依赖手动清除。
一份可对照的排错清单
| 现象 | 最可能原因 | 处理方式 |
|---|---|---|
| 构建报「找不到输出目录」 | 输出目录填错 | 本地构建一次,确认产物实际目录名 |
| 本地能构建,线上报依赖错误 | 缺锁文件或 Node 版本不一致 | 提交 lock 文件,显式设置 NODE_VERSION |
| 页面能开,样式全丢 | 部署在子路径但 base/site 未同步 | 在框架配置中填写完整站点 URL |
| 刷新子页面 404 | 未配置 SPA 回退或缺少 404 页面 | 加 _redirects 或提供 404.html |
| 自定义域一直”验证中” | DNS 未传播或记录类型错误 | dig 确认后重试,必要时等待传播 |
| 环境变量读不到 | 缺少框架要求的前缀 | Astro 用 PUBLIC_、Vite 用 VITE_ |
其中”刷新子页面 404”值得单独说明:静态托管默认按文件路径查找,访问 /posts/hello 会去找同名文件。单页应用需要一条回退规则:
# public/_redirects
/* /index.html 200
上线前的检查清单
- 本地执行过一次干净构建,产物目录确认无误
- 依赖锁文件已提交,Node 版本已显式指定
- 环境变量按框架前缀命名,敏感值未进入客户端产物
- 自定义域已生效,且
http://与www均 301 到规范版本 -
robots.txt与sitemap.xml可访问 - 404 页面存在,且访问不存在的路径会返回 404 状态码而非 200
- 确认过一次回滚操作,知道出问题时点哪里
最后一条最容易被跳过。部署系统的价值不在于”能发布”,而在于”发错了能三秒内退回去”。 上线前花一分钟点开部署历史,确认回滚按钮在哪,比事后翻文档有用得多。
常见问题
免费额度会不会突然不够用?
个人内容站通常远低于免费额度上限。但具体配额(每月构建次数、带宽、并发构建数、可绑定的自定义域数量)会随官方策略调整,请以 Cloudflare 官网定价页为准。如果站点开始有稳定流量,建议提前确认一下当前的限额说明,避免临时被限流。
部署成功但访问是 404,最可能的原因是什么?
绝大多数情况是构建输出目录填错了。Astro 与 Vite 默认是 dist,Next.js 静态导出是 out,Hugo 是 public。平台报「找不到输出目录」时,先在本地跑一次 npm run build,用 ls 看产物实际落在哪个文件夹,再回填配置重新部署即可。
自定义域接入后一定要把 DNS 托管到 Cloudflare 吗?
不一定。托管 NS 到 Cloudflare 时证书会自动签发与续期,配置最省事,是默认推荐路径。DNS 留在原服务商也可以,需手动添加 CNAME 并用高级证书手动验证,代价是证书续期要自己关注。两种方式在访问速度上没有本质差异。
改了内容但线上没更新,应该怎么排查?
按顺序查四件事:构建日志是否真的成功、是否部署到了预期环境、最新那条部署是否标记为生产、浏览器与 CDN 缓存是否已失效。多数”没更新”其实是新部署还停在预览环境没有提升为生产版本;如果确实已发布,再用 curl -I 看 cf-cache-status 是否为 HIT,决定是否需要清除缓存。