你已经把 <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-..."> 和 <ins class="adsbygoogle"> 标签都贴好了。DevTools → Network 确认 adsbygoogle.js 返回 200。但刷新再刷新、等了几个小时,广告位还是空白。这是 AdSense 里最典型的”看着对、其实不对”的故障,基本都能归到 7 种接线或账号问题之一,跟代码本身关系不大。
最快路径: 读 AdSense 脚本回写到 <ins> 标签上的 data-ad-status 属性(下面第 1 步)。如果是新账号、所有广告位都显示 unfilled,那大概率是你还在审核中、或者处于 Google 24-48 小时的投放预热期——这不是 bug,改任何代码都没用。如果这个属性根本不存在,那是你的 push({}) 没执行。这两种读数能覆盖绝大多数情况。
常见原因
按命中率从高到低。
1. 账号还在审核,或处于 24-48 小时投放预热期
这是新发布者最常见的原因,也是大多数教程会跳过的一条。AdSense 申请还在审核期间,所有广告位都显示空白——账号完全通过前,Google 不会投放真实广告。即使通过后,添加/修改广告代码或接入新站点后,广告开始投放也可能要最多 48 小时(截至 2026 年 6 月);Auto ads 经常是移动端先出现,桌面端晚一两天。
怎么判断:看 AdSense 后台首页。出现 “Getting ready” / “We’re reviewing your site” 横幅,或者 Sites 里站点标着 “Requires review” / “Needs attention”,就说明还不会投真实广告。审核通常几天,也可能拖到 2-4 周。这种没有代码可改——等够,48 小时后再看。
2. (adsbygoogle = window.adsbygoogle || []).push({}) 没执行
脚本加载了,但激活 slot 的 push({}) 调用要么缺失,要么放在一个永远跑不到的地方(某个没触发的路由切换 hook、被 if 拦了、SSR 到 hydration 之间被剥掉了)。
怎么判断:DevTools 控制台执行 window.adsbygoogle.loaded。返回 undefined 或 false,push 就没跑。同时 <ins> 上完全没有 data-ad-status 属性(连 unfilled 都没有)。
3. slot 渲染时宽度为 0
<ins> 元素存在,但父容器是 display: none、当前断点宽度为 0、或者在没展开的 tab/accordion 里。AdSense 拒绝 0 宽度的响应式 slot,位置就一直空着。
怎么判断:看控制台。0 宽度 slot 会抛出明显报错:Uncaught TagError: adsbygoogle.push() error: No slot size for availableWidth=0。也可以检查 <ins> → Computed → width,是 0px 就是这条。常见于布局完成前宽度未知的 flex 子元素、position: absolute/float 的父容器、width: max-content 的包裹。
4. 同一个 slot 被 push 了两次(重复 push 报错)
如果两个 push({}) 指向同一个 <ins>,或者 React/Next 的 StrictMode 在开发环境里把你的广告组件挂载了两次,第二次 push 会抛错,slot 可能就空了。控制台里的确切报错是:Uncaught TagError: adsbygoogle.push() error: All 'ins' elements in the DOM with class=adsbygoogle already have ads in them.
怎么判断:控制台查这串文字,并数一数 push 调用次数。每个 <ins> 应该且只应该被 push 一次。在 React/Next 里这就是典型的 StrictMode 双渲染症状。
5. 同时有多个 <script async ...adsbygoogle.js> 标签
如果 layout 把 AdSense 脚本贴了两次(一次在 head 一次在组件里),脚本加载两次,第二次有时绑不上,slot 半推半就。
怎么判断:控制台执行 Array.from(document.querySelectorAll('script[src*="adsbygoogle"]')).length,应该返回 1。
6. <ins> 标签写错了
常见拼写错误:data-ad-clinet、data-ad-slt、漏 class="adsbygoogle"、漏 style="display:block",或者 React 把你写的 dataAdClient 转换后丢了 kebab-case。
怎么判断:查看源代码(真实 HTML,不是 React render tree)。出站的标记必须原样包含 class="adsbygoogle" 和 data-ad-client="ca-pub-..."。
7. 页面达不到内容阈值,或你的 IP 被限流
薄页面——50 字占位、只有链接列表的分类页、搜索结果页——内容不够撑起广告,拍卖返回空,data-ad-status="unfilled" 就会出现。另外,如果你一直猛按 F5 测广告,AdSense 可能当天对你自己的 IP 限流投放。
怎么判断:长文章能填、薄页面不填,就是内容阈值问题。如果某个已知正常的页面只对你空白,用手机切蜂窝(不同 IP)打开——那边能投,就是本地被限流。
最短修复路径
第 1 步:读渲染后 <ins> 的 data-ad-status
控制台:
document.querySelectorAll('ins.adsbygoogle').forEach((el, i) =>
console.log(i, el.getAttribute('data-ad-status'), el.offsetWidth));
截至 2026 年 6 月,这个属性有三个取值:filled、unfilled、unfill-optimized(没返回广告,slot 已被 AdSense 优化)。把读数对应到原因:
| 状态 | 宽度 | 含义 |
|---|---|---|
null / 缺失 | 任意 | push 没跑(原因 #2、#4、#5 或 #6) |
unfilled / unfill-optimized | > 0,新账号每个 slot 都这样 | 账号在审核或处于 24-48 小时预热期(原因 #1) |
unfilled / unfill-optimized | > 0,只有部分页面 | 内容阈值或本地限流(原因 #7) |
unfilled | 0 | 宽度问题(原因 #3) |
filled | > 0 | 正常——还是看不到就查广告拦截器 |
读到 null/缺失,说明脚本根本没把 slot 绑完;读到 unfilled/unfill-optimized,说明绑完了但拍卖没拿到广告。
第 2 步:先排除账号问题(别去调本来没毛病的代码)
如果第 1 步显示新账号或新站点每个 slot 都是 unfilled/unfill-optimized,先停手别调代码。打开 AdSense 后台 → Sites,确认站点显示 Ready,而不是 “Requires review” 或 “Getting ready”。如果还在审核,或者通过还不到 48 小时,那就等——这是预期行为,不是接线 bug。
第 3 步:手动触发 push 验证
控制台:
(window.adsbygoogle = window.adsbygoogle || []).push({});
如果 slot 现在填了之前没填,就是你的 push 代码没跑到。普通页面里,把 push 内联在 <ins> 紧后面:
<ins class="adsbygoogle" ...></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
SPA(React / Next / Astro islands)里,AdSense 脚本只识别整页加载、不识别客户端导航——所以每次路由切换都要重新 push,并把 <ins> 按路由路径 key 一下让它干净地重新挂载,同时挡住重复 push 报错:
useEffect(() => {
try {
(window.adsbygoogle = window.adsbygoogle || []).push({});
} catch (e) {
// StrictMode 双挂载或 "already have ads"——可以忽略
}
}, [router.asPath]);
第 4 步:修正标签
按这个模板原样贴,不要改属性名:
<ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"
data-ad-slot="1234567890"
data-ad-format="auto"
data-full-width-responsive="true"></ins>
React 里 kebab-case 的 data-* 属性会原样保留,所以用 data-ad-client(不是 dataAdClient)。Astro 默认保留属性。如果你的框架会改写它们,就改用原始 HTML 渲染这段。
第 5 步:删除重复的 script 标签
grep -rn "adsbygoogle.js" src/ public/
应该只出现一次(在某个 layout/template 文件里)。重复的删掉。
第 6 步:给 slot 强制最小宽度
.adsbygoogle {
display: block;
min-width: 250px;
min-height: 100px;
}
广告位可见之前不要塞进 display: none 容器——这正是触发 availableWidth=0 的元凶。要隐藏又不想塌掉布局,用 visibility: hidden。
第 7 步:把确实空着的 slot 收起来
确实没填的 slot(原因 #7)不该留一个可见的空洞。Google 官方推荐的规则会把它收起:
ins.adsbygoogle[data-ad-status="unfilled"] {
display: none !important;
}
注意这条只在脚本跑起来之后生效;如果广告拦截器直接拦掉 adsbygoogle.js,属性永远不会被写上,空洞照样在。AdSense 本身已经会自动收起视口外的未填充 slot,只对视口内的留空白以避免页面重排。
第 8 步:换个 IP 测
手机蜂窝、朋友的网、或 VPN。那边能投,就是本地被限流——AdSense 大约 24 小时会自动恢复,所以别再 F5 自己的站测了。
如何确认已修复
- 在真实的已发布 URL 上硬刷新(
Cmd/Ctrl + Shift + R),不要在localhost上测——AdSense 不会在localhost或未列入白名单的域名上投放。 - 重跑第 1 步那段代码。每个可见 slot 都应报告
filled且offsetWidth > 0。 - 控制台没有
TagError(既无No slot size for availableWidth=0,也无already have ads in them)。 - AdSense 后台里站点显示 Ready,且页面 URL 在 Sites 下被允许。
常见问题
账号刚通过审核但 slot 还是空白,是不是坏了? 大概率没坏。通过审核后(或添加/修改广告代码、添加新站点后),广告开始投放可能要最多 48 小时(截至 2026 年 6 月)。Auto ads 通常移动端先出。改代码之前先等满 48 小时。
data-ad-status 是 unfilled(或 unfill-optimized),页面又长又已通过审核,为什么还没广告?
unfilled 表示 push 跑了、拍卖只是没返回广告——通常是该页面/受众的广告主需求低、话题冷门、或者你自己反复刷新导致的临时本地限流。这不是标记 bug。加上第 7 步的 CSS 让空洞收起,再换个 IP 测。
控制台报 No slot size for availableWidth=0,怎么办?
<ins> 渲染在了一个 0 宽度容器里——塌掉的 flex 子元素、display: none 的父容器、或没宽度的 position: absolute/float 盒子。给 slot 容器一个真实宽度(常用 width: 100%),加上第 6 步的 min-width/min-height,并且在元素可见之前不要 push。
控制台报 All 'ins' elements... already have ads in them,怎么消掉?
你把同一个 slot push 了两次。React/Next 里这是开发环境的 StrictMode 双挂载——用 try/catch 包住 push(第 3 步),每个 <ins> 只 push 一次,并把广告组件按路由 key 一下,避免导航时重复触发。
为什么手机能看广告、笔记本看不到? 要么是桌面浏览器里的广告拦截器/隐私插件,要么是你反复测试刷新后 AdSense 对你家 IP 限流了。关掉插件、别再 F5 自己的站,投放大约 24 小时内恢复。
是不是直接用 Auto ads 就好、别用手动 <ins> 了?
Auto ads 省去逐个 slot 的接线(没有 <ins> 拼写错、不用手动 push),但审核期和预热期一样不投,而且需要时间扫描页面。很多站采用混合方案:几个手动单元放在验证过的好位置,其余交给 Auto ads。两者都治不了原因 #1 造成的空白。
预防建议
- 所有 slot 走单一的
<AdSlot>组件 / partial——不复制粘贴标记,不会拼错。 - AdSense 脚本只在一个地方加载(根 layout 或 document head)。
- 加 lint 校验每个
<ins class="adsbygoogle">都有且仅有一个对应的push({})。 - 全局保留
.adsbygoogle { min-width: 250px; }规则,外加第 7 步的未填充收起规则。 - 新站点要预期最多 48 小时内 slot 空白,确认后台显示 Ready 再去当 bug 调。
- 测试时别反复刷同一个页面——用手机或第二个浏览器。
相关阅读
官方参考见 Google 的 data-ad-status 文档 和 站点尚未准备好投放广告时怎么办。