35.1 Disqus: The Classic Hugo Comment Integration
Right, let’s talk about Disqus. It’s the comment system you love to hate, or maybe just hate. But for a long time, it was the only game in town for adding dynamic, managed comments to a static Hugo site without building your own backend from scratch. It’s the digital equivalent of a well-worn, slightly uncomfortable pub stool—it’s been there forever, it does the job, and everyone knows where to find it, even if the upholstery is a bit suspect.
The core idea is simple, and honestly, a little brilliant: you offload the entire commenting nightmare—spam, moderation, user accounts, real-time updates—to a third-party service. Your static site stays static. You just embed a little bit of JavaScript magic, and Disqus handles the rest. It’s a classic trade-off: you get functionality and zero server maintenance in exchange for handing over your user data and saddling your site with a third-party script.
The Basic Integration: A Script and a Hope
Integrating Disqus into a Hugo site is straightforward. It’s so straightforward, in fact, that the designers of the internet have given us approximately seventeen different ways to do it. We’re going to use the best one: Hugo’s built-in internal template.
First, you need a Disqus account and, crucially, a “shortname.” This is the unique identifier for your site on their platform. It’s like your comment’s neighborhood. Once you have that (let’s say yours is my-awesome-blog-123), you add it to your Hugo site’s configuration. In your config.toml (or .yaml/.json):
disqusShortname = "my-awesome-blog-123"
Now, in your single page template (typically layouts/_default/single.html), you need to tell Hugo where to inject the comments. You place this where you want the thread to appear, usually after the article content.
{{ if .Site.DisqusShortname }}
{{ template "_internal/disqus.html" . }}
{{ end }}
And that’s… it. Hugo’s internal template generates all the necessary HTML and JavaScript. On your live site, a Disqus thread will appear, magically tied to the unique identifier of each post (using its .Permalink).
Why This Works (And Why It’s a Bit Cursed)
The magic is in the disqus_config variable the template generates. It uses the current page’s permalink and identifier to tell the Disqus script exactly which thread to load. This is rock solid. It works.
The “cursed” part is what happens next. That Disqus script is a beast. It loads a small universe of other scripts, styles, and tracking pixels. Your pristine, lightning-fast static site now has to go out, hat in hand, and beg Disqus’s servers for all its junk. This will block rendering. It will affect your Google PageSpeed Insights score. It will make your site feel slower. There’s no sugarcoating it. You’re trading performance for convenience.
Taking Control: The Partial Workaround
If the default template is too blunt an instrument for you (and it often is), you can create your own partial. This gives you control over the placement and the styling. Create a file at layouts/partials/disqus.html:
<div id="disqus_thread" class="my-comments-container"></div>
<script>
var disqus_config = function () {
this.page.url = '{{ .Permalink }}';
this.page.identifier = '{{ .File.UniqueID }}'; // A more stable ID than .Permalink
this.page.title = '{{ .Title }}';
};
(function() {
var d = document, s = d.createElement('script');
s.src = 'https://{{ .Site.DisqusShortname }}.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the comments.</noscript>
Why is this better? A few reasons. First, you can control the surrounding HTML for styling. Second, using .File.UniqueID is more robust than relying on the permalink; if you change your URL structure, old comments won’t suddenly vanish because the identifier remains the same. And third, it forces you to understand what’s actually happening, which is always a win.
The Development Gotcha (You Will Fall For This)
Here’s the pitfall that has claimed more victims than a sarlacc pit: Disqus will not load in your local localhost:1313 development environment. The Disqus script checks the URL of the page and, seeing it’s not a registered domain for your shortname, refuses to work. You’ll just see a blank space.
This drives people insane. They’ll tweak their config for an hour, convinced they’ve broken everything. You haven’t. It’s a feature, not a bug (a security feature, to be precise). To test it, you have two options:
- Build your site (
hugo) and serve thepublicdirectory with a local web server that can mimic a real domain (e.g., usingpython -m http.serverand modifying your/etc/hostsfile). This is a pain. - The pro move: Use Hugo’s built-in “future date” trick. Set the
disqusShortnamein your config only if the site is being built for production. You can do this with an environment variable.
# In your terminal, when building for production
HUGO_ENV=production hugo
# In config.toml
disqusShortname = "my-awesome-blog-123"
[context.production]
disqusShortname = "my-awesome-blog-123"
Now, when you run hugo server locally, disqusShortname will be empty and the template won’t even try to load the broken script. When you build for production, it will populate. Sanity preserved.
So, should you use Disqus in 2024? If your primary goal is to have a familiar, low-maintenance comment system quickly and you’re willing to accept the performance and privacy trade-offs, it’s still a valid, if slightly dated, choice. Just go in with your eyes open. It’s not the shiny new thing, but it’s a thing that works, and sometimes that’s enough.