This post hopefully marks a bright new era for my blog. You might ask: “What’s the exciting news?” Well, I added a comment system to my blog. More than that, it is a fully custom solution that I have been working on for around one month now. I think it’s quite exciting, but perhaps you do not think so… yet.

Hopefully in this post I can give you an idea (1) why I think this is an exciting new era for my blog, (2) the rationale behind why I pursued the route of a “custom system” and, (3) a bit about the technical details of this project.

Table of Contents

Why Build A Comment System?

From my observations, it appears that much of the web is moving away from blogs. Furthermore, the blogs that I do come across increasingly lack a comment section. I suspect that this move away from comment sections can be attributed to the rise of statically generated websites (like my own), but I also see that many people look towards social media as a new forum for conversation.

Why did I decide to add a comment system at all?

In short:

  • I don’t need to rely on social media or email to have conversations with people about my blog posts.
  • I don’t want to exclude people wanting to be a part of the conversation.
    • Similar to the first reason, I feel as though being reliant on social media is a bit gatekeeper-y. Not everyone has an account on <insert social media platform here> and I might not want to join the platform that you are on!
  • I hope to own my own data (and content!) and not be beholden to the overlords of any single social platform.

The IndieWeb Wiki page for the “Publish (on your) Own Site, Syndicate Elsewhere” concept has a great section outlining some of the benefits to posting content on your website first, and then posting links or publishing that content elsewhere afterwards.

While that article centers around posting your content, and focuses less on conversation, I believe that a central comment system living on my blog is of a similar spirit. Why should the conversation live so far away from the original content?

Why Now?

I set a goal for myself to write more posts this year. So far, I have not been great at that, but part of what has been holding me back is that no one is able to reply to my posts. I want to have a conversation, not just shout into the void! In some posts I am even seeking feedback, but before the best I could do is ask people to “email me” or “reach out to me on <social media name>”. Both of those solutions suck!

In line with my goals this year, I hope to remove some personal barriers to entry to writing, like:

  • “Micro-blogging” – not every post needs to be a long, well-thought-out essay!
  • “Drafts folder” – I have had several posts sitting in a “drafts” state for a long time. Some for over 1 year! It would be great to have a “drafts” tag on my blog for those posts.
  • Ideas in Math/CS – “micro-papers”
    • I did not get into a graduate program this year and I’m worried about my ability to publish any academic papers once I get another full-time job. But perhaps I don’t need to worry so much! Posting about the (incomplete) ideas that I’ve been working through is a great way for people to see that I’m “still thinking [academically]” as well as what I’m thinking about.

Now that I have wrapped up this project, I hope to be posting (and communicating with you all) soon!

Why Not Use <X>?

There are many open-source, self-hosted comment systems out there (and here is a nice list). So why didn’t I use any of them?

Answer #1: They didn’t meet my requirements

For the comment system I envisioned deploying to this website, I had some design constraints that excluded nearly every other comment system out there:

  1. It shouldn’t require JavaScript.
    • This requirement alone excludes almost every existing comment system, especially the popular ones.
    • There are many reasons why one might not want to run JavaScript on your system. Maybe you’re privacy-focused and have JavaScript disabled at the browser level, or you’re using a browser extension like NoScript. Maybe your web browser doesn’t support JavaScript at all (like with lynx). I’ve been in all of those situations at some point in time, and it’s terrible when you can’t do basic tasks on the web without enabling JavaScript.
    • There is little reason why JavaScript should be necessary for a system like this! The web existed long before JavaScript did, and a comment system is nothing new. I want to remain accessible to people with arcane & archaic web browsers, as well as users of accessibility technologies that don’t play well with JS.
  2. Doesn’t require external authentication.
    • Authentication can be a barrier to entry for many people. Even if it’s possible, it’s inconvenient and will turn a casual reader off versus just being able to post a comment immediately.
    • Most external authentication providers would require JavaScript anyways.
  3. Static site compatible.
    • I did not want to change the core of my website – a statically generated website that is hostable on GitHub Pages.
    • Unless I embed comments via an iframe and/or JavaScript, this would require rebuilding the site for every new comment. Some systems, like Gitcus, support this, but they fail the “no external auth” and “no JavaScript” tests, in addition to being an ugly system.
  4. iframes might be bad, actually.
    • I did use iframes at some point in the development, and it was actually my original intent when designing this system. But I discovered several drawbacks to iframes with substandard “workarounds” necessary for making them pleasant.
    • If you want to make iframes somewhat pleasant to use, then you end up using quite a bit of JavaScript. Additionally, command-line browsers like lynx do not handle iframes gracefully enough to make it worth it.
    • Permalinks to comments (via a URL hash) are not possible without leveraging JavaScript. This means that for a “true permalink” I would either need to (1) rely on JavaScript or (2) send users to the external comment system.

