Ad Blockers Broke My Vercel Static Site Layout — Script Paths That Trigger Filters
AdSense on Hugo/Vercel — uBlock hid more than ads. Filenames, third-party domains, and lazy-load patterns that look like malware.
Problem: 30% of visitors (per Plausible + manual testing) saw broken nav. Console:
GET https://mysite.com/js/ads.js net::ERR_BLOCKED_BY_CLIENT
Followed by JS error — my nav code was in the same file as ad loader.
Mistake: ads.js bundled with app logic
<script src="/js/ads.js"></script> <!-- blocked -->
<script src="/js/site.js"></script> <!-- fine -->
EasyList blocks paths matching ads.js, /ads/, ad-slot.
Fix: separate concerns
<script src="/js/site.js" defer></script>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" crossorigin="anonymous"></script>
Site works when ad script blocked. Ad slot collapses gracefully:
.ad-slot:empty { display: none; min-height: 0; }
.ad-slot { min-height: 90px; } /* reserve space only when ad loads */
Don't name folders /ads/
Static path /ads/banner.html — blocked even if it's your analytics dashboard.
Rename to /sponsors/ or /monetization/.
class names
class="ad-banner" — cosmetic filters hide it. Use neutral names:
<aside class="content-promo" data-ad-slot="sidebar"></aside>
Test with blockers on
# Chrome: install uBlock Origin
# Or curl won't help — must test in browser
Checklist:
- Disable JS in ads file — site navigable?
- No layout shift when slot empty?
- No combined bundles with "ad" in filename
Vercel note
Hosting doesn't cause blocking — path naming on your static export does. Same issue on any host.
Result
Bounce rate from ad-block users dropped 18% → 9% after splitting scripts.
Detect blocked ads in analytics
// Optional: don't gate features on ad load
window.addEventListener('load', () => {
const slot = document.querySelector('[data-ad-slot]');
if (slot && slot.offsetHeight === 0) {
document.documentElement.dataset.adblock = 'likely';
}
});
Use for layout tweaks only — never nag users to disable blockers.
Third-party script order
Load site.js first with defer. Ad scripts async at end of <body>. Parser never blocks on blocked ad URL.