Search Console 这周报已索引 URL 涨 15%。你一查——是标题为「Untitled」「[TODO 写 intro]」「Draft notes - DON’T PUBLISH」的文章。你的草稿被 build 进了生产,现在在 Google 索引里——啥都不排、给 Google「低质内容」信号、给找到它们的人显示占位符文字。
多数「draft 误发」事件是配置 gap、不是人为错误:你的 build 不认 draft: true、bulk-edit 脚本翻了 flag、staging 被升级到 prod。修法:审计 + 去索引泄漏的,然后 CI 加 guard 让 build不可能把 draft 发到生产。
常见原因
按命中率从高到低:
1. Build 管道不认 draft: true
你的 content loader 不管 draft flag 都包含——flag 是装饰、没东西读它。
如何判断:在 codebase 搜 draft 怎么处理:
grep -rn "draft" src/content/config.ts src/pages/ astro.config.mjs
build 路径里没引用——flag 没用。
2. bulk-edit 脚本翻了 draft flag
你跑了 normalize frontmatter 的脚本(「给所有文章加 publishedAt」)。脚本也默默把 draft: false 写进了每个文件——草稿一夜上线。
如何判断:git log --all -p -S "draft: false" 看你最近 bulk-edit 附近。脚本不分青红皂白设 draft: false 就是它。
3. staging 部署被升到生产、草稿搭便车
你 staging 用 DRAFTS=true、prod DRAFTS=false。有人把 staging build 部到 prod(或把 staging env 复制到 prod)——草稿搭车。
如何判断:看 prod 部署的 build log——DRAFTS=true 就是 staging build 不是 prod build。
4. 新文件默认 draft: false
你 / CMS 建新文章时模板默认 draft: false——新文章一旦部署就立即上线,即便你本想”先存以后写完”。
如何判断:看文章模板 / 生成器——新文件默认 draft: false 就是每篇新文章自动发布。
5. 多 agent / 合著翻 flag 不一致
一个作者 draft: true 写,另一个作者打开文件保存时没注意——保存把 flag 替换成编辑器默认。竞态条件。
如何判断:泄漏文章的 draft: 行做 git blame——翻 flag 的是没写这内容的人 = 误存。
6. Build cache 服务的还是上一个 draft: false 版本
你设 draft: true 推了——build cache 还在服务上一份 build。文章看着在线因为 cache 没失效。
如何判断:强制清 cache / 重 build。clean rebuild 后文章消失——是 cache 问题不是 build 本身。
最短修复路径
按紧迫度。Step 1 和 2 加起来 10 分钟——别等。
Step 1:找泄漏 URL + 去索引
# CMS / repo 列当前所有草稿
grep -rln "^draft: true" src/content/articles/
# Search Console:按占位符标题过滤
# (Performance → Pages → 搜 "untitled" / "todo" / "draft")
每个泄漏 URL:
- 想留 + 写完:写完时改 `draft: false`
- 想删:410 这个 URL(返 HTTP 410 Gone)
- 临时隐藏:noindex + Search Console removal 请求
永久删用 410——比 404 让 Google 丢得快。
Step 2:让 build 认 draft: true(如果没认)
Astro:
// src/content/config.ts
const articles = defineCollection({
type: "content",
schema: z.object({
draft: z.boolean().default(false),
// 其他字段
}),
});
页面 loader:
// src/pages/articles/[slug].astro
export async function getStaticPaths() {
const all = await getCollection("articles");
const published = all.filter(a => !a.data.draft || import.meta.env.DEV);
return published.map(a => ({ params: { slug: a.slug }, props: { article: a } }));
}
草稿现在 dev(pnpm dev)里在,pnpm build 输出里没有。
Step 3:CI 加 guard 拦 sitemap 里的 draft
# .github/workflows/no-drafts-in-prod.yml
on: pull_request
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm install && pnpm build
- name: Block drafts in production output
run: |
if grep -l "draft.*true\|DRAFT\|TODO" dist/sitemap-index.xml dist/**/*.html 2>/dev/null; then
echo "::error::生产产物里发现 draft 标记"
exit 1
fi
生产产物里有 draft 标记 PR 就 fail。
Step 4:新文章默认 draft: true
CMS / scaffold:
---
title: ""
draft: true ← 默认
publishedAt:
---
作者必须主动翻成 draft: false 才发——opt-in 不是 opt-out。
Step 5:修完后强制清 cache
CDN 缓存了泄漏 URL:
# Cloudflare 全清
curl -X POST "https://api.cloudflare.com/client/v4/zones/<zone>/purge_cache" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-d '{"purge_everything":true}'
# Vercel:无缓存重部
vercel deploy --prod --force
cache 没清时去索引对新请求不可见。
Step 6:误索引页申请 Search Console removal
Search Console → Removals → New Request
- 提交每个泄漏 draft 的 URL
- 选「Temporarily hide URL」(90 天)
- 90 天内要么写完页面要么 410
比等重爬快——不要省。泄漏 draft 伤站点质量、值得这几分钟。
预防建议
- Build 必须认
draft: true——CI 加 check 确保dist/或 sitemap 里没草稿 - 新文章模板默认
draft: true——opt-in 发布、不是 opt-out - 修 frontmatter 的 bulk-edit 脚本必须显式保留
draft: true - staging 和 prod 用不同
DRAFTSenv var、永不互换 - 加个
pnpm audit:drafts-published脚本,随时能跑 - 出事后写时间线——反复泄漏揭示系统性 gap 要关