Ad refresh and lazyloading strategies — for publishers and networks

Emil Hein
Ad-tech
Published in
6 min readFeb 22, 2023

How on earth can we optimize our ad units? With code examples, I will show at least one general way of doing it.

Photo by Joshua Sortino on Unsplash

The problem

As an example, let’s say a website/app, has 4 ads on the front page. If 1000 people visit the website, it will result in 4000 ad impressions. As a publisher or network, we typically want to optimize our ads to maximize revenue. There are exceptions, but that is usually the main point of running ads. This can be done in a few ways.

  1. Increase the number of visitors to your website, so we get more impressions (hard)
  2. Optimize your Ecpm, so each impression pays more (this requires a medium amount of work)
  3. Ad more ads (easy, but there is an upper limit if you want to maintain your website design)
  4. Show each user more ads, without adding more ad units to the page (this is what we will examine today)

By refreshing each ad unit, you can increase the number of ad impressions without adding more ad units to the page or without disturbing the user too much. We are going to look at two opposite working forces, that should help increase the quality of your ad slots. These are lazy loading and ad refreshing.

Lazy loading

This is the practice of only retrieving an ad, just before the user needs to see it. This is typically done by loading an ad, just before a user scroll’s it into the viewport. This is done, to make sure we don’t load a lot of ads, the user will never see, thereby decreasing the viewability dramatically (which means lower ECPM). The trick here is to figure out when to start loading your ad. If you do it too soon, some of the impressions might not be seen by the user. And if you do it too late, the ad might not get to load before the user has scrolled past it.

Note: The intersection observer API, can only listen/look for elements already on the page. If you’re working with a page where HTML elements are dynamically inserted (infinite scroll could be an example), then you should look into the Mutation observer API, which I described here

  • So in general, this practice will create fewer ad impressions

How to implement this

In modern browsers, this is usually done with the Intersection observer API, which I described in detail here. The basic idea is, that we can use this API (given by all modern browsers) and use it to figure out when a certain element (the one where we show our ads) is reaching the viewport. A basic wrapper for using this API could look like this

const onIntersection = ({ observerConfig, element, excutionFunction }) => {
if (!(element instanceof Element)) return;
const observerFun = (entries, observer) => {
entries.forEach((entry) => excutionFunction(entry, observer));
};
let observerObjectBe = new IntersectionObserver(observerFun, observerConfig);
observerObjectBe.observe(element);
return observerObjectBe;
};

I have used that to compose this example:

Example using the Intersection observer api

With the above code, a publisher or network can now begin to test some parameters that fit their specific sites.

The reason why there is no golden rule is that every site is different, and the behavior of the users is different. Therefore I strongly suggest that A/B tests are created to make sure you can measure the effect of this lazy loading.

From internal tests, our default is that you should load mobile ad units roughly twice the amount of pixels before the user reaches it, compared to desktop devices.
So if you on a desktop have decided to load the ad 200 pixels before it appears in the viewport, our general rule is that you should load the ad 400 pixels before the element appears in the viewport for mobile ad units.

Ad refresh

This is the practice of refreshing an ad unit, there is already present on the page. This is done to increase the number of ad impressions per user. The tradeoff here is that we don’t want to refresh too often, as most ad servers have rules about it, since it will hurt the user experience. Image a page updating the ads every 5 seconds (not cool). The general minimum rule is roughly 30 seconds between refreshes.

  • So in general, this practice will create more ad impressions

How to implement this

A first naive implementation of this could be using the setInterval function and look like this

setInterval(() => {
//REFRESH YOUR AD
}, 30 *1000); // 30seconds

Note: Strictly speaking the setInterval should not be used if you wan’t complete accuracy, since the architecture of the api, makes it possible for the timer to “drift”, thereby not running every 5 seconds precisely. For thid demo it’s precise enough, but know you can easily google your way to a better “timer”

With your own code inside that function, it will refresh every 30 seconds.
I've created a little example, that should show why this is a problem.

Ad refresh version 1

If you scroll down and stay there a bit and scroll up again, you should see that the function is still running even though our target (in real-life an ad unit) is not in view.
This is not exactly what we want. We want to only refresh when the ad unit is in view.
As described above, we can use the Intersection Observer API for that. A rough example without all the details implemented could look like this:

Ad refresh version 2

The “advanced version”

Depending on how experienced you are, and how “correctly” you want to implement your refresh logic, there are still a few edge cases and details still not implemented in version 2. We are still missing to check if the user has the browser window active (if not, the ad will just refresh in the background, without any viewability). Our logic in version 2 also refreshed the ad every 30 seconds, which is technically not correct. Let's say the user scrolls down to our ad, to start the refresh loop, but then scrolls up again and stays there for a while. When the user scrolls back down, the ad will refresh, even though it hasn’t been in view for 30 seconds. This will implement a version of these two features (some edge cases might still be present)

Ad refresh version 3

Final thoughts

As described above, these two mechanisms give the opposite outcome but believe that if both are used in combination, you can get both quality/viewability with lazyloading and more impressions with ad refresh.

I would always do A/B tests, but my preferred default logic is as follow.

  1. lazy load most ad units, with a default margin of 300/600 pixels.
  2. Start with refreshing every 30 seconds if the ad unit is in view. The advanced version would wait until the ad has had 30 seconds of in-view time + is currently in view.
  3. If not in view wait until the next time it's in view and start the same refresh loop again.

When testing these things it’s important to look at a lot of numbers, so make sure

/ Give me quality!

You might like:

--

--

Emil Hein
Ad-tech

Fullstack developer. I enjoy prototyping and testing new services. I like working with JavaScript, Nodejs, AWS and Vue, Browser API's, adtech, Go + more