I never got around to implementing a comment system on this blog. After migrating from Wordpress to Jekyll and Github Pages (a static site) it was never someting that really interested me enough to spend time on. I briefly looked into a client side comment systems like Disqus but never actually took it any further than the research phase. I’m glad about that considering the hit Disqus has on web performance and issues data ownership. Things like:
- loading takes place from multiple different domains
- poor cache optimisation for loaded content
- you don’t own the comments, they are stored on a third-party server
There’s an excellent post here by Nicolas Hoizey who goes into these issues and others in much more detail if you’d like to know more. So no Disqus comments. What’s the alternative?
I must admit I hadn’t heard of Webmentions until fairly recently, but once I did I thought they sounded very interesting. So I’ve spent the past few weeks looking into implementing them on this blog. So what are they you may ask? Well I’ll let the W3C specification explain that:
Webmention is a simple way to notify any URL when you mention it on your site. From the receiver’s perspective, it’s a way to request notifications when other sites mention it.
Essentially they are quite similar to the WordPress Pingback and Trackback systems, which allowed WordPress blogs to talk to each other. For example, a blog post written on one WordPress blog could tell another that it had linked to it using these systems. Unfortunately the systems were hijacked by spammers resulting in its decline. Great idea, ruined by a certain demographic of users on the web. This is why we can’t have nice things…
The first W3C Public Working Draft was published on 12 January 2016 by Aaron Parecki, and it has grown in popularity ever since. Webmention offers a number of advantages over Pingbacks and Trackbacks:
- it uses HTTP and
x-www-form-urlencodedcontent, a much more widely accepted format compared to XMLRPC used by PingBacks and Trackbacks
- faster and easier to integrate due to use of these accepted formats
- W3C standard, rather than a proprietary system (bundled with WordPress)
- spam protection using the Vouch protocol
- human readable format rather than a simple title and an ellipsed summary as was the case with Pingbacks
As mentioned before I use Jekyll via GitHub pages to compile a set of posts and templates into static HTML pages. It works well, but a major issue with GH pages is that you can’t use custom plugins. You can only use those listed on this page. That means jekyll-webmention_io isn’t an option, unless I plan to completely change my workflow. This isn’t something I’m willing to do at the moment as I plan to migrate to 11ty at some point in the future. I will leave that large change until then. So I decided to look into a client-side only alternative.
This is what I wanted from my implementation:
- progressive enhancement
- minimal impact on web performance
- no external dependencies and minimal impact on the build pipeline
I’ll go into each of these points individually.
Minimal impact on web performance
IntersectionObserver API comes in handy. It’s an API that allows you to read the position of a DOM element relative to the top-level viewport. It is often used to lazy-load images and scripts. Only once a user reaches a particular point in the page will an image load or script execute. For this blog I’ve set the bottom of a post to be the point where the script is loaded and executed.
There’s a small
app.js file (1.1KB minified, 686 bytes gzipped) that does a few things. It performs a very simplistic version of “cutting the mustard”, by testing the availability of the
IntersectionObserver API. If the browser passes, there is then a test to see if certain ES6 (ES2015) features are supported (more on that later). Assuming the browser passes both tests, the loading of the
webmentions.js script is primed (6.4KB minified, 2.1KB gzipped). If a user now scrolls to the bottom of the page, the
webmentions.js script is injected in to the page and it executes. This in turn loads all the Webmentions that the post has (if any).
webmentions.js script has a couple of performance considerations too. All the templates for the replies, likes, and reposts are contained within this file, rather than using a
<template> tag. This means that these templates are only loaded if a users browser passes both tests, and the user scrolls to the bottom of the page. This gives a slight saving in initial HTML size for all users, no matter what browser they use.
The second consideration is related to the profile images that are retrieved for each Webmention. By default, each of these profile images is large in terms of physical dimensions and page weight. Now considering we could be loading quite a few of these profile images, and they are only displayed at thumbnail size, that’s a lot of pixels and data that will be loaded that isn’t needed. To solve this I’ve made use of the Cloudinary free tier. The Cloudinary API allows you to send an image URL to their servers, which then responds back with an optimised (and resized) image for use on a page. These optimised images are cached on their server once generated, so image manipulation / optimisation only occurs once.
No external dependencies and minimal impact on build pipeline
IntersectionObserver test and the ES6 (ES2015) test.
Show me the code already!
I’m sure you’re bored of my ramblings by now and all you want is the code. So here it is:
The code contains lots of comments and links to sources where I have copy & pasted others code (isn’t open source great!). You can see it in action if you scroll to the bottom of this page on WebPageTest waterfall charts. If you spot any improvements or idiotic errors in the code (or this post), either leave a comment on the gist or send me a Webmention! I’m using Bridgy, so a mention on Twitter should work too.