Answer #2: Because I can hack on it

I can extend this new system anyway that I see fit in the future. Because I understand what I have written completely and I own my own data, it really opens up the options in terms of extendability.

For example, some future methods to post to this system that I want to add, and can add could be:

I can even go a little more crazy and add other ways of posting to this same system, like:

  • Email!
  • IRC?!
  • Telnet?!?!

Regardless, there are many benefits of knowing my code well and owning the (easy to use & extend) underlying database.

Technical Details

The Basic Architecture

The general architecture of the comment system is one that is decoupled from the static blog post (which you are on right now), and the static website as a whole.

When you navigate to the Post a New Comment link, you are brought to an “external” website hosted on comments.freddy.us where you can fill out, and later edit, a comment for the post that linked to it. That “comment page” is a PHP script that interfaces with a simple SQLite database to store your comments in.

A tiny architecture diagram
A tiny architecture diagram describing the flow of comments from Jekyll, to PHP, and back to Jekyll.

Comments aren’t shown until I have approved them. This makes sense from a comment moderation standpoint, but this assumption allows for one of the “tricks up the sleeve” of this system.

When I approve a comment, my moderation backend sends a message to a trigger which allows this Jekyll website to rebuild itself. The “trigger” could be an API call to the GitHub Action, which will rebuild and redeploy the website, or to a simple Ruby TCP Server, which rebuilds the website upon a connection.

Rebuilding a Jekyll website isn’t useful unless there is a way to fetch and display comments. To solve this issue, I wrote a custom Jekyll plugin that can read in a SQLite database or use a REST API to populate comment data which is then injected into the blog post HTML template.

How It Solves All of my Requirements

I went through all of this effort to design a custom comment system, but did I even achieve my goals? I did! But not necessarily in the way that I thought. I’ll go through each goal and discuss how they were solved.

Requirement 1: No (Dependence on) JavaScript

Outside my own moderation panel, there is no JavaScript used for the comment system on freddy.us. On comments.freddy.us, the only JavaScript that the user interacts with is a tiny snippet that allows for a confirmation to be displayed when you want to delete your comment. If you don’t use JavaScript, there is a graceful fallback for non-JS users that means that the functionality is still accessible.

So, I have achieved the requirement of being JS-free! ✅

Requirement 2: Doesn’t Require External Authentication

As you can verify for yourself, there is no authentication whatsoever on the comment posting system. Yes, I may see an uptick in spam, but the end users will not. Once I implement them, projects like go-away or Anubis may provide some minimal form of protection, and in the meantime I can still do “IP bans”.

There is the additional benefit of:

  1. It requires no trust (in me). You don’t need to trust me to keep your credentials safe, or to allow you access to your external accounts via an OAuth-like scheme.
  2. It removes a barrier to entry. You can just post a comment! It’s very simple. Try it!

For editing comments, the system keeps track of your recent comments (which have not yet been approved/rejected) in a session token, so as long as you’re still in your current browser session and the comment has not been approved/rejected, you should still be able to edit them via a “magic link”.

So, I have achieved the requirement of not requiring authentication! ✅

Requirement 3: Static Site Compatible

I designed this system with static site compatibility as one of its first principles. By decoupling the dynamic backend for posting a comment from the static frontend and exposing approved comments via an API, this system is better suited for a static website generation framework than a dynamic one. Furthermore, with this decoupling, it is easy to write plugins for generators other than Jekyll, e.g., Eleventy.

With static site generation as one of the first principles of this system, I have achieved that requirement! ✅

Requirement 4: iframes, and avoiding them

