Rescuing Landing Pages from a mass placeholder-image-loading catastrophe

SanfordWhiteman
Level 10 - Community Moderator
Level 10 - Community Moderator

When you think about images and page load time, you typically think about large images — that is, “large” in terms of bytes, not necessarily dimensions — especially over mobile connections.

 

Developers don’t think much about total image load failures from a performance standpoint, assuming an image that isn’t available will return a 404 or 500 error immediately. Indeed, that’s true almost all the time.

 

So we don’t usually worry about image requests hanging for an extended period before failing, and almost never consider hidden images at all!

 

But ignoring rare-but-not-impossible cases is dangerous.

 

Take the client who was using placeholder images, via placeholder.com, throughout their Landing Page Templates:

<img src="https://placeholder.com/300x300">

 

On a good day, that returns the helpful image:

image-1[1].png

 

Those were placed inside Marketo editable areas that could be switched on and off using mktoBoolean variables.

 

But “on” and “off” are misnomers. In fact, the variable doesn’t switch the image off in any real way. You might have a <div> whose display style depends on the variable, like:

<div style="display: ${displayContainer1};">
  <img src="https://placeholder.com/300x300">
</div>

So you end up with either

<div style="display: block;">
  <img src="https://placeholder.com/300x300">
</div>

or

<div style="display: none;">
  <img src="https://placeholder.com/300x300">
</div>

when Marketo outputs the value.

 

But the image URL is still loaded in both of those cases. This is even true if the display property is set right on the <img> tag:

<img style="display:none;" src="https://placeholder.com/300x300">

 

And, as with all <img> tags, the page is not considered to have fully loaded until that image has either completely failed or completely succeeded. While it’s in process, the page is still loading.

 

If you’re waiting for load, you’re waiting for images

The fact that the load event won’t fire on the window object until all images are complete might not be a problem on your site. But it certainly was a problem for this client, since they used window.onload to trigger all the stuff that made the page usable (buttons clickable, forms submittable, etc.), e.g.:

  window.onload = function(){
    setUpCriticalPageBehaviors();
  };

 

Then the perfect storm happened: placeholder.com’s main site broke, so this URL no longer works:

https://placeholder.com/300x300​

But instead of returning an error immediately, it took on the order of 7-10 seconds:

2022-04-15-20_50_18-Database-Error[1].png

 

Before finally returning an HTTP 500 and a database error:

2022-04-15-20_52_15-Database-Error[1].png(Of course, that database error would never be seen by the end user because it’s in an <img> tag, but you can see it by opening the image URL in its own tab.)

 

So what happens? All of their Landing Pages — and I do mean all, since they had at least one placeholder in each — were taking ~10 seconds to be usable.

 

JavaScript — and a browser quirk — to the rescue

Leaping into action, we have 2 rules:

  1. 1. We can’tfind and remove those <img> tags on every one of hundreds of pages.
  2. 2. We can add a <script> tag to the template and reapprove every page.

 

The solution is this code in the <head>:

document.addEventListener("DOMContentLoaded",function(e){
  const arrayify = getSelection.call.bind([].slice);
    
  arrayify(document.images)
    .filter(function(imgTag){
        let srcLoc = document.createElement("a");
        srcLoc.href = imgTag.src;
        return srcLoc.hostname == "placeholder.com";
    })
    .forEach(function(placeholderImgTag){
        placeholderImgTag.src="";
    });
});

 

This works because

  1. 1. DOMContentLoaded fires when the <img> tags have been parsed, but doesn’t wait for the image assets to be downloaded/decoded/error out.
  2. 2. An <img> tag is considered complete if the src value is the empty string, including if you change the src to an empty string on-the-fly.

 

Coming soon...

A better way to hide unused modules, not display: none;, that doesn’t waste resources/impede performance.

903
2
2 Comments
Dave_Roberts
Level 10

FWIW, I've found placeholder.com to be must less reliable than fpoimg.com for serving placeholder images. The more "cutesy" the placeholder (I've seen kittens and puppies and random images too) the less reliable the images/service seem to be in my experience as well. It's all the same if any of these break, but a small improvement here might be to use a different placeholder service.


Another work-around here might be to download the placeholder images you use on the page and load those into Marketo and reference them from there instead. I've done this a few times and it usually only ends up being a handful (less than ten) of similarly sized images that you need to build out any given layout. Maybe worth the add'l effort for clients that  depend so heavily on "onLoad" scripting to do fancy things on the page. 


I'm interested to see the follow up to this one:

Coming soon...

A better way to hide unused modules, not display: none;, that doesn’t waste resources/impede performance.


Thanks again for posting this kind of stuff out to the community, I've learned so much for these posts along the way!

SanfordWhiteman
Level 10 - Community Moderator

FWIW, I've found placeholder.com to be must less reliable than fpoimg.com for serving placeholder images


Could be! I use gifpng.com usually. But when the placeholder service is built into the template (for example, the Summit template from K—k uses placeholder.com) that doesn’t help.

 

Agreed that downloading the FPO images in advance is safest. But again won’t help people who have LPs already running with the bad placeholders.

 

I think you’ll like the new unused module suppression method. Takes a (one-time) creation of some small-but-tricky {{my.tokens}} but the result is sleek.