NNicheStackTutorials
← All tutorials
Hugo

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.

·11 min read·Hugo
Adsterra Ads in Hugo via Shortcodes (Without Killing Page Speed) cover

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:

MetricNo adsSync adsLazy ads
TBT40ms520ms85ms
CLS0.010.190.02

Revenue note

RPM lower than sync by ~8% in my 30-day test — acceptable for passing CWV and AdSense eligibility later.

Tags: HugoAdsterraMonetizationShortcodes