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 alot 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.
Top comments (0)