Making Non-Blocking Advertising Work
A couple of weeks back I spent some time building an advert for one of our company's websites. In doing so I learnt a lot about ad servers and how to make them work better, and shave precious seconds off load time in the process.
One of the biggest annoyances about internet advertising is the adservers that deliver them are deliberately crummy when it comes to the way they return the ads. I guess this is to help prevent fraud, but it has the counter effect of making most websites that rely on advertising really slow and limited in design.
So I'm going to share with you my findings and how they might help you.
Sample code now on Github: https://github.com/wildtangent/ajax-adtech-multi-ads
Disclaimer - please check your ad network's terms and conditions regarding asynchronously loading ads, they might not like it. Also be sure to check your metrics before and after to see if they've been adversely affected by the changes.
Why doesn't AJAX loading of ads work anyway?
The main reason for this is document.write
. An annoying JavaScript function which expects to return its results directly to the page it is shown on. This has the effect of splatting your whole page if you load it through a callback. So the first piece of the puzzle is to get rid of document.write
.
I've tried this a few times before but simply overriding the document.write
and document.writeln
methods just doesn't work properly.
After hunting through the AdTech documentation, I discovered that they have a horrendous bit of JavaScript for doing Async ad tags, which uses a library called writeCapture. Looking into this I discovered it is over 4 years old and effectively abandoned. The replacement for it is a great library called postscribe and this is what I'm going to use to make our async/AJAX ads behave themselves.
https://github.com/krux/postscribe
Getting started
Firstly, you need to include the postscribe and htmlParser in your project. Depending on how you're going to use the library, you may want to put it in the <head>
, but it is better if you can keep all your JavaScript in the footer below the <body>
tag for further performance:
<script type="text/javascript src="/javascripts/htmlParser.js></script>
<script type="text/javascript src="/javascripts/postscribe.js"></script>
Replacing your ad tags with placeholders
Now, replace your evil ad tags with placeholder divs, giving each an ID so you can refer to it later. if you need to give more configuration you could add some data attributes but we'll keep it simple for now:
<div id="ad_1"></div>
Loading your ads later on
Create a JavaScript file and refer to it in the footer of your page. If you're using an asset packager, e.g, Sprockets, just add the file to your manifest as usual.
Refer to each advert slot, inserting the tag you'd normally place inline. Note I'm putting this in a jQuery document.ready
function closure, but this shouldn't technically be needed if the JS file is in the footer anyway, or if you're not using jQuery. Still it is best to encapsulate the code to isolate it from the rest of the page scripts. A tip that ad server developers clearly have never learnt!
I'm going to describe a few different ways of doing this. Some will be specific to AdTech but there will likely be similar ways to achieve the same result on your ad server.
$(function(){
postscribe("#ad_1", '<script>if (window.adgroupid == undefined) { window.adgroupid = Math.round(Math.random() * 1000); };document.write('<scr' + 'ipt src="http://adserver.adtech.de/addyn/3.0/1299.1/3743479/0/225/ADTECH;sub1=whitelines_new;loc=100;target=_blank;size=728x90;grp='+window.adgroupid+';misc='+new Date().getTime()+'"></scr' + 'ipt>');}<\/script>');
}
Firstly, if i missed a bracket or quote in there, sorry, this isn't taken from production code, but you get the idea, just paste your ad tag into the second argument of the postscribe method. if you get problems with your script tags escaping too early, make sure the ones you're writing are split up or escaped .
This is pretty ugly though and not strictly asynchronous. But we have moved the advert load from being inline to being at the end of the document, so at least if the adserver takes agest to reply then the user can still get on with using the site (after all, that is why they are there, right?)
Multi Ad Tag
It turns out another gem hidden in the AdTech docs is the multi ad tag. There is definitely one of these for OpenX as well, so it seems to be common practice among adservers to implement such a thing.
What this does is reduce the number of adserver calls down to just one for the page, since it returns all the ads at once, wrapped up in functions which can be called later on in your page.
This is already a big win if you're loading 4 or 5 ads on a page, but a lot of those tags write more and more nested script tag calls so it is still not wonderful
Enter postscribe again
Let's change that ugly JavaScript to something more like this:
<script type="text/javascript">
if (window.adgroupid == undefined) {
window.adgroupid = Math.round(Math.random() * 1000);
}
document.write('<scr'+'ipt type="text/javascript" src="http://adserver.adtech.de/multiad/3.0/25/0/0/-1/ADTECH;mode=multiad;plcids=2154536,2130496,2130497;loc=100;target=_blank;grp='+window.adgroupid+';key=key1+key2+key3+key4;sub1=[subst];cookie=info;misc='+new Date().getTime()+'"></scri'+'pt>');
</script>
$(function(){
postscribe("#ad_1", "<script>ADTECH_showAd(2130496, document, false);</script>");
}
Better! Now we can do all that at the end of the page, save some time and escaping things all over the place.
Still not convinced? Behold the JSON Multi Tag!
So that's all well and good but what if we want to reload ads periodically, or load them into new slots as the page loads, e.g. infinite scrolling, new AJAX content? Since the multi tag has already yielded an actual ad tag, we'd just be getting the same ones over and over.
Also we're still blocking, loading the multi ad tag, albeit at the end of the page where noone really cares.
So here's hopefully the final piece.
Let's deconstruct that multi ad tag script and make it a bit more sane:
if (window.adgroupid == undefined) {
window.adgroupid = Math.round(Math.random() * 1000);
}
I guess we need this bit, something global for the adserver to understand which request it was (cachebusting and the like).
document.write(......);
Bleugh! lets get rid of this right away
var adRequestUrl = 'http://adserver.adtech.de/multiad/3.0/25/0/0/-1/ADTECH;mode=json;cors=yes;plcids=2154536,2130496,2130497;loc=100;target=_blank;grp='+window.adgroupid+';key=key1+key2+key3+key4;sub1=[subst];cookie=info;misc='+new Date().getTime();
While we're at it, we've changed the mode=
parameter from multiad
to json
.
EDIT: We've also added the cors=yes
parameter, which makes AdTech return CORS preflight headers so we can make crossdomain requests.
Of course, you'll need to update this with your own options, placement IDs etc
Take a moment to test this in your browser to make sure it is right, and to see how the json rendering mode differs from the multiad mode
Great, now let's turn this into an ASYNC request, using a smattering of jQuery, and use the callbacks to insert the ads
$(function(){
var loadAds = function(){
if (window.adgroupid == undefined) {
window.adgroupid = Math.round(Math.random() * 1000);
}
var adRequestUrl = "your JSON multi ad tag";
$.ajax({
url: adRequestUrl,
dataType: "json",
method: "get",
cache: false,
crossdomain: true
success: function(data, status, xhr){
$.each(data.ADTECH_MultiAd, function(i, item){
var adSlot = "#ad_" + (i + 1);
var adTag = "<script>" + item.Ad.AdCode + "</script>"
postscribe(adSlot, adTag, {
beforeWrite: function(str){
$(adSlot).html("");
return str;
}
});
})
}
})
loadAds();
}
And that's it, really. Of course, you'll probably want to do some more logic about how to choose which ad for which slot, and you get more useful information off the JSON, such as the reported creative size, which you can use to determine this.
And the great thing about this is you can call this whenever you like, so if you want to reload the ads every few minutes the user is idle, load units as they come into view, load new units into slots which only come available after the user interacts with something, etc... then do it!
One last thing, don't try this with Google Adsense, or you'll get banned tout suite! But if you're running your own network, then this is perfect.
Written by Joe Connor
Related protips
5 Responses
Joe,
Thank you very much for this useful tutorial. Thanks to it I could implement responsive Adtech tags very easily!
I did as you described and am getting the following error message which as far as I know comes from the generated script by adtech:
Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.
I am implementing the postscribe into my AngularJS app.
Hi, I have started to see this too - i think that Chrome has started blocking all document.writes itself if they are loaded async. Haven't investigated any further yet though do let me know if you find out why
Recently got hold of AdTech's DAC.js library - if you're an AdTech user you might want to get this as it wraps up most of the code above in an AdTech-approved, documented setup. Unfortunately, it doesn't do the JSON response which was the best thing about this whole setup, and the multiad tag still doesn't support AdTech marketplace.
But the benefits of ASYNC loading go further than this - reloading content asynchronously, with ad reloads has added 5 million pageviews/month to my current platform
Fantastic solution, saved me heaps of times. Thanks.