我们踩过一次很安静的坑,安静到几乎没有任何一个常规监控能报警。
那天早班照例巡检,第一项 curl -I https://站点/ 返回 200 OK。sitemap 也 200。如果巡检到这里就收工,结论会是"站点一切正常"——但实际上,那一刻全站所有博客文章和商品页都是 404,询盘接口也是坏的。访客搜进来的每一个内容页都打不开,而首页看起来完好无损。
这篇文章记录完整的发现过程、判据和修复动作,全部数字来自我们自己的生产站在 2026-10-07 早班 的实测输出。
按固定巡检脚本跑 8 个 URL,结果是这样的:
| URL | 返回 | 判读 |
|---|---|---|
/ | 200 | 首页正常 → 会让人误以为站点没事 |
/sitemap.xml | 200 | 也 200,但内容陈旧(关键线索在下面) |
/products/ | 404 | 商品聚合页没了 |
/products/suzhou-dialect-lexicon | 404 | 主商品页没了 |
/blog | 200(空响应体) | 最迷惑的一项:状态码对、内容是空的 |
/blog/<最新一篇> | 404 | 抽查任意近期文章均 404 |
/api/lead(GET) | 404 | Functions 端点消失 |
/wechat-pay-qrcode.jpg | 200 | 老静态资源还在 → 更强化"没事"的错觉 |
判断"线上是哪一版",最省事的办法不是去翻控制台的部署历史,而是数 sitemap 条数——它是我们每次上线都会同步增长的计数器官。
# 线上 curl -s https://站点/sitemap.xml | grep -c "<loc>" → 18 # 本地源码目录 grep -c "<loc>" sitemap.xml → 35
18 对 35,一句话就定性了。再补一刀,看线上 sitemap 里最新的 <lastmod>:
curl -s https://站点/sitemap.xml | grep -o "<lastmod>[^<]*" | sort -u | tail -3 → <lastmod>2026-06-19 → <lastmod>2026-06-24
线上最新内容是 2026-06-24,而此刻是 2026-10-07——线上落后了整整三个多月。这期间我们上线的所有文章、商品页、询盘 Functions,一个都没生效。
因为它不依赖外部服务,也不需要凭证。任何静态站只要在 sitemap 里维护条数,就有了一个自带的"版本号"。同理,"最新一篇博客 URL 是否 200" 也是一个极佳的探针——它天然覆盖"部署是否生效"这件事。
部署成功 ≠ 线上生效。要证明"线上生效",必须拿一个只有新版本才会有的东西去线上验证,而不是看部署命令有没有报错。
询盘接口的表现很有信息量:GET 返回 404,POST 返回 405。这两个码一起出现,几乎可以确定成因:
核心结论是:Functions 没有被打包进这次部署。Cloudflare Pages 的 Functions 依赖部署目录下的 functions/ 目录被 wrangler 识别并编译;一旦 wrangler 找错了项目根,函数就不会被编译,/api/* 就退化成普通静态路径。
如果部署目录的父目录里也有一个 package.json,wrangler 会把父目录当成项目根,于是它去父目录找 functions/——找不到,就不编译。表现就是:部署"成功",但 Functions 全灭。
# ✅ 正确:进入部署目录内部再 deploy . cd /path/to/cf-deploy-cn npx wrangler pages deploy . --project-name <项目名> --branch main --commit-dirty true # ❌ 错误:在上级目录执行(父级 package.json 会带偏 wrangler) cd /path/to/parent npx wrangler pages deploy ./cf-deploy-cn --project-name <项目名>
Compiled Worker successfully 与 Uploading Functions bundle。如果这两行没出现,哪怕显示 "Deployment complete",也是一次没有后端的部署。我们还有一个意外好用的物证。询盘 Functions 在把数据转发到 webhook 桶时,Cloudflare 会在请求头里带上 cf-worker 标识:
"headers":{"cf-worker":["<项目名>.pages.dev"], "x-lead-source":["..."]}
翻桶里的历史记录,最后一次带 cf-worker 头的投递停留在 2026-10-05 上午。也就是说,我们可以精确知道 Functions 是在那个时间点之后消失的,而不必靠猜测。凡是链路中间有第三方旁路(webhook、日志、埋点)的系统,都能用这种方式做"历史取证"。
修复动作本身只有一步——回到正确的目录、用 wrangler 重新部署:
cd C:\Users\...\ai-service-portal python cf_deploy_cn.py
脚本内部做的正是上一节的"正确姿势"(PowerShell 调 wrangler、Set-Location 进部署目录、清理 wrangler 缓存)。这次的输出:
Compiled Worker successfully Uploading... (86/86) Success! Uploaded 2 files (84 already uploaded) (1.62 sec) Uploading _headers Uploading _redirects Uploading Functions bundle ← ★ 关键行,出现即 Functions 上线 Deployment complete! ... https://5ad4f299.<项目名>.pages.dev
注意 Uploaded 2 files (84 already uploaded):这不代表"只上传了 2 个文件所以没更新"。Cloudflare Pages 用内容寻址去重,内容没变的文件不会重复上传。判断部署是否生效只能靠线上实测。
/products/suzhou-dialect-lexicon 与最新一篇 /blog/<slug> → 实测全部 200(此前均为 404);<loc> 从 18 → 35,与本地一致;/api/lead 返回 {"ok":true},且投递腿为 sink:"sent | sent"(双桶均送达);sent 只说明"我们发出去了",不说明"对面收到了"。当投递目标是第三方(webhook / 邮件 / 推送)时,必须到收方确认,否则你会以为链路是通的。事后复盘,真正的问题不是"部署会失败",而是巡检项的粒度太粗——只测首页,等于没测。现在我们的固定巡检至少覆盖四类:
| 类别 | 探针 | 通过判据 |
|---|---|---|
| 版本一致性 | 线上 sitemap <loc> 条数 | 等于本地源码目录的条数 |
| 新增内容可达 | 最新一篇博客 / 最新商品页 | 200(这两个一定是新版本才有) |
| 后端存活 | POST /api/lead | ok:true 且 sink:"sent" |
| 下游实收 | 第三方桶 / 邮箱查记录 | 能查到刚才那条探针 |
Uploading Functions bundle / Compiled Worker successfully,不要看"Success"这种泛化措辞,也不要被 Uploaded N files 的 N 误导。这套体检法本身与平台无关——静态托管、CDN、容器部署都一样:能 200 的东西不代表对的东西,只有拿新版本独有的内容去追问线上,才能问出真相。如果你也在维护这类"看起来一直好好的"站点,建议今天就去数一次 sitemap 条数。顺便,我们站内上架了可商用的方言语料资产与数字人素材,做内容站、语音类应用时可以直接取用。
看看站内已上架的数字资产 →不是。首页是部署包里的一个静态文件,只要生产环境有任意一版部署,首页就会 200。真正要判断的是"线上跑的是哪一版"——用内容指纹,最简单的就是数 sitemap.xml 里的 <loc> 条数并与本地源码目录对比。我们实测线上 18 条、本地 35 条,一眼就能确认线上是两个月前的旧版本。
405 通常意味着 Functions 没有被编译进这次部署,请求被当成静态路径处理了。最常见根因是在错误的目录执行 wrangler:如果父目录里也有 package.json,wrangler 会把父目录当成项目根,于是找不到 functions/ 目录。部署输出里不会出现 Uploading Functions bundle,也不会有 Compiled Worker successfully。修复方式是在部署目录内部执行部署。
不是。Cloudflare Pages 用内容寻址去重,文件内容未变就不会重复上传,所以出现 Uploaded 2 files (84 already uploaded) 是正常的。判断部署是否生效不能看上传文件数,只能看线上实测:新 slug 是否 200、sitemap 条数是否 +1、Functions 是否收到请求。
三个高性价比的内容指纹:①sitemap.xml 的 <loc> 条数(本地 35 / 线上 18);②最新一篇博客 URL 是否 200;③最新 lastmod 日期是否等于你最后一次部署的日期(线上最新是 2026-06-24)。这三个都不依赖任何监控平台,一条 curl 就能拿到。
会静默丢失。Functions 失效后,表单提交要么返回 404/405,要么被当成静态文件处理而没有任何后端接收。这段时间的询盘没有任何地方留存。所以巡检必须把 POST 探针当作固定项,返回 sink:sent 才算通。