草稿页误发布:6 种泄漏来源 + CI guard 防再发

Build 把还 `draft: true` 的页面也部上线了,Google 收录了占位符内容——审计 + 去索引 + CI 加 guard 让 build **不可能**把 draft 发到生产。

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 用不同 DRAFTS env var、永不互换
  • 加个 pnpm audit:drafts-published 脚本,随时能跑
  • 出事后写时间线——反复泄漏揭示系统性 gap 要关

相关阅读

标签: #内容运营 #站点质量 #站点审计 #排查 #草稿误发布