Static sites have no server to make decisions at request time, which sounds like a problem for advertising and mostly is not. The ad decision happens in the browser, and the static host serves HTML that never has to change.
Why the ad must be client-side
If you baked the ad into the HTML at build time, changing a sponsor would mean a rebuild and a redeploy, campaign scheduling would be impossible, and every reader would see the same creative regardless of frequency capping.
So the page ships an empty slot, and a small script fills it after load. Your build stays static; only the ad request is dynamic.
```html <!-- in your layout, once --> <script async src="https://edge.example.com/v.js" data-site="s_..."></script>
<!-- wherever the ad goes --> <div data-ad="below-header"></div> ```
The four things that go wrong
1. Layout shift
Static sites are usually fast, which makes a late-arriving ad more jarring, not less. Your content paints, then a banner appears and pushes it down.
Reserve the space in CSS before anything loads:
``css [data-ad="below-header"] { min-height: 90px; } @media (max-width: 640px) { [data-ad="below-header"] { min-height: 100px; } } ``
This is not optional. Cumulative Layout Shift is a ranking signal, and static sites otherwise score well on it — an unreserved ad slot can be the single thing dragging your Core Web Vitals down.
2. The framework strips or duplicates the script
Astro — put the script in your base layout, and use is:inline so it is not processed by the bundler.
Next.js static export — use next/script with strategy="afterInteractive" rather than a raw tag, or it may be executed on every client-side navigation.
Hugo / Eleventy / Jekyll — a plain tag in the base template works. Watch for HTML minifiers that strip async.
3. Client-side navigation does not re-render the slot
This catches every SPA-flavoured static site. The first page load works, then the reader navigates, the URL changes, the DOM swaps, and the ad slot is either empty or still showing the previous page's ad.
The tag needs to be told to re-scan. Most expose something like:
``js // after a client-side route change window.__adtag?.refresh() ``
In Next.js, hook it to the router event. In Astro, listen for astro:page-load. If your ad server has no refresh API, that is a real limitation on a site with client-side routing.
4. Content Security Policy
Static hosts make CSP easy to add and easy to get wrong. The ad domain needs to be allowed in script-src, connect-src, and img-src:
`` script-src 'self' https://edge.example.com; connect-src 'self' https://edge.example.com; img-src 'self' data: https://edge.example.com https://cdn.example.com; ``
That last one needs to include wherever creative images are hosted, which is often a different domain from the ad server.
If ads work locally and not in production, CSP is the first thing to check — the header is usually added at the host level and does not exist in dev.
What static sites do better
Speed. The ad request is the slowest thing on the page, which means it is the only thing to optimise.
Caching. Your HTML sits on a CDN indefinitely because it contains no per-reader content. Sites that render ads server-side cannot cache pages as aggressively.
Predictable placement. Templates are yours, so slots go exactly where you want them without fighting a theme.
A note on build-time house ads
One legitimate exception: if you want a fallback that renders instantly with no network request, put a plain house ad in the HTML and have the tag replace it if a paid ad comes back.
``html <div data-ad="below-header"> <a href="/newsletter">Get the weekly email →</a> </div> ``
The slot is never empty, there is no shift, and the reader with an ad blocker sees your newsletter promotion instead of a gap.