Back to All Posts

How to Lazy Load Disqus for Improved Site Performance

I’ve been a vocal opponent of using Disqus for a number of reasons, one of which being what it does to the performance of your website. It’s actually one of the reasons I built an alternative.

Despite that, it’s a widely used service that won’t be going away anytime soon. And if people are going to continue to rely on it, there’s at least one thing they should do to help soften that performance hit: lazy load it. Let it wreak havoc on the user only when the user is actually going to see it.

This one change can have a hefty impact on metrics like overall page load time, the amount of time the main thread of the browser is blocked, and how much code you require your readers (specifically those that don’t even reach your comments) to download, parse, and execute. And it’s rather simple to implement.

How a Typical Implementation Falls Short

By default, setting up Disqus requires including a bit of HTML and some inline JavaScript. It’s not much:

<div id="disqus_thread"></div>

<script>
    var disqus_config = function () {
	    this.page.url = PAGE_URL;
		  this.page.identifier = PAGE_IDENTIFIER;
    };

    (function() {
    var d = document, s = d.createElement('script');
    s.src = 'https://EXAMPLE.disqus.com/embed.js';
    s.setAttribute('data-timestamp', +new Date());
    (d.head || d.body).appendChild(s);
    })();
</script>

But that <script> tag shouldn’t be underestimated. When it executes, it loads and executes a lot of code. And what’s particularly bad is that it happens even if your user doesn’t scroll down to the part of the page where any of it’s used.

Initialize When in View

In order help users only pay that cost only when necessary, the IntersectionObserver API is a great one to leverage. Using it, we can set up an “observer” and tell it to fire some code only when an “intersection” occurs between two elements. In this case, the intersection we’re concerned with is when the root HTML element where Disqus will be rendered meets the browser’s viewport. So, let’s stub some things out.

The first step is to set up the observer itself.

const observer = new IntersectionObserver((entries, observer) => {
	entries.forEach((entry) => {
		if (entry.isIntersecting) {
			console.log("Initializing Disqus!");
		}
	});
});

// Start listening:
const mountNode = document.querySelector("#disqus_thread");
observer.observe(mountNode);

When this is loaded onto a page, we wouldn’t see that log until the #disqus_thread element came into the browser’s viewport. It’s worth noting that if we wanted to, we could configure this to fire when the element came within a certain number of pixels of the viewport. That’s probably a good thing to consider for a production site, but for the sake of simplicity, I’m leaving things more basic here.

Next, we can run the code necessary for initializing Disqus on our target element. There’s nothing custom going on here. We’re literally just copying in the code Disqus gives you to embed from the beginning. It’s just that it won’t be executed until the mount element is in view.

const observer = new IntersectionObserver((entries, observer) => {
	entries.forEach((entry) => {
		if (entry.isIntersecting) {
			console.log("Initializing Disqus!");

			// Starting Disqus's universal embed code.
			var disqus_config = function () {
				this.page.url = PAGE_URL;
				this.page.identifier = PAGE_IDENTIFIER;
			};

			(function() {
				var d = document, s = d.createElement('script');
				s.src = 'https://EXAMPLE.disqus.com/embed.js';
				s.setAttribute('data-timestamp', +new Date());
				(d.head || d.body).appendChild(s);
			})();
			// Ending Disqus's universal embed code.
		}
	});
});

// Start listening:
const mountNode = document.querySelector("#disqus_thread");
observer.observe(mountNode);

Important! Destroy the Observer After Triggering

That gets the job mostly done, but we’re missing a critical piece. As soon as we’ve initialized Disqus, we need to destroy the observer we set up. If we don’t, Disqus will be reinitialized every time another intersection is made, which would occur anytime the user scrolls away from the mount element and back to it once again.

const observer = new IntersectionObserver((entries, observer) => {
	entries.forEach((entry) => {
		if (entry.isIntersecting) {
			console.log("Initializing Disqus!");

			// Starting Disqus's universal embed code.
			var disqus_config = function () {
				this.page.url = PAGE_URL;
				this.page.identifier = PAGE_IDENTIFIER;
			};

			(function() {
				var d = document, s = d.createElement('script');
				s.src = 'https://EXAMPLE.disqus.com/embed.js';
				s.setAttribute('data-timestamp', +new Date());
				(d.head || d.body).appendChild(s);
			})();
			// Ending Disqus's universal embed code.

+			// Stop observing to prevent reinitializing Disqus.
+			observer.unobserve(entry.target);
		}
	});
});

// Start listening:
const mountNode = document.querySelector("#disqus_thread");
observer.observe(mountNode);

And with that, we’re set. Disqus won’t download or execute any of its JavaScript until it’s necessary:

The Impact

Initializing Disqus lazily means far fewer requests to handle and much less code to execute on page load for a good chunk of users. Here’s the number of requests a sample page was handling before lazy loading. It’s significant, especially if your users are on low-end mobile devices with limited data availability.

After, however, things look much better (until the user scrolls to where comments are placed, of course).

It’s a Bandage

It’s important to render that an approach like this is nothing more than a bandage on an open wound. There’s still a big cost to pay if you choose to use Disqus, albeit not monetary. It’s just that the cost will now only be realized for those who interact with your comments.

Without question, the best path forward is to pivot to a more performant comment service. But for the time being, consider an approach like this to minimize the impact to your users and maybe make your site a little more enjoyable to visit.


Alex MacArthur is a software engineer working for Dave Ramsey in Nashville-ish, TN.
Soli Deo gloria.

Get irregular emails about new posts or projects.

No spam. Unsubscribe whenever.
Leave a Free Comment

2 comments
  • Tom Scott

    I hate using 3rd party software on my websites for the reasons you allude to: you have no control. For instance, I have a "switch" on many web pages to test for mobile. I then make some modifications if the test evaluates to true--increasing font sizes, for one. There's simply no way I could find to do this in 2023, and Google search console penalizes you for it on mobile devices saying "font too small"
    Very Best Regards,
    Tom Scott
    Author ● Speaker ● World's Leading Expert on the Corrupt U.S. Legal System
    www.stloiyf.com


  • Mike

    I did something slightly similar a few years ago, as loading Disqus severely impacted my load time.
    My approach was to load nothing until scroll start, at which point only an activation button is embedded.
    Upon click, the activation button has a small animation which helps to psychologically disguise the time taken for the Disqus files to load.
    Here's an example: https://websemantics.uk/tools/responsive-font-calculator/
    The method has the advantage of retaining a point of contact via Disqus, while having it hidden reduced the amount of spam.


    2 replies
    • The question is, is Google still indexing your comments?

      From the looks of it, it seems the answer is no. That is the one big downside to Disqus; you choose between Google indexing your comments and impacted load time / page experience, or fast load time and Google not seeing your comments at all, which sort of defeats the purpose.


    • Alex

      Oh... I like that approach (and nice sound effect lol). That way, the only cost paid is by those who actually intend to leave a comment (not everyone who scrolls down to a particular part of the page). Nice.