A simple commenting system for my static site generator
Contents
Inspired by some of my favorite blogs, and in search of my next funemployment project (July ‘23 update: I’m back at Google), I’ve decided to experiment with enabling comments on this site.
I’m not sure I’ll keep it around, but it’s been a fun project, so I thought I’d record some thoughts on my goals and the technical and design decisions I made along the way in case anyone else using a static site generator is looking for inspiration on how to add comments to their own site.
Goal
Since reviving this blog a bit, I’ve gotten a lot of valuable feedback, particularly on the Cynical PM Framework, and had some great discussions with really thoughtful PMs.
My goal is to lower the friction for that discussion and to nudge it into public to encourage more folks to participate.
Requirements
First and foremost, any commenting system I’d adopt or build would need to respect the privacy of my visitors. There are no trackers on my site — there’s barely any JavaScript at all — and I use only Netlify’s privacy-respecting, server-side analytics. I had no plans to change that stance.
Second, I wanted the experience to be simple and responsive, not requiring 3rd-party authentication or user account creation of any form. I wanted it to look simple, to perfectly (not just mostly) nestle into the design of this site. And I didn’t want it to have a meaningful impact on page load speed, which is a priority for me, and which I take pride in.
Finally, because I want to keep the quality of discussion high, I wanted a degree of moderation capability, if not some flavor of spam filtering built in.
Technical decisions
Static-site native vs. externally hosted
From the outset, I knew I had a critical decision to make. Since this site is built with Lektor, the commenting system needed to play nice with a static site generator.
There are two categories of static site-generator-friendly commenting systems. The first is a static site-native solution that somehow adds comments to a git repository, triggering a rebuild, and, after a short delay, republishing the site with the new comment. The second is to use an externally-hosted solution, typically instantiated on the site with JavaScript.
Despite really wanting to try my hand at a static site-native solution (I’ve been having a lot of fun building Lektor plugins), I ended up selecting the latter category because I could see a clearer path to a finished product sooner. My goals, this time around at least, did not include learning new ways of building software. I just wanted to ship something.
Externally hosted options
Before writing a commenting system myself, I had a mild preference to find something awesome off the shelf. Spoiler: I did not, but I did evaluate two options. Here’s what I found.
Disqus
Lektor’s official Disqus Plugin was highly tempting. Disqus is well-known and familiar, even among non-tech folk, its UI is reasonably clean, and it would’ve been trivial to install. It also has built-in spam filtering and a polished moderation dashboard.
But Disqus is not made for tiny, independent, privacy-focused websites. Disqus’ customers are large publishers who want to “Monetize engagement” and “Engage their audience.” Trackers do not respect the privacy of my readers and they slow down my site. Disqus was a no-go.
Hyvor Talk
I hadn’t previously heard of Hyvor Talk but checked it out after reading about it, among other commenting options, on Average Linux User.
Hyvor Talk checks the privacy box, is fully-featured (I love the Reactions feature), and has a reasonably (although not completely) customizable UI. It costs $12/month, which wasn’t a dealbreaker. In fact, $12/month is the cost of a small Heroku app. (This may be foreshadowing.) I don’t love that it requires users to log in to use most of its interesting features.
The more I investigated various options, the more I realized I’d have the most fun and deliver the best solution by building something myself rather than wrangling some existing solution, hosted or not, into the shape I wanted.
How it works
At this stage in my life, much to the dismay of my engineer friends, I have a strong aversion to learning new frameworks and programming languages unless they’re a necessary means to an end. So I built this commenting system in Rails and vanilla JavaScript.
Basic comment operations work simply: submissions and fetches include the post’s path as a key. (This post’s key, for example, is /blog/introducing-comments/
.)
New comments aren’t publicly visible right away — they go into a moderation queue. Initially, this raised an interesting design problem: because I’m not requiring commenters to log in, if a commenter refreshes the page, their comment apparently disappears into the ether. I wanted to make sure that, as long as they came back using the same browser, they’d still see their pending comment.
To solve this, after the comment is submitted, the comment system sends a randomly generated ID back to the browser, stored in localStorage, that the frontend can use on subsequent requests to get unapproved comments for a given post.
The frontend is ~175 lines of JavaScript, about 5.5kb, embedded directly in the page. (View source on this page to see it and mock me for my amateurish code.) I figured it’d be nice to avoid incurring the additional network call, and it made it trivial to render page-level variables inside my JavaScript code using Lektor template tags.
Everything’s hosted in Heroku, for which I pay $12/month. I think I’d prefer it to be a bit cheaper, but I can stand the cost if it means I don’t have to get a certification in DevOps to keep this thing running.
What's next?
Although the comment system does support moderation, I expect the spam bots to eventually catch on and begin bombarding the form, which is not yet rate limited, with requests.
All CAPTCHAs are terrible, so I’ve been reading a bit about various spam-filtering options on the backend, and Akismet, a pay-what-you-can service, looks reasonably simple to integrate.
I also really like Hyvor Talk’s reactions feature, which is a more sophisticated version of the “Wave” feature I used to have on this blog. I can imagine adding something like that but have some thinking to do about how to support it without requiring visitors to log in.
With that, all that’s left is for you, dear reader, to comment! Looking forward to hearing from you.