静态站手动广告位不渲染(Astro / Next 导出)

Astro / Next / Hugo 静态导出里 AdSense `<ins>` 块一直空——hydration 与组件挂载怎么破坏广告渲染。

在 Astro、Next.js 静态导出、SvelteKit、Hugo 上,你已经放好了 AdSense 的 <ins> 块和 adsbygoogle.js 脚本。查看源代码一切都在。控制台 window.adsbygoogle.loaded 返回 false。位永远空白。这是静态站 / SPA 的 AdSense 经典坑。脚本加载了,但激活 slot 的 push({}) 调用要么没触发、要么太早、要么触发了两次——AdSense 对这三种都会静默拒绝。

修复就是让 push({})<ins> 进入 DOM之后、每个 slot 精确执行一次。听起来简单;但现代框架的生命周期让这不简单。

常见原因

按命中率从高到低。

1. push 在 <ins> 挂载前就触发(竞态)

你的组件:

useEffect(() => {
  (window.adsbygoogle = window.adsbygoogle || []).push({});
}, []);

如果这个 effect 跑在一个比 <ins> 提交进 DOM 还早的 layout 里(或者 hydration 之前),AdSense 的队列收到了 push 但找不到对应的 <ins>

怎么判断:控制台 document.querySelector('ins.adsbygoogle')。返回了元素但 data-ad-statusundefined,就是 push 太早了。

2. 同一 slot 触发多次 push

SPA 切路由让你的 useEffect 重新跑,但前一次渲染的 <ins> 还在。AdSense 看到一个 slot 有两次 push,拒绝填充。

怎么判断:控制台 Failed to load resourceTagError: All ins elements in the DOM with class=adsbygoogle already have ads in them——就是 double-push。

3. SSG 只渲染 HTML——没有客户端 push

Astro / Next 静态导出,广告组件忘了 client:load。HTML 里有 <ins> 但没有 JS 跑去 push。

怎么判断:页面加载完 window.adsbygoogle 还是 []——push() 从未执行。

4. 组件用 client:only 但时机不对

Astro 的 client:only 把渲染推迟到 JS 跑起来后。<ins> 出现得很晚;如果 AdSense 脚本之前就跑过 push,那次 push 找不到匹配的 slot。

怎么判断:Timeline 里 <ins> 在页面加载后 1-2 秒才出现。AdSense 脚本先跑、找到 0 个 slot。

5. AdSense 只在生产生效

本地开发 (localhost) 和 Vercel 预览 URL 永远不投真广告。slot 空是预期的,不是 bug。

怎么判断:同样的代码在生产域名工作、staging/preview 不工作。别在那调试——上生产测。

6. 容器拦截脚本(CSP、iframe sandbox)

如果你的页面 CSP 不允许 googlesyndication.compagead2.googlesyndication.com,脚本能加载但广告 iframe 渲染不出来。

怎么判断:控制台 “Refused to load script from ‘pagead2.googlesyndication.com’ because it violates the following Content Security Policy directive.”

最短修复路径

第 1 步:建一个统一的可复用 AdSense 组件

Astro + React island (client:load):

// AdSlot.jsx
import { useEffect, useRef } from 'react';

export default function AdSlot({ slotId, format = 'auto' }) {
  const insRef = useRef(null);
  useEffect(() => {
    if (!insRef.current || insRef.current.dataset.adPushed === 'true') return;
    try {
      (window.adsbygoogle = window.adsbygoogle || []).push({});
      insRef.current.dataset.adPushed = 'true';
    } catch (e) {
      console.warn('AdSense push failed', e);
    }
  }, []);

  return (
    <ins
      ref={insRef}
      className="adsbygoogle"
      style={{ display: 'block' }}
      data-ad-client={import.meta.env.PUBLIC_ADSENSE_CLIENT}
      data-ad-slot={slotId}
      data-ad-format={format}
      data-full-width-responsive="true"
    />
  );
}

用法 <AdSlot client:load slotId="1234567890" />

纯 Astro(不用 React),用内联 <script>

---
const { slotId } = Astro.props;
---
<ins class="adsbygoogle"
     style="display:block"
     data-ad-client={import.meta.env.PUBLIC_ADSENSE_CLIENT}
     data-ad-slot={slotId}
     data-ad-format="auto"
     data-full-width-responsive="true"></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>

第 2 步:防 double-push

上面组件里的 data-ad-pushed="true" 标志位防止重新渲染时重复 push。SPA 路由切换尤其关键。

第 3 步:处理 SPA 路由切换

Astro 的 View Transitions 或 Next.js App Router:

import { useEffect } from 'react';
import { usePathname } from 'next/navigation';

useEffect(() => {
  // 路由切换时给所有还没 push 过的新 <ins> push 一下
  document.querySelectorAll('ins.adsbygoogle:not([data-ad-pushed])').forEach(() => {
    (window.adsbygoogle = window.adsbygoogle || []).push({});
  });
}, [pathname]);

第 4 步:上生产测,不要在本地测

AdSense 在 localhost*.vercel.app 预览 URL 等任何未批准域名上不投广告。部署到真实域名再测。AdSense 论坛上一半的求助帖都是这个。

第 5 步:CSP 放行 AdSense

如果有 CSP 头,需要:

script-src 'self' 'unsafe-inline' https://pagead2.googlesyndication.com https://googleads.g.doubleclick.net;
frame-src https://googleads.g.doubleclick.net https://tpc.googlesyndication.com;
img-src 'self' data: https://*.googlesyndication.com https://*.doubleclick.net;

能不加 'unsafe-inline' 就别加。

第 6 步:等 24-48 小时让 AdSense 报告同步

哪怕修对了,AdSense 收益面板也要 24-48 小时才反映出新的填充。

哪些情况可能不是你操作错了

AdSense 对 SPA / 静态站的行为没文档,部分要靠论坛反馈推断。生产上耐心测。静态站社区在这方面已经做了大量试错。

容易误判的情况

以为广告 slot “坏了”,其实是 AdSense 客户端没在对的时机 push。脚本标签是加载了(Network 显示 200),但 push 没触发或触发不对。

预防建议

  • 建一个可复用的 <AdSlot> 组件。所有 slot 都走它。
  • 每个元素都用 flag 防止重复 push。
  • SPA 项目每次路由切换都重新扫描 + push。
  • 上生产测,不要在本地测。
  • 加一个简单的 CI 测试:部署 → curl 页面 → 确认 <ins class="adsbygoogle"> 出现。

FAQ

  • 静态站到底能用 AdSense 吗? 能——push 时机对就行。大多数主流内容站都是静态的。
  • 改用 Auto Ads 更简单吗? Auto Ads 自动处理这套——静态站确实更简单,减少 bug 表面积。

相关阅读

标签: #AdSense #变现 #排查 #静态站