How to lazy load Outbrain widgets

Tim Hanlon

Outbrain widgets are typically placed directly beneath the body of an article, so you don't want it to be fetching data while the page is loading – which is the default behavior.

We recently implemented lazy loading of Outbrain widgets for one of our publishing clients, and saw a noticeable improvement in how quickly pages loaded (and Lighthouse scores) as a result.

Here's the sample implementation from Outbrain's Implementation Guide, which is most likely what you're currently using:

<div class="OUTBRAIN" data-src="DROP_PERMALINK_HERE" data-widget-id="WIDGET_ID"></div>
<script type="text/javascript" async="async" src="https://widgets.outbrain.com/outbrain.js"></script>

The mechanism we're going to use is found in Outbrain's Dynamic Widget Loading documentation, however Outbrain leaves the implementation details up to you.

The flow is:

  1. Detect that the reader is approaching the end of the article
  2. Dynamically insert a div into the DOM for Outbrain
  3. Call the OBR.extern.researchWidget() method from Outbrain's tag

We're going to use the Intersection Observer API to handle step one in a performant way. This API has been supported by all major browsers since early 2019.

First, we'll replace the div from the sample implementation with a container, which we'll observe with our Intersection Observer:

<div class="outbrain-container"></div>

The full Intersection Observer implementation looks like this:

<script>
  let obLazy = {};

  // replace these values with the ones from your existing Outbrain code
  obLazy.widgetId = 'WIDGET_ID';
  obLazy.obTemplate = 'TEMPLATE';

  // a reference to the container div
  obLazy.container = document.querySelector('.outbrain-container');

  // the options for our Intersection Observer
  obLazy.options = {
    rootMargin: '100px', // fire when the container div is 100px from the viewport
    threshold: 1.0
  }

  // the callback which runs when the container intersects
  obLazy.callback = (entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        // create the Outbrain div
        let obDiv = document.createElement('div');
        obDiv.classList.add('OUTBRAIN');
        obDiv.setAttribute('data-src', document.URL);
        obDiv.setAttribute('data-widget-id', obLazy.widgetId);
        obDiv.setAttribute('data-ob-template', obLazy.obTemplate);

        // add the Outbrain div to the container
        obLazy.container.appendChild(obDiv);

        // tell Outbrain to look for the div
        OBR.extern.researchWidget();

        // stop observing the container
        obLazy.observer.unobserve(obLazy.target);
      }
    });
  };

  // create the Intersection Observer
  obLazy.observer = new IntersectionObserver(obLazy.callback, obLazy.options);

  // observe the container div
  obLazy.observer.observe(obLazy.container);
</script>
© twofutures Pty Ltd 2020