What Is Footer Bidding?

Header Bidding through Prebid.js has almost become a standard for online publishers. At Ancient History Encyclopedia, we adopted header bidding early and we’ve never looked back; the revenue lift is significant and it’s easy to manage.

The premise is quite simple: Run an ad auction in the website’s header, before the page loads, in order to get more bids to compete in your ad server, making you more money. It’s ingenious and it works well, but there’s one problem: The browser has to send many ad requests while the rest of the page is loading, which can slow down your page load speed, especially on weaker computers. The more header bidding adapters you add, the more bids you get and the more money you might make. That comes with a big “but,” though: The more requests you make, and the more your user experience suffers. You might end up with a slow-loading page or some stuttering on slower machines.

What Is Footer Bidding?

Enter Footer Bidding. I first learned of the idea from Premesh Purayil of Ranker.com. The idea is simple: Don’t call any ad requests (neither Prebid nor Google Ad Manager) until the page has fully loaded. Instead of getting the ads ready as early as possible (in the header), you do it as late as possible (in the “footer,” even though the code most likely still lives in the HTML’s header).

The whole thing sounds quite counterintuitive, so you’re probably wondering why anyone would do that, right? There are several clear benefits, along with a few drawbacks.

Benefits 

  • Improved User Experience: Getting the page displayed to the user is priority. If users have to wait, they might abandon loading the page and not come back. By loading the page content first, you’re more likely to have an engaged user that starts reading and might visit another page.
  • Easy Ad Layout Checks: If your website is like ours and you have many different types of pages with varying ad layouts and ad units, then footer bidding is going to make your life a lot easier. Loading ads that aren’t on the page can result in impression loss and reduced viewability metrics. You can check which ad units are present on the page through JavaScript, but if you run the check too early (before the browser has fully parsed the document), the script might not find an ad unit even though it’s going to be on the page. If you run your ad-related scripts only after the page has finished loading, that problem goes away.
  • SEO: Having a faster-loading page and more users engaging with your site has a positive impact on your search ranking. How much? Who knows, but Google keeps pointing out that user experience is key.

Drawbacks

  • Late Ad Loading: Your ads are possibly going to load quite late. If your page doesn’t load quickly, people might already be reading before the first ad appears. If you have ads in the header of your site, users might have scrolled past them already when they load. We definitely don’t want that! To counteract this, you’ll need to make sure your site loads lightning-fast. More about that later.

Implementation

For this article, I am assuming that the reader already has a header bidding setup on their site (not a managed solution) and that you understand some JavaScript. We’re going to use Prebid.js with Google Ad Manager and Amazon TAM for this example. Without Amazon TAM you could simplify this a lot, but I decided to include it because when we implemented it, we couldn’t find any good working example of it.

I’m including some code snippets here in the article and I’ve put a complete footer bidding example online for you, too. Or you can see it all in production on our Ancient History Encyclopedia website (the source code is harder to follow though).

Let’s get started. First, we’re going to look at the steps you can take to make sure footer bidding’s benefits outweigh its drawbacks.

Page Load Speed

First, we need to make sure that our page loads quickly and that our window load event is executed as quickly as possible. To do that, we took the following steps:

Lazy-Load Images

Having images lazy-load as they come into view drastically improves your page load speed. We used the native loading=”lazy” HTML attribute on all our images, which is supported by most major browsers except for Safari. Our website doesn’t get lots of Safari traffic (and Safari monetizes badly), so that worked for us. 

<img loading=”lazy” src=”https://www.ancient.eu/img/c/p/360×202/11072.jpg?v=1566292535″ width=”360″ height=”202″ alt=”Egyptian Scribes” class=”ci_image “>

You can also use one of many JavaScript libraries to lazy-load your images, or use a service like Gumlet (which I can highly recommend).

Image Optimization

Not only do we lazy-load images, we also make sure they’re as small as possible. We used Gumlet to optimize our images and then store them on our own CDN (not on Gumlet’s CDN, which is their default). You could also use other services like Kraken.io. On WordPress, there’s also Smush.

Lazy-Load Embedded Content

We applied the same logic to embedded content, such as Youtube videos, Sketchfab 3D objects and other iFrames. There are many ready-made plugins out there (for jQuery and for WordPress), but we decided to build our own using the browser’s native intersectionObserver API, which runs faster than most older JS libraries. Here’s a nice helper function that we used:

