AdSense 控制台横幅写着 “Earnings at risk — your ads.txt file is missing”。你加了文件。警告没消。或者 https://yourdomain.com/ads.txt 返回 404,但你确定推送了。ads.txt 规范很简单——根目录一个纯文本文件,列授权 seller——但部署上有 4 个坑能套住大多数独立站。
下面讲 ads.txt 究竟该放哪、怎么验证、以及 AdSense 那个让你修对之后横幅还要挂几天的对账延迟。
常见原因
按命中率从高到低。
1. 文件路径错
ads.txt 必须在 https://yourdomain.com/ads.txt——不是 /public/ads.txt、不是 /assets/ads.txt、不是 /static/ads.txt。爬虫只命中这一个 URL。
怎么判断:
curl -sI "https://yourdomain.com/ads.txt"
要看到 HTTP/2 200。其它(404、301、403)都说明路径不对。
2. 框架没把文件 ship 出去
Astro:ads.txt 放 public/。Next.js:也是 public/。Vercel:Next 项目放 public/,静态站放根。SvelteKit:static/。放进 src/ 不会被部署。
怎么判断:部署后 curl https://yourdomain.com/ads.txt。404 就是 build 没带它。
3. Content-Type 错或内容被 gzip 成 HTML
某些平台(nginx 配错、CDN 规则不当)会把 .txt 文件返回 Content-Type: text/html,或者把它包在一个 200 状态的 404 HTML 页里。AdSense 看到 HTML,找不到 seller 行,就当文件缺失。
怎么判断:
curl -sI "https://yourdomain.com/ads.txt" | grep -i content-type
应该是 Content-Type: text/plain(或 text/plain; charset=utf-8)。如果是 text/html,平台在做改写。
4. publisher ID 错或格式问题
行必须是:
google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0
常见错误:尾部空格、复制粘贴带智能引号、文件开头有 BOM、publisher 号错、漏了逗号。
怎么判断:
curl -s "https://yourdomain.com/ads.txt" | od -c | head -3
看开头有没有奇怪的字符(BOM 是 357 273 277)。
5. Cloudflare / CDN 缓存返回旧版
你改了但缓存还在返回旧版。AdSense 爬到的还是旧的(缺失)状态。
怎么判断:加 ?nocache=$(date +%s) 绕缓存:curl "https://yourdomain.com/ads.txt?nocache=$(date +%s)"。内容不一样就是 CDN 缓存陈旧。
6. AdSense 还没重新爬
修对后横幅会挂 24-48 小时——AdSense 按自己的节奏重查。
怎么判断:curl 验证 ads.txt 正确、修完不到 48 小时,就是等。
最短修复路径
第 1 步:从 AdSense 拿到精确文本
AdSense → Sites → 你的站 → “Get snippet”。复制那一行——别手敲。
应该像:
google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0
第 2 步:放到正确路径
| 框架 | 文件放哪 | URL |
|---|---|---|
| Astro | public/ads.txt | /ads.txt |
| Next.js (pages 或 app) | public/ads.txt | /ads.txt |
| SvelteKit | static/ads.txt | /ads.txt |
| Nuxt | static/ads.txt | /ads.txt |
| Hugo | static/ads.txt | /ads.txt |
| 纯 HTML | webroot 的 ads.txt | /ads.txt |
部署,验证。
第 3 步:用 curl 验证
curl -sI "https://yourdomain.com/ads.txt"
# 期望:HTTP/2 200, Content-Type: text/plain
curl -s "https://yourdomain.com/ads.txt"
# 期望:google.com, pub-XXXXX, DIRECT, f08c47fec0942fa0
Content-Type 是 text/html 或其它,说明平台 / CDN 在改写。加显式规则:
Vercel — vercel.json:
{
"headers": [
{ "source": "/ads.txt", "headers": [{ "key": "Content-Type", "value": "text/plain" }] }
]
}
Netlify — _headers:
/ads.txt
Content-Type: text/plain
Cloudflare Pages — 根目录的 _headers:
/ads.txt
Content-Type: text/plain
第 4 步:清 CDN 缓存
Cloudflare → Caching → Purge → Custom Purge → 输入 https://yourdomain.com/ads.txt。
Vercel 通常部署时自动清。
第 5 步:等 24-48 小时 AdSense 重新核对
横幅不会瞬间消。只要 curl https://yourdomain.com/ads.txt 返回正确的纯文本,AdSense 48 小时内会识别。
第 6 步:CI 加校验
# .github/workflows/verify-ads-txt.yml 或 postdeploy 脚本
curl -sf "https://yourdomain.com/ads.txt" | grep -q "pub-$ADSENSE_PUB_ID" || \
{ echo "ads.txt missing or wrong"; exit 1; }
每次部署后跑一次。能瞬间发现回归。
哪些情况可能不是你操作错了
AdSense 按周期重查 ads.txt,不按需。修对后控制台横幅 24-48 小时才消失。别反复重发——等就行。
容易误判的情况
把 ads.txt 放错目录(或框架 404 掉)——静态站最常见的错误。用 curl 验证,别只看代码库。
预防建议
ads.txt永远放在静态 / public 目录,每次部署自动带上。- CI 里加校验:
curl -sf yourdomain.com/ads.txt | grep -q pub-$PUB_ID。 - 平台配置里强制
Content-Type: text/plain。 - 改 CDN 设置后再验证一次
ads.txt仍然正确返回。 - 接第二家广告网络(Mediavine、Ezoic 等),
ads.txt同步加它们的行。
FAQ
- 只用 AdSense 也要 ads.txt 吗? 要——Google 对 AdSense 完整收益有要求。
- ads.txt 能多行吗? 能,每个授权 seller 一行。多家广告网络各自一行。