I had originally designed this project to leverage iframes as a means of interacting with the comment system backend. In this design, an iframe could be embedded in the blog post and you’d be able to post a comment without ever leaving the post.

After pursuing this option for a while, I encountered some technical limitations of this approach (discussed below), which made me reconsider that core design choice. I overcame this by creating a custom Jekyll plugin that directly injects the comment data into the webpage.

In practice, this is a great solution compared to iframes! Having the HTML of the comment directly embedded in the blog post improved accessibility, both for esoteric browsers and assistive technologies, but also allows for nice features like permalinks to individual comments through anchor links. Loading the page is also snappy, since you only need to load one webpage as opposed to two.

You will only see iframes in one part of this system: where you post a comment. They are not required in any way, but they allow the user to read the original post that they are commenting on without having to have it open in another tab.

I have achieved the requirement of not relying on iframes! ✅

Below, I outlined my journey to ditching iframes, but be warned: it goes into some technical details with a tone of slight aggression. If you want to skip this section and move onto the next, Source Code Availability, feel free to.

Warning: This (hidden) section contains some (minor) angst about iframes.

I had originally designed this project to leverage iframes as a means of interacting with the comment system backend. In this design, an iframe could be embedded in the blog post and you’d be able to post a comment without ever leaving the post.

While this sounded nice in theory, and even allowed for users without JavaScript(!), I encountered some “dealbreakers” after finishing its implementation. That’s right, I have a completely functioning version of an iframe embed. It is really that undesirable.

“Okay, I know iframes are ancient and not good, but really what’s so bad about them?” you might ask.

Well.

The issues I have with iframes in this context can be lumped into two categories:

  1. You’re probably going to end up using JavaScript.
  2. Accessibility is a nightmare (and you’ll end up using JavaScript anyways).

If you’ve ever worked with iframes before, then you know that they come in “all different shapes and sizes.” What I mean by that is: you need to specify the size of your iframes, and be happy with it. This means that either (1) you know the size of the content beforehand, (2) you’re happy with your content being squished, or (3) you use JavaScript to send a message from the child <iframe> to the parent specifying the dimensions of the webpage, and then the parent webpage resizes the child accordingly.

This was not the dealbreaker – that comes later. Because having a sane JS-less fallback was important for me, I settled on the approach to

  1. Set a reasonably large initial iframe size (for NoScript users).
  2. Leverage JavaScript to resize the iframe (as many others do).

Sure, okay, I did that. But then I realized that command-line web browser like lynx don’t support iframes. They do, however, have a fallback, which allows navigating to the embedded page directly or displaying fallback text that I would place inside the <iframe> . </iframe> tags. Okay… woof…

The real nail in the coffin for iframes was my realization that you could not use anchor links to create a link directly to a comment. If I want to say, “Hey, check out this comment,” without proper anchor/hash link support, nothing would happen. On the other hand, with support for that style of links, the page would be scrolling directly to the comment I linked when loaded.

One can fix this behavior, and I did. The only problem was (drum roll please!) you have to use JavaScript. The basic idea is to use cross-iframe messaging, similar to resizing iframes, except in a bidirectional manner. The procedure would go:

  1. Parent page notifies child iframe that it has received an anchor/hash link.
  2. The child iframe calculates where on its page the anchor link is. It then sends a message to the parent containing this location calculation.
  3. The parent computes, with respect to its size and the child iframe’s size, where in the webpage it needs to scroll.

That procedure is on top of what needs to be done to just resize the child iframe to fill the appropriate size. It’s pretty ugly and it really exemplified why iframes are a substandard solution when compared against actually injecting static HTML into a page.

Source Code Availability

I’m working on releasing the source code for both the Jekyll website & plugin, as well as the PHP backend. As of now, the code for freddy.us can be found on GitHub but it is possible that I migrate off of GitHub if I decide to stop hosting my website there.

As for the PHP source code, I will likely be hosting it on a personal Git server for the foreseeable future. For now, my Git server is invite-only, but I plan on making it publically accessible once I am able to get go-away (the anti-scraper tool) fully set up.

Leave a comment with your email address filled out (wooh!) and I’ll let you know when it’s live! (Just a reminder, your email address is visible only to me and is not public.)

Thanks for reading!