function observedElementInView(elementSelector,callback,offSet) {

    var wHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

    var viewLazyLoadOffset = Math.round(wHeight * 0.75) || 1000;

    if (offSet > 0) {

        viewLazyLoadOffset = offSet;

    }

    const ioConfig = { rootMargin: viewLazyLoadOffset + ‘px’ };

    var observer = new IntersectionObserver(function(entries, self) {

        entries.forEach(entry => {

            if (entry.isIntersecting) {

                callback(entry.target);

                self.unobserve(entry.target);

            }

        });

    }, ioConfig);

    const elems = document.querySelectorAll(elementSelector);

    elems.forEach(elem => {

        observer.observe(elem);

    });

}

// And this is how you use it:

observedElementInView(‘[data-src]’,function(elem) {

    // do this when the element enters the viewport

    elem.setAttribute(‘src’,elem.getAttribute(‘data-src’));

    elem.removeAttribute(‘data-src’);

});

The idea here is to make sure that all your iFrame and Youtube embeds don’t have an “src” attribute, but instead a “data-src” attribute. The script will automatically move the “data-src” attribute into the “src” attribute when the element comes into view, thereby loading the iframe.

Defer Scripts & CSS

We also decided to defer the loading of some non-essential scripts until the page has fully loaded. Specifically, these were: Google Analytics, AdSense Page-Level Ads, Admiral (our anti-adblock solution that I can highly recommend), and a few others. Essentially, we moved everything we could until after the load event gets triggered. For CSS, we also put all the CSS concerning items far down the page (like the footer) into a separate CSS file that only loads after the page is fully loaded. You can either defer scripts using the native “defer” HTML attribute or you can defer them via JavaScript. We used a bit of both.

    <!– Stylesheets –>

    <link async rel=”stylesheet” type=”text/css” href=”/template/css/final/ahe.css” media=”all”>

    <link defer rel=”stylesheet” type=”text/css” href=”/template/css/exclude/deferred.css”>

    <link defer rel=”stylesheet” type=”text/css” href=”https://fonts.googleapis.com/css?family=Karla:400,700|Libre+Baskerville:400,400i,700&subset=latin-ext&display=swap”>

    <!– Post-Load Deferred Styles & Scripts –>

    <noscript id=”deferred-styles-and-scripts”>

        <link rel=”stylesheet” type=”text/css” defer href=”/template/css/exclude/print.css” media=”print”>

    </noscript>

 

