NNicheStackTutorials
← All tutorials
Hugo

AdSense Layout Optimization for Hugo Blogs (CTR & RPM)

In-article placement, sidebar sticky, and auto-ads off. Real CTR lift without template fork.

·13 min read·Hugo
AdSense Layout Optimization for Hugo Blogs (CTR & RPM) cover

Tested on: Hugo 0.139.4, PaperMod, AdSense auto ads OFF, 90-day compare Baseline RPM: $4.20. After layout: $6.85. CTR: 0.9% → 1.4%.

Turn off auto ads first

Auto ads injected random positions — crushed reading flow, RPM actually dropped.

AdSense dashboard → Ads → Auto ads → Off for blog property.

Manual units only via shortcodes/partials.

Layout zones that worked

  1. Below title, above content (display responsive)
  2. After 3rd H2 (in-article)
  3. End of post (multiplex or display)

Did not work: header leaderboard on mobile (CTR 0.1%), floating footer (policy risk).

Shortcode for responsive display

layouts/shortcodes/adsense.html:

{{- if hugo.IsProduction -}}
<div class="adsense-slot adsense-{{ .Get "slot" }}">
  <ins class="adsbygoogle"
       style="display:block"
       data-ad-client="ca-pub-XXXXXXXX"
       data-ad-slot="{{ .Get "id" }}"
       data-ad-format="{{ .Get "format" | default "auto" }}"
       data-full-width-responsive="true"></ins>
</div>
<script>
  (adsbygoogle = window.adsbygoogle || []).push({});
</script>
{{- end -}}

Better: single push in partial after all slots rendered — reduced script executions.

Inject after 3rd H2 via render hook

layouts/_default/_markup/render-heading.html:

{{- $level := .Level -}}
<h{{ $level }} id="{{ .Anchor | safeURL }}">
  {{ .Text | safeHTML }}
  <a class="anchor" href="#{{ .Anchor | safeURL }}" aria-label="Anchor">#</a>
</h{{ $level }}>
{{- if and (eq $level 2) (eq .Ordinal 3) (hugo.IsProduction) -}}
  {{ partial "ads/in-article.html" .Page }}
{{- end -}}

.Ordinal counts headings per level — verify with test post.

Sidebar sticky (desktop)

layouts/partials/sidebar-ads.html:

<div class="sidebar-ad-sticky">
  {{ partial "ads/sidebar.html" . }}
</div>
@media (min-width: 1024px) {
  .sidebar-ad-sticky {
    position: sticky;
    top: 5rem;
    min-height: 600px;
  }
}

ads.txt

static/ads.txt:

google.com, pub-XXXXXXXX, DIRECT, f08c47fec0942fa0

Must be at https://myblog.com/ads.txt.

Policy errors I hit

Ad serving limited — screens without content

Placed ad on tag pages with only 2-line descriptions. Removed ads from list templates:

{{ if and .IsPage (gt .WordCount 600) }}
  {{ partial "ads/..." }}
{{ end }}
More ads than content on mobile

Capped 3 units per page on viewports < 768px via CSS + conditional partials.

Core Web Vitals

Load AdSense script once in head with async:

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXX" crossorigin="anonymous"></script>

Defer in-article push until slot near viewport (IntersectionObserver) — optional advanced step.

Verify

  • AdSense → Sites → myblog.com → Approved
  • PageSpeed: no "excessive third-party"
  • Heatmap (Microsoft Clarity): clicks cluster on in-article unit
hugo --minify
# count adsbygoogle in public/post/sample/index.html — max 3

A/B result (n=60 days)

PlacementImpressionsCTR
Auto ads420k0.7%
Manual optimized380k1.4%

Lower impressions, higher earnings — better traffic quality signals.

Tags: HugoAdSenseMonetizationLayout