Your homepage shows AdSense ads fine. You click into an article and the slots are blank. Same ad-unit IDs, same domain, same approved account. This is the “different template” failure mode, and it is almost never an AdSense bug. Something is structurally different between your two templates: a missing script tag, a CSS rule that collapses the slot to zero, a wrong data-ad-slot value, or content thin enough to fail on a single article but not on the aggregated homepage.
Fastest fix: open the article in Chrome, press F12, go to the Network tab, type ads? in the filter. If you see no request starting with ads?client, your article template never loaded or fired the AdSense code — that is causes 1 or 5 below, and the fix is moving the script and push() into one shared layout. If the request is there but returns 403 or the slot shows data-ad-status="unfilled", it is a content/policy or ad-unit-ID problem (causes 3, 4, 6).
The diagnostic key is comparing the two templates side by side in DevTools, not re-reading your AdSense settings. The Google Publisher Toolbar extension that older guides recommend was retired in 2020 — as of June 2026 the supported tool is plain Chrome DevTools plus the in-account ad-serving troubleshooter.
Which bucket are you in (30-second triage)
| What you see on the article page | Likely cause | Jump to |
|---|---|---|
No ads?client request in Network | Script/push() missing in article template | Cause 1 / 5 |
Request present, <ins> has width/height = 0 | CSS collapses the slot | Cause 2 |
Request returns 403 | Account/site-level serving block (ads.txt, policy) | Cause 3 / 4 |
data-ad-status="unfilled" on a normal-size slot | Thin content or wrong ad-unit ID | Cause 4 / 6 |
ERR_BLOCKED_BY_CLIENT in Console | Ad blocker (yours), not a site bug | Not on you |
Common causes
Ordered by hit rate, highest first.
1. AdSense script loads on homepage but not the article template
A common scenario: you added the AdSense loader to your homepage layout, but the article uses a different layout that does not include it. The article’s <ins> tag is there, but adsbygoogle.js never executed.
How to spot it: DevTools → Network → on an article page, type ads? in the filter. If there is no request beginning with ads?client, no ad request left the page. Also check for the loader itself by filtering adsbygoogle.js — 0 results means the script tag is absent from the article template.
2. Article template wraps the slot in a CSS-collapsed container
The slot exists in the DOM, but its parent is display: none, width: 0, height: 0, or inside a tab/accordion that is not open. The homepage uses a different parent, so the slot renders there. AdSense runs a width validation: if the responsive <ins> has no measurable space, it refuses to fill and the Console logs an adsbygoogle.js validation error such as No slot size for availableWidth=0.
How to spot it: Inspect <ins class="adsbygoogle"> on the article → Computed → check width and height. If 0, the parent is collapsing it. Compare to the homepage version.
3. Different ad-unit IDs — only the homepage’s is correctly configured
The two templates use different data-ad-slot values. The homepage’s was set up correctly in AdSense; the article’s points to a slot that was deleted or never created. A request to a non-existent unit can come back 403 or simply stay unfilled.
How to spot it:
grep -rn "data-ad-slot" src/
Compare the values to AdSense → Ads → By ad unit. The article’s value must match an existing, active unit.
4. Article content too thin to serve ads
AdSense program policies and the Google-served ads on screens without publisher content policy restrict serving on low-value or sparse pages. The homepage aggregates content (latest posts, hero text, categories), so it clears the bar; a very short individual article may not. There is no published hard word count, but as of June 2026 pages where more than half the visible area is non-original or under ~300 words are the usual trigger, and AdSense will set data-ad-status="unfilled" on those articles while still serving the homepage.
How to spot it: Word count under ~300, thin structure, little original text. Confirm with the bucket: a normal-size slot reporting unfilled across many refreshes, while other (longer) articles fill, points here.
5. Article template fires push() before the slot exists (or never)
If your framework hydrates the article asynchronously and adsbygoogle.js runs push({}) before the <ins> is in the DOM — or the push() never runs at all on the article route — the slot stays blank. SPA route changes are the classic trap: the first page load works, but client-side navigation into an article does not re-run push().
How to spot it: Console on the article page: window.adsbygoogle?.loaded. If undefined on the article but defined on the homepage, that template never pushed. With Astro view transitions or Next.js client navigation, also reload the article URL directly — if a hard reload fills but soft navigation does not, the push is not wired to route changes.
6. CSP or iframe sandbox blocks ad rendering on the article template
If the article template uses a stricter Content Security Policy, or wraps content in an <iframe sandbox>, AdSense cannot inject its frame.
How to spot it: DevTools Console on the article page. Look for Refused to load / Refused to frame CSP errors, or iframe sandbox errors mentioning googlesyndication.com or doubleclick.net. Your CSP must allow these in script-src and frame-src.
Shortest path to fix
Step 1: Verify an ad request actually leaves the article page
Open the article, press F12, go to Network, type ads? in the filter (this is Google’s official DevTools check).
- A request beginning with
ads?client= the code ran and asked for an ad. Read its status:200but blank slot → ads.txt or content/unit issue, continue to Step 4 and Step 5.403→ Google refused to serve to this page (account/site block, policy, or unknown unit). Check AdSense → Sites and Policy center.
- No
ads?clientrequest → the script never fired here. Continue to Step 2.
Step 2: Confirm the loader is present on both pages
// Run in the DevTools Console on each page
document.querySelectorAll('script[src*="adsbygoogle"]').length
// Should be 1 on both pages
If it is 0 on the article page, your article template does not include the loader. Move it into a single shared layout component so every page inherits it.
Step 3: Compare <ins> computed dimensions and status
// Run in the Console on each page
Array.from(document.querySelectorAll('ins.adsbygoogle'))
.map(el => ({ status: el.getAttribute('data-ad-status'), w: el.offsetWidth, h: el.offsetHeight }));
data-ad-status is filled (ad served), unfilled (no ad returned, blank space kept), or unfill-optimized (fill-optimization on, intentionally empty). See the official reference.
| Homepage | Article | Diagnosis |
|---|---|---|
filled, w/h > 0 | unfilled, w/h = 0 | Article CSS is collapsing the slot (cause 2) |
filled, w/h > 0 | undefined, w/h > 0 | push() never ran in the article template (cause 5) |
filled, w/h > 0 | unfilled, w/h > 0 | Content thin or wrong ad-unit ID (cause 4 / 6) |
Step 4: Confirm push() runs after the slot exists
The script tag must be present and push() must run once per slot, after the <ins> is in the DOM. For SSR/static markup:
<ins class="adsbygoogle" ...></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
For SPA route changes (Next.js, Astro view transitions), re-run push({}) after each navigation, once per newly mounted <ins>. Pushing twice for the same slot logs adsbygoogle.push() error: All ins elements ... already have ads in them. in the Console.
Step 5: Verify ad-unit IDs match
grep -rn "data-ad-slot" src/ | sort -u
Open AdSense → Ads → By ad unit. Every data-ad-slot value above must match an active unit. A typo or a deleted unit yields a 403 or a permanently unfilled slot.
Step 6: Test on a different article
If only one article is affected and others fill, that article is too thin (cause 4). Add ~500 words of substantive, original content.
If every article is affected, it is a template-level issue (causes 1, 2, 3, 5) — fix the shared layout, not the article.
Step 7: Run the in-account troubleshooter
In AdSense, open Help → Troubleshoot → “Ads not showing” (the ad-serving troubleshooter). It scans your account for serving issues — site not added/verified, policy actions, ads.txt problems — that DevTools cannot see from the page alone.
How to confirm it’s fixed
- Hard-reload (
Cmd/Ctrl+Shift+R) the article with an ad blocker disabled. - Network filter
ads?shows a200ads?clientrequest. document.querySelector('ins.adsbygoogle').getAttribute('data-ad-status')returnsfilled.- Repeat on at least 3 different article URLs, including one reached by client-side navigation (not just a hard load).
When this is not on you
AdSense fill rate fluctuates through the day with advertiser demand. A 10–30% swing is normal, so an occasional blank slot on a healthy page is expected. The threshold for “something is wrong” is “blank consistently across refreshes for hours on the same page,” not “blank on one refresh.” Likewise, ERR_BLOCKED_BY_CLIENT in your own Console means your browser’s ad blocker ate the request — test in a clean profile before assuming a site bug.
Easy to misdiagnose as
People assume “ads not showing on articles” is an AdSense outage or account problem. When the homepage on the same account is serving, the account is fine — the difference is almost always template structure, CSS, the wrong ad-unit ID, or thin content on that page.
Prevention
- Use one shared
<AdSlot>component across templates. No copy-pasted<ins>markup. - Pin the AdSense loader in a single root layout that every page inherits.
- Validate ad-container CSS in CI: every
<ins>parent must have non-zero width. - Keep total ad area well under ~30% of the viewport per page; over-dense pages serve blank silently as of June 2026.
- Set a minimum word-count gate (~500 words) for articles you want monetized.
- After framework upgrades or routing changes, re-verify ads on at least 3 article URLs, including one via soft navigation.
FAQ
- Ads work on the homepage but every article is blank — is my account banned? No. If any page on the account serves, the account is serving. A site-wide block returns
403on the homepage too. Article-only blanks are a template/content difference. - The Network request returns
200but the slot is still empty. Why? A200with no ad usually means ads.txt is missing or wrong, or the page is too thin/low-value to fill. Verify ads.txt atyourdomain.com/ads.txtlists your publisher ID, then check content depth. - Why does fill rate vary? Advertiser demand changes by content, audience, geography, and season. A 10–30% daily swing is normal.
- Will adding more ad slots improve fill? Only up to a point. Too many slots dilutes each one; 2–3 manual slots per article (optionally plus Auto ads) is the sweet spot for most niches.
- Do I still need the Google Publisher Toolbar extension? No — it was retired in 2020. Use Chrome DevTools and the in-account ad-serving troubleshooter instead.