The Stack
How to add AdSense to Hugo without layout shift
Hugo is a near-ideal ad-site engine — fully static, blazing fast, no client-side routing to fight. The one thing that trips people up in 2026 is that Hugo moved its template directories, so every older AdSense-on-Hugo guide points you at paths that no longer exist.
Hugo is quietly one of the best engines for an ad-supported site: it renders to static HTML, ships nothing to the client you didn’t ask for, and — because it’s fully static with hard page navigations — it sidesteps the “ads only load on the first page” bug that plagues SPA frameworks. Ad init is genuinely simple here.
The one thing that trips people up in 2026 is a moving target the tutorials haven’t caught up to.
First: Hugo moved its template directories (v0.146)
Hugo v0.146.0 (March 2025) re-implemented the template system, and almost every AdSense-on-Hugo guide still ranking shows the old paths. For a current site, use the new ones:
| Purpose | New (v0.146+) | Legacy (pre-0.146) |
|---|---|---|
| Base template | layouts/baseof.html | layouts/_default/baseof.html |
| Partials | layouts/_partials/ | layouts/partials/ |
| Shortcodes | layouts/_shortcodes/ | layouts/shortcodes/ |
The _default/ folder is gone (move its files up to layouts/), and partials/shortcodes gained an underscore prefix. Hugo maps old paths to new for backward compatibility, so many themes still build — but it isn’t fully transparent, so author new files at the new paths. On Hugo < 0.146, use the legacy paths in the right column.
Step 1 — Load AdSense in the head
Add the loader once, site-wide, in your head partial — layouts/_partials/head.html (called from layouts/baseof.html):
<script async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-0000000000000000"
crossorigin="anonymous"></script>
The async attribute keeps it off the critical rendering path — that’s your CWV lever. Load it once; don’t repeat it per unit. On a third-party theme (PaperMod, Blowfish, Docsy…), don’t edit the theme directly — use its head-extend hook (e.g. layouts/_partials/extend_head.html) so a theme update doesn’t wipe your change.
Step 2 — ads.txt in static/
Drop the file at static/ads.txt; Hugo copies everything in static/ to the site root at build:
google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0
Confirm it resolves at https://yoursite.com/ads.txt after deploy.
Step 3 — A reserved-space ad shortcode
Shortcodes are the right Hugo primitive for dropping an ad into markdown — and they have a hidden advantage (see the note below). Create layouts/_shortcodes/adunit.html:
<div class="ad-slot" style="min-height:280px;display:block;text-align:center;margin:1.5rem 0;">
<ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-0000000000000000"
data-ad-slot="{{ .Get "slot" }}"
data-ad-format="{{ .Get "format" | default "auto" }}"
data-full-width-responsive="{{ .Get "responsive" | default "true" }}"></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
</div>
Call it from any post’s markdown:
Some paragraph before the ad.
{{</* adunit slot="1234567890" */>}}
Paragraph after.
Two things make this work. The wrapping <div> with an explicit min-height reserves the slot so the ad doesn’t shove content down when it loads — the single biggest thing you control for CLS. And .Get "slot" reads the named param you pass, with | default supplying fallbacks. Match the min-height to the unit you expect at each breakpoint (the placement guide has the reserve table).
The
unsafetrap. goldmark strips raw HTML from markdown by default (markup.goldmark.renderer.unsafe = false). If you paste a raw<ins>block straight into a.mdfile, it vanishes — and the usual “fix” is to flipunsafe = true, which lowers your guard site-wide. You don’t need to: shortcode output isn’t subject to the raw-HTML filter, so the shortcode above renders withunsafeleft safely off. Prefer the shortcode for exactly this reason.
Step 4 — Consent Mode v2 for EEA/UK traffic
EEA, UK, and Swiss traffic requires a Google-certified CMP, and the consent signal must load before the AdSense loader. In layouts/_partials/head.html, put the CMP (or AdSense’s own built-in Privacy & messaging banner) above the Step-1 loader. If you use AdSense’s built-in CMP, the existing loader handles the banner once you configure it in the dashboard.
Why Hugo is a strong choice for this
Because the two things that make ads pay — speed and simplicity — are Hugo’s defaults. Static output behind a CDN gives you the Core Web Vitals that lift viewability (fast pages average far higher ad viewability than slow ones), and full-page navigation means each adsbygoogle.js init just works, with no SPA re-init dance. Keep the loader on Google’s CDN via the async tag — don’t run it through Hugo Pipes or self-host it. That’s the cost-inversion thesis in practice: when the fast stack is also nearly free to host, it wins on margin and revenue. Prefer components and content collections? Here’s the same setup on Astro.
FAQ
- Where do I put the AdSense script in Hugo?
- In the head of your base template, loaded once site-wide. On Hugo v0.146 and later, add the async loader in layouts/_partials/head.html (called from layouts/baseof.html); on older Hugo it's layouts/partials/head.html. If you use a third-party theme like PaperMod, don't edit the theme — use its head-extend hook, such as layouts/_partials/extend_head.html, instead.
- Do I need goldmark unsafe = true to add ads in Hugo?
- Only if you paste raw <ins> HTML directly into a markdown file, because goldmark strips raw HTML by default. If you place ads with a shortcode instead — the idiomatic Hugo way — you do NOT need unsafe rendering, because shortcode output isn't subject to the raw-HTML filter. Prefer the shortcode and leave unsafe = false.
- Where does ads.txt go in Hugo?
- In the static/ directory as static/ads.txt. Everything in static/ is copied verbatim to the site root at build, so it publishes at yoursite.com/ads.txt where AdSense expects it. This behavior was unchanged by the v0.146 template overhaul, which only touched the layouts/ directory.
Keep reading
- Add AdSense to Astro The other static-first stack — same speed advantage, different gotcha.
- Where to put ads (without wrecking your page) The placements and reserved-space rules that protect Core Web Vitals.
- You don't need WordPress to run an ad site Why a lean, fast static stack beats WordPress on cost and speed.