Server-Side rendering doesn't break your ads. It makes them better than ever before.
For a variety of reasons, everyone wants their site to perform well. A performant site often correlates directly with quantifiable outcomes—greater user conversion, better ranking with search engines, etc. However, ultimately everyone wants this to translate into real revenue, and when we first use the tools most suited for this—namely, static site generators—we often see ad revenue tank.
What happens here? Well, thankfully it’s nothing insurmountable. In fact, leveraging server-side rendering unlocks some of the most powerful architectures for improving both your page performance and your ad auction performance.
Loading Your Scripts
Third party ads libraries such as Prebid.js, Google Publisher Tag, and TAM are loaded with script tags—this is in part because they attach globally to your browser’s window object.
It’s useful then to know with precision when exactly the window object is created in a Next.js lifecycle. Often advice in this area assumes you should wait post-hydration to allow scripts to capture the window object, but the window object is available after the first server-side render.
Thus, when you first start using Next.js with ads, you might receive the advice to use “afterInteractive” on all your script tags. This isn’t strictly necessary. In fact, if you don’t load your scripts as “beforeInteractive,” you could be missing out on revenue, since loading the scripts sooner means your ads can load sooner.
Command Queues
With GPT, instead of waiting for hydration, you can use the “defer” keyword on your script tag to ensure that execution waits until just after HTML parsing.
<script defer src="gpt.js" />You can then predefine command queues:
window.googletag = window.googletag || {};
googletag.cmd = googletag.cmd || [];This is the exact formula googletag uses internally to attach to the window object. Once the script finishes loading, it will use what’s there, and attach to the window object internally, so there won’t be any synchronization issues. You’re then free to use the `async` keyword:
<script async src="gpt.js" />This means you can attach commands to the command queue even if your scripts aren’t loaded yet. The command queue allows you to continue setup functions before the script is fully loaded:
googletag.cmd.push(function() {
googletag.defineSlot(...);
});This is dependency inversion, and it comes up repeatedly to resolve tough asynchronous execution knots. Interestingly, this comes up a lot in GPU programming as well—if your pipelines have a queue to pop commands off from, it means the pipeline can consume it as soon as it decides it’s ready, instead of waiting for something external that presumes it isn’t.
Single Request Architecture
This dependency inversion enables us to leverage one particularly powerful googletag feature—Single Request Architecture:
googletag.cmd.push(() => {
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});This tells googletag to batch all eligible ad slots into a single network request. This is designed specifically for server-rendered pages. The critical constraint here is that all slots you want in the batch must be defined—and otherwise ready (i.e. header bidding is completed)—before the first “googletag.display” call.
This is a killer reason to get your slot definitions going pre-hydration. Fewer network requests has performance benefits—reducing RTT and main thread contention—but the real benefit is in what Google Ad Manager does with a bundled request: it enables portfolio-level optimization. Advertisers want reach and context, and giving them that context increases their willingness to bid. This also allows Google Ad Manager to evaluate your set of ads as a whole, and unlock bundle-based pricing.
Duplicate Definitions
One gotcha here is that you must make sure not to define the slot twice—this can happen if, for example, you use strict mode. I recommend creating an object from which to manage all your ads scripts that is built from a “once” function.
Client-Side Navigation
Once you have your initial page load correct, there’s just one more thing you need to worry about: client-side navigation.
While it’s useful overall for page performance to not reload everything every time a link is clicked, without a page reload your ad scripts won’t know to load new ads.
This simply means you have to refresh your ads manually when your navigation route changes, e.g.:
“use client”;
import { useEffect } from “react”;
import { usePathname } from “next/navigation”;
export default function GamRefresh() {
const path = usePathname();
useEffect(() => {
if (window.googletag?.pubads) {
googletag.pubads().refresh();
}
}, [path]);
return null;
}Conclusion
When you first attempt to convert to a static site generator, it might look like a daunting regression. With just a few tweaks, however, you can see both your site’s performance and revenue leap ahead. It’s worth the investment!
If you’d like to explore more about how to optimize your ads, I recommend examining how collapsible ad slots can hurt CLS.