<script>

        function loadDeferredScriptsAndStyles() {

            if (!window.deferredLoaded) {

                window.deferredLoaded = true;

 

                // Load Deferred Styles

                var addStylesNode = document.getElementById(“deferred-styles-and-scripts”);

                var replacement = document.createElement(“div”);

                replacement.innerHTML = addStylesNode.textContent;

                document.body.appendChild(replacement);

                addStylesNode.parentElement.removeChild(addStylesNode);

 

                // Other Deferred Scripts go here

                // Google Analytics, Anti-Adblock, etc…

        }

        $(document).ready(function() {

            // If it’s taking too long, go ahead and load deferred before window.load() event occurs.

            window.deferredLoadTimer = setTimeout(function() {

                loadDeferredScriptsAndStyles();

            },window.deferredTimeout);

        });

        $(window).load(function() {

            // Normal case: Load deferred after window.load() event.

            if (!window.deferredLoaded) {

                clearTimeout(window.deferredLoadTimer); // Remove slow connection load timeout.

                loadDeferredScriptsAndStyles();

            }

        });

        // END Deferred Javascript

</script>

 

Other Speed Improvements

Apart from the list above, it’s vital that you tap into all of the tools available to modern web developers. If you’re not using a CDN like Cloudflare (free plan available) or Fastly (not free), use one! Make sure your static assets are cached with the right HTTP headers (that goes beyond the scope of this article). 

We even went so far as to serve fully cached static HTML to users who aren’t logged in and sending logged-in users to a subdomain with caching turned off. Our HTML now loads lightning-fast to users who aren’t logged in (which is 99% of them).

Ad Loading

Preload, Prefetch & Preconnect

As we’re footer bidding, we are only running the ad auction once the page has loaded. When that happens, we want to be sure that it’s happening super quickly, so we want to be sure that all our ad code is loaded when it’s needed and that the user’s browser is already prepared to talk to our demand partners’ RTB endpoints. We preload, dns-prefetch and preconnect directives to load critical resources early:

  • In our HTTP headers we’re sending a preload directive to the browser to immediately load our core JavaScript functions (which include our ad code), even before the HTML is downloaded.
  • In our HTML header we’re sending preload directives for Google Ad Manager and Amazon TAM’s JS libraries.
  • We then have preconnect directives in our HTML to preconnect to all our header bidding partners’ RTB endpoints (you can find them using Chrome Devtools to watch network requests).

This way, we ensure that not only is everything ready for the auction when our page has loaded, but the browser has already initialized a connection to get the bids in as quickly as possible.

Here’s our HTTP header:

Link: </js/ahe-final.js?v=1589915222>; rel=preload; as=script, </ajax/ipcountry.js.php>; rel=preload; as=script, </template/css/final/ahe.css?v=1589915222>; rel=preload; as=style

And here are some of our HTML headers:

<link rel=”preload” as=”script” type=”text/javascript” href=”https://c.amazon-adsystem.com/aax2/apstag.js”>

<link rel=”preload” as=”script” type=”text/javascript” href=”https://c.amazon-adsystem.com/bao-csm/aps-comm/aps_csm.js”>

<link rel=”preload” as=”script” type=”text/javascript” href=”https://securepubads.g.doubleclick.net/tag/js/gpt.js”>

<link rel=”preload” as=”script” href=”/js/prebid-3.13.0.js?v=1589915222″>

<link rel=”preconnect” href=”https://securepubads.g.doubleclick.net”>

<link rel=”preconnect” href=”https://confiant-integrations.global.ssl.fastly.net”>

<link rel=”preconnect” href=”https://protected-by.clarium.io”>

<link rel=”preconnect” href=”https://raintwig.com”>

<link rel=”preconnect” href=”https://ap.lijit.com”>

<link rel=”preconnect” href=”https://ce.lijit.com”>

<link rel=”preconnect” href=”https://ib.adnxs.com”>

<link rel=”preconnect” href=”https://dmx.districtm.io”>

<link rel=”preconnect” href=”https://ad.doubleclick.net”>

<link rel=”preconnect” href=”https://tpc.googlesyndication.com”>

<link rel=”preconnect” href=”https://pagead2.googlesyndication.com”>

Load Timeout

It’s possible that due to a slow connection or a server issue the page doesn’t load super quickly. Maybe one image simply doesn’t load. In that case, our ad auction would never trigger—and we don’t want that. So we put in a timeout of several seconds starting from when the document ready event triggers. When the timeout is reached, the ad auction runs even if the page load even hasn’t fired yet.

            // Slow connection: Go ahead and run auction after a certain time even if window.load() event hasn’t occurred yet.

            $(document).ready(function() {

                window.adsLoadTimer = setTimeout(function() {

                    if (!window.prebidAuctionRun) {

                        window.prebidAuctionRun = true;

                        runPrebidAuction();

                    }

                },5000);

            });

            // Normal connection: Run auction when the window.load() event occurs.

            $(window).on(‘load’, function () {

                if (!window.prebidAuctionRun) {

                    window.prebidAuctionRun = true;

                    clearTimeout(window.adsLoadTimer);

                    runPrebidAuction();

                }

            });

 

CMP, CCPA & Tracking Pixels

Here’s one additional benefit to footer bidding: We can be pretty sure that our GDPR CMP, CCPA CMP (and any other acronym-based follies we are required to have) are loaded by the time our ads are fired. The same is true for tracking pixels, for example.

Our A/B Test Data

Switching from header bidding to footer bidding is a big deal, so we wanted to be sure that we’re doing it right. We ran two tests over 10 days to ensure that we weren’t hurting our revenue.

Revenue Impact

The first test took our existing ad setup and “simply” switched from header bidding to footer bidding. We noticed a small drop in revenue (about 1.2%) as there were fewer ad impressions per page. Most likely, this was due to some users leaving the page before ads loaded, possibly due to slow connections.

For the second test, we added two more demand partners to our stack. Now that bid requests were sent after the page had loaded, we could afford to call more of them. With those added in, we had a final result of a 0.5% increase in ad revenue.

Page Speed Impact

The big winner was page speed: Based on our data, our page load speed halved  (yes, that’s right, it halved) after we switched to footer bidding. That means people were seeing our page much sooner, able to engage with our content. Even with a small revenue loss, we’d have gone for footer bidding with these page speed results.

Further Improvements

The switch to footer bidding was a big win, but there’s more that can be done. Right now, we’re testing lazy-loading our ads in order to further speed up page load speed.

Additionally, we’re moving some demand over to server-to-server bidding using Amazon TAM. That’s worked well for us so far, and we’re doing more of it in the future.

Final Thoughts

Should you switch to footer bidding? Maybe. It really depends on your site: If you’ve got ads right at the top of your page, above the content, these might see a drastic drop in viewability. If your ads are more below the fold, within your content or on the side, footer bidding might be right for you.

If you’re running a WordPress site and you have very little control on when different parts of the page are loaded, then footer bidding might not be right for you. You can only do this if you have control of every element of your site.

Implementation is quite straightforward (provided you have dev resources) and doesn’t take long. Just make sure to A/B test it!