你从 AdSense Auto Ads(或 <ins class="adsbygoogle">)换到 Google Publisher Tag(GPT),因为 Ad Manager 给你广告调度、header bidding、line item 定向这些精细控制。gpt.js 接好了,defineSlot、display 都调了,部署上线——所有 slot 仍然是空的。DevTools console 要么报 googletag is not defined,要么报 defineSlot was not called for slot,要么什么也没有。GPT 失败基本归到五类:脚本根本没加载、slot DOM id 和 defineSlot 不一致、googletag.cmd.push 在 gpt.js 之前跑、SPA 路由切换导致双初始化、广告拦截扩展。这篇按命中率排序走完每一类,给出具体的 console 诊断和最小可用代码。
10 秒判断你是哪种情况
DevTools console 依次敲:
| 看到的提示 | 最可能的原因 |
|---|---|
Uncaught ReferenceError: googletag is not defined | gpt.js 根本没加载(网络或广告拦截) |
googletag.pubads().getSlots() 返回 [] | defineSlot 没执行 |
getSlots() 有 slot 但 <div> 还是空 | defineSlot 的 id 和 DOM <div> 的 id 不一致 |
警告 defineSlot was not called for slot ... | display() 在 defineSlot 之前调,或 slot id 拼错 |
| 两个 googletag iframe 加载、一个空白 | 双初始化(脚本写了两份,或 SPA 路由切换重跑了 setup) |
常见原因(按命中率排序)
1. gpt.js 被广告拦截器 / CSP / 网络挡了
uBlock Origin、Brave Shields、AdGuard、企业网络默认都拦 securepubads.g.doubleclick.net。如果你没在 Content Security Policy 里白名单 Google 广告域名,CSP 也会挡。
怎么判断:DevTools → Network → 筛 gpt。如果 gpt.js 标红、状态 (blocked:csp) 或 (blocked:other),脚本压根没下载,后续访问 googletag 必然报 is not defined。
修复:
- CSP:script-src 白名单
https://securepubads.g.doubleclick.net,frame-src 和 img-src 白名单https://*.googlesyndication.com。 - 用全新的 Chrome profile(关掉所有扩展)测一遍,确认是客户端拦截不是代码问题。
- 别想着「绕开」广告拦截器——优雅降级:GPT 没加载就渲染你自己的内容或干净的空白。
2. 没用 googletag.cmd.push 模式
正确写法是每个 GPT 调用都放进 googletag.cmd.push(function() { ... })。cmd 是个命令队列,gpt.js 加载完后会清空。如果你在脚本加载前直接调 googletag.pubads(),会抛异常。
<!-- 错:race condition -->
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
<script>
googletag.defineSlot('/123/leaderboard', [728, 90], 'banner').addService(googletag.pubads());
googletag.enableServices();
</script>
<!-- 对:用 cmd.push 队列 -->
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
<script>
window.googletag = window.googletag || { cmd: [] };
googletag.cmd.push(function() {
googletag.defineSlot('/123/leaderboard', [728, 90], 'banner').addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
</script>
原理:googletag.cmd = [] 是内联同步初始化的,cmd.push 可以立即调;gpt.js 一加载完就把 cmd 替换成一个会立刻执行 callback 的函数。这是标准异步 loader 模式。
3. slot div 的 id 和 defineSlot 不一致
defineSlot(adUnitPath, sizes, divId) 和 googletag.display(divId) 必须引用和 <div> 完全一样的 id——大小写也算。任何不一致 slot 就是空的。
<!-- HTML -->
<div id="div-gpt-ad-leaderboard"></div>
<!-- 脚本 -->
<script>
googletag.cmd.push(function() {
googletag.defineSlot('/123/leaderboard', [728, 90], 'div-gpt-ad-leaderboard')
.addService(googletag.pubads());
googletag.enableServices();
googletag.display('div-gpt-ad-leaderboard');
});
</script>
怎么判断:console 敲 googletag.pubads().getSlots()[0].getSlotElementId() 看 GPT 认为要塞哪个 id,再对照 DOM 里 <div id="...">。有差异 slot 就填不进去。
4. SPA 路由切换导致双初始化
React / Vue / Astro 客户端路由切换时组件会 remount。如果 defineSlot 每次 mount 都跑,重复定义会被 GPT 静默拒绝。更糟的是 enableServices() 整个页面生命周期只应该调一次。
怎么判断:每次切路由 googletag.pubads().getSlots().length 都在涨;只有第一个 slot 填了,后面的都空。
修复:slot 在 app 入口定义一次,路由切换时用 googletag.pubads().refresh([slot]) 重新拉广告而不是重新定义:
// App 入口——只跑一次
window.googletag = window.googletag || { cmd: [] };
googletag.cmd.push(function() {
window._adSlot = googletag.defineSlot('/123/leaderboard', [728, 90], 'div-gpt-ad-leaderboard')
.addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
// 路由切换 handler
function onRouteChange() {
googletag.cmd.push(function() {
googletag.pubads().refresh([window._adSlot]);
});
}
5. 你其实想用的是 AdSense 不是 GPT
GPT 属于 Google Ad Manager(原 DFP)。只有 AdSense 账号是用不了 GPT 的——你的广告位路径 /12345/slot-name 在 Ad Manager 找不到任何 inventory。Ad Manager 的 network code 自助开户 21 位起,老账号短一些;AdSense 是 <ins class="adsbygoogle"> + pub-XXXX client id,完全两套。
怎么判断:console 看 securepubads... 请求返回 200 但广告服务器回 204 No Content。你 Google 账号下只有 AdSense 没有 Ad Manager。
修复:要么在 Ad Manager 把 AdSense 接成 demand source(先开 Ad Manager,再 link 账号),要么继续用 AdSense <ins>。不要同一页面混用 AdSense Auto Ads 和 GPT——它们会抢库存。
最小可用 GPT 模板(可直接复制)
<head>
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
<script>
window.googletag = window.googletag || { cmd: [] };
googletag.cmd.push(function() {
googletag
.defineSlot('/NETWORK_CODE/leaderboard', [[728, 90], [970, 90]], 'div-gpt-ad-leaderboard')
.addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.pubads().collapseEmptyDivs();
googletag.enableServices();
});
</script>
</head>
<body>
<div id="div-gpt-ad-leaderboard" style="min-width: 728px; min-height: 90px;">
<script>
googletag.cmd.push(function() { googletag.display('div-gpt-ad-leaderboard'); });
</script>
</div>
</body>
NETWORK_CODE 换成你 Ad Manager 的 network code;collapseEmptyDivs() 让填不到广告的 slot 自动塌缩,避免一大块空。
最短修复路径
按命中率排序:
- DevTools Network → 确认
gpt.js真的加载了 → 没加载就是广告拦截 / CSP / 网络 - Console:
googletag.pubads().getSlots()→ 空说明 defineSlot 没跑;非空进下一步 <div id>、defineSlot(...)、display(...)三个 id 完全对齐 → 修掉绝大多数「脚本加载了但空着」- 每个调用都包进
googletag.cmd.push(function(){ ... })→ 修掉时序竞争 - SPA 路由切换用
refresh()而不是 redefine → 修掉「首页有广告后续页都空」
预防建议
- 一个站只用一个广告系统:AdSense
<ins>或 Ad Manager GPT,永远别混。 - slot 在 app 根定义一次,路由切换调
refresh()。 - dev 模式加个小 console banner 显示 slot 数和填充率,QA 时空 slot 一目了然。
- 每个 slot 的
<div>设min-width/min-height等于你定义的最小尺寸——防 CLS,空 slot 也能直观看到。 - 测试前用无痕窗口、关掉所有扩展,再下结论说「slot 坏了」。
FAQ
Q:GPT 能用 AdSense 的广告位吗? A:能——在 Ad Manager 里把 AdSense 接成 backfill demand source。页面上挂的是 GPT,Ad Manager 没匹配 line item 时 AdSense 兜底填充。
Q:为什么用 GPT 而不是 AdSense? A:GPT 给你直销 line item、广告调度、header bidding、人群定向、频次封顶;AdSense 是设完不管的产品;GPT 适合主动管理 demand 的发布者。
Q:本地 OK 生产挂。 A:三种可能:(a)生产 CSP 挡了 Google 广告域名;(b)忘了把 staging 的 network code 改成生产的;(c)生产域名没在 Ad Manager 站点白名单。看 console 具体错。
Q:googletag.pubads().getSlots() 有 slot 但还是空的。
A:库存问题不是代码问题。要么没有 line item 定向到这个广告位路径,要么账号太新还没 demand。去 Ad Manager → Delivery → Line Items 查匹配。
Q:同一页第一个 slot 填了,第二个空着。
A:基本是漏了 enableSingleRequest(),第二个 slot 请求在抢第一个;在 enableServices() 前加上 googletag.pubads().enableSingleRequest()。或者第二个 slot 你 defineSlot 了但忘 googletag.display(secondDivId)。