You’ve added <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-..."> and the <ins class="adsbygoogle"> tag. DevTools Network confirms adsbygoogle.js returned 200. But after refreshes, after hours, the ad slot stays a blank <ins> block. This is the most “looks correct, isn’t” failure mode in AdSense — and almost always traces to one of six wiring issues, not a code bug.
The diagnostic key is the data-ad-status attribute the AdSense script sets back onto your <ins> tag. Once you read that, the fix is usually 5 minutes.
Common causes
Ordered by hit rate, highest first.
1. (adsbygoogle = window.adsbygoogle || []).push({}) never fires
The script loads, but the push({}) call that activates the slot is missing or in a place that never runs (inside a route change handler that didn’t fire, gated by an if, removed during SSR vs hydration).
How to spot it: In DevTools console: window.adsbygoogle.loaded. If undefined or false, the push never ran. Also: the <ins> will have no data-ad-status attribute at all (not even unfilled).
2. Slot rendered with width = 0
The <ins> element exists in a parent that’s display: none, has 0 width on the current breakpoint, or is inside a tab/accordion that isn’t open. AdSense rejects 0-width slots silently and the slot stays blank.
How to spot it: Right-click → inspect the <ins>. Computed → width. If 0px, that’s it. Common in flex containers that collapse and width: max-content boxes around the ad.
3. Multiple <script async ...adsbygoogle.js> tags
If your layout has the AdSense script in two places (e.g., once in the head, once in a component), it loads twice and the second one can fail to bind correctly. The slots end up half-pushed.
How to spot it: Array.from(document.querySelectorAll('script[src*="adsbygoogle"]')).length in console. Should be 1.
4. <ins> markup is invalid
Common typos: data-ad-clinet (sic), data-ad-slt, missing class="adsbygoogle", missing style="display:block", or React mangling kebab-case to camelCase when you wrote dataAdClient.
How to spot it: View source (not React render tree). The actual HTML must contain exactly class="adsbygoogle" and data-ad-client="ca-pub-...".
5. Page is below AdSense’s content threshold
Some pages — a 50-word “Hello world” stub, a category page that’s just a link list with no description, a search results page — don’t have enough content to host an ad. AdSense’s data-ad-status="unfilled" will fire.
How to spot it: Compare to a known-good article on the same site. If long articles fill but thin pages don’t, content threshold is the cause.
6. Your IP or test session is flagged
If you’ve been F5-ing the page to test ads, AdSense may rate-limit serving on your IP for the day. You’ll see ads from another network (mobile data, friend’s laptop) but not your own.
How to spot it: Open the page from your phone on cellular data (different IP). If ads serve there, you’re being rate-limited locally.
Shortest path to fix
Step 1: Read data-ad-status from the rendered <ins>
In console:
document.querySelectorAll('ins.adsbygoogle').forEach((el, i) =>
console.log(i, el.getAttribute('data-ad-status'), el.offsetWidth));
| Status | Width | Meaning |
|---|---|---|
undefined | any | Push never ran (cause #1 or #4) |
unfilled | > 0 | Push ran, auction returned nothing (cause #5 or #6) |
unfilled | 0 | Width problem (cause #2) |
filled | > 0 | Working — check ad blockers if you don’t see it |
Step 2: Force the push call (test it manually)
In console:
(window.adsbygoogle = window.adsbygoogle || []).push({});
If a slot fills now but didn’t before, your push code wasn’t running. For an SPA / Next / Astro page, place the push in:
<ins class="adsbygoogle" ...></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
Inline, immediately after the <ins> tag. SPA route changes: re-run the push on route change.
Step 3: Fix the markup
Use this exact template; do not change attribute names:
<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>
In React / Astro, write attributes as dangerouslySetInnerHTML or as kebab-case props your framework preserves (Astro keeps them as-is; React requires data-ad-client).
Step 4: Remove duplicate script tags
grep -rn "adsbygoogle.js" src/ public/
Should appear in exactly one layout / template file. Delete duplicates.
Step 5: Force min-width on slots
.adsbygoogle {
display: block;
min-width: 250px;
min-height: 100px;
}
Don’t put ads inside display: none until visible. Use visibility: hidden only if needed.
Step 6: Test from a fresh IP
Phone on cellular, friend’s network, or a VPN. If it serves there, AdSense will recover for you in 24 hours — stop reloading your own site to test.
Prevention
- Use a single
<AdSlot>component / partial. All slot markup goes through it. No copy-paste. - Pin the AdSense script to one place — root layout or document head.
- Lint that every
<ins class="adsbygoogle">has a matchingpush({})call. - Add a CSS
min-width: 250pxrule for.adsbygooglein your global stylesheet. - Don’t reload the same page repeatedly when testing — use a phone or another browser.