Adsterra Ads in Hugo via Shortcodes (Without Killing Page Speed)
Lazy-load banner and native ad units with shortcodes. Pass Core Web Vitals with async injection.
Tested on: Hugo 0.139.4, Adsterra banner 728x90 + native, PageSpeed March 2026 Constraint: INP must stay under 200ms — sync ad scripts failed that.
Shortcode: banner slot
layouts/shortcodes/adsterra-banner.html:
{{- if hugo.IsProduction -}}
<div class="ad-slot ad-banner" data-adsterra="banner" data-key="{{ .Get "key" }}" style="min-height:90px">
<noscript>Enable JavaScript for ads.</noscript>
</div>
{{- else -}}
<div class="ad-placeholder" style="height:90px;background:#f3f4f6;display:grid;place-items:center">
Ad placeholder ({{ .Get "key" }})
</div>
{{- end -}}
Use in markdown:
{{< adsterra-banner key="post-top" >}}
Deferred loader (one script site-wide)
layouts/partials/adsterra-loader.html:
{{- if hugo.IsProduction -}}
<script>
(function () {
const KEY = 'YOUR_ADSTERRA_SCRIPT_KEY';
function loadAdsterra() {
if (window.__adsterraLoaded) return;
window.__adsterraLoaded = true;
const s = document.createElement('script');
s.async = true;
s.src = 'https://www.highperformanceformat.com/' + KEY + '/invoke.js';
document.head.appendChild(s);
}
if ('requestIdleCallback' in window) {
requestIdleCallback(loadAdsterra, { timeout: 2500 });
} else {
setTimeout(loadAdsterra, 1500);
}
})();
</script>
{{- end -}}
Include in footer.html — not <head>. Loading in head pushed TBT to 520ms.
Native ad shortcode
layouts/shortcodes/adsterra-native.html:
{{- if hugo.IsProduction -}}
<aside class="ad-native" id="ad-native-{{ .Ordinal }}" data-adsterra="native"></aside>
{{- end -}}
Adsterra dashboard gives container IDs — map in loader after script loads.
CSS reserve space (CLS)
assets/css/extended/ads.css:
.ad-slot { margin: 1.5rem 0; text-align: center; overflow: hidden; }
.ad-banner { min-height: 90px; }
@media (min-width: 768px) { .ad-banner { min-height: 90px; } }
.ad-native { min-height: 280px; margin: 2rem 0; }
Reserved min-height stopped CLS 0.18 → 0.02.
Policy: ad density
My rules in layouts/_default/single.html:
{{ .Content }}
{{ if and (gt .WordCount 800) (not .Params.noAds) }}
{{ partial "ads/mid-article.html" . }}
{{ end }}
No ads on short pages or noAds: true legal content.
Errors
Ad script blocked — site flagged "malware"
Third-party cookie warnings in Chrome — not a Hugo issue. Verify Adsterra domain not on blocklists.
Blank ad slot in local hugo server
Expected — hugo.IsProduction is false. Build with HUGO_ENV=production hugo to test placeholders only.
Verify
HUGO_ENV=production hugo --minify
grep -r "highperformanceformat" public/ | wc -l
# should be 1 (single loader)
PageSpeed mobile on production URL with ads enabled:
| Metric | No ads | Sync ads | Lazy ads |
|---|---|---|---|
| TBT | 40ms | 520ms | 85ms |
| CLS | 0.01 | 0.19 | 0.02 |
Revenue note
RPM lower than sync by ~8% in my 30-day test — acceptable for passing CWV and AdSense eligibility later.