Medium, we had fun, but you've changed. A beautiful reading experience tarnished by paywalls, advertisements, and popovers. I want my content back on my own platform.

Owning my own platform gives me freedom. Freedom to tweak the code and eek out those extra milliseconds of performance. Freedom to add whatever features I want, and remove those that I don't. But most of all, the freedom of knowing it won't ever change or disappear, unless I want it to.

It does come with a few downsides. The biggest two being discoverability and collaboration. I can write all the posts I want, but no one will see them unless they actively look for them.

With a couple of extra additions to this site, I may have solved these problems. I just had to use something old, something new, something borrowed, and something blue.

Something old

Back in the good-old-days we all had our own blogs. People were still able to discover our posts by using RSS feeds. You could subscribe to the RSS feeds of you favourite blogs and your RSS reader would collate them all. It's a bit like Podcasts. You subscribe to the stations you want to listen to. Then your podcast player displays all the new podcasts from your favourite stations. In fact, it's exactly like podcasts. Because RSS is what they use to manage this.

Unfortunately, using RSS for blogging died off a little in recent years. But as more and more people want to own their own data again, they're regaining popularity.

So, in the sprit of boosting RSS back to the top, I created a feed for this blog. This site is running on Gridsome which made it easy to add an RSS feed. All I needed to do was hook up gridsome-plugin-rss

module.exports = {
  plugins: [{
    use: "gridsome-plugin-rss",
    options: {
      contentTypeName: "SanityPost",
      latest: true,
      feedOptions: {
        title: SITE_NAME,
        feed_url: `${GRIDSOME_BASE_PATH}/rss.xml`,
        site_url: GRIDSOME_BASE_PATH
      },
      feedItemOptions: node => ({
        title: node.title,
        description: node.description,
        url: `${GRIDSOME_BASE_PATH}/wrote/${node.slug.current}`,
        date: node.publishedAt
      })
    }
  }]
};

I dropped in the default config, tweaked it to match my own site, ran yarn build to give it a test, and success! Sort of. The good news was, the feed was there and all my posts were in it. The bad news was, they were in a completely random order. After a little digging I realised they were all alphabetised on their UUIDs. A lovely order, but an entirely useless one. This was because gridsome-plugin-css sorted posts by node.date and my Sanity implementation doesn't have a date field. It's a little more granular and has _createdAt, _updatedAt, and publishedAt. The one I wanted was publishedAt, but there was no way to tell the plugin this information. Yet.

The best thing about open source is that everyone can contribute. I found a problem with the plugin and took it upon myself to fix it. A couple of changes to the codebase and I could pass in dateField: "publishedAt" which orders our posts by the correct date field. Joy!

The RSS feed is up-and-running. It's nothing fancy, but it gets the job done.

Something New

The best posts are the ones that spark a conversation. But how do we allow those discussions on a static site? I could add Disquss, or use FaaS (Functions as a Service) to let people post comments. But it's not quite what I want. Usually, I prefer using the boring solution. But this time I went for the shiny option. It's my platform, I'll do what I want.

The shiny solution here is a new standard called Webmentions. In essence, they're a list of all the places that "mentioned" your post. If you Tweet out your post on Twitter, that's a mention. If someone responds to that Tweet, that's a mention. If someone likes, or retweets that Tweet, that's a mention too. Then all you do is take that list, filter it, and render it to your site as "comments". This isn't just Twitter either. It works for other sites too, but for simplicity, I'll stick with Twitter for now.

Getting this list wasn't too hard. I followed the instructions on Max Böck's post to the letter. If you want them on your site too, I'd start there.

Once they were set up, I had to integrate them into Gridsome. Unfortunately, Max uses eleventy, so I could only use his post as guidance. There are also no Gridsome plugins for Webmentions yet, so I had to go alone on this one.

If you want to see exactly how I did it, I'd recommend looking at the Merge Request (that's what we call Pull Requests at GitLab). This whole blog is public, so you can dig in and have a proper look at the code. If you want the cliff notes, the whole thing was done in three steps:

1. Add the Webmentions feed into the Gridsome API

This is probably the most complex part. I needed to take the API provided by webmention.io and expose it on the GraphQL endpoint in Gridsome. This was done by adding the following code to gridsome.server.js

const axios = require("axios");

const API_ORIGIN = "https://webmention.io/api/mentions.jf2";
const { GRIDSOME_WEBMENTIONS_TOKEN } = process.env;

module.exports = function(api) {
  api.loadSource(async store => {
    const { data } = await axios.get(API_ORIGIN, {
      params: { token: GRIDSOME_WEBMENTIONS_TOKEN }
    });

    const mentions = store.addCollection("mentions");

    for (const item of data.children) {
      mentions.addNode(item);
    }
  });
};

2. Pull the mentions into a PostComments.vue component

Once the Webmentions data was available on the GraphQL endpoint, I could pull it into the project. Webmentions collates, likes, retweets, mentions, and replies. But I'm only interested in replies for now so I filtered everything else out using wm_property.

query {
  comments: allMentions (sortBy:"published" filter: { 
    wm_property: { eq: "in-reply-to" }
  }) {
    edges {
      node {
        id
        author {
          name
          photo
          url
        }
        content {
          text
        }
        published(format: "MMMM Do, YYYY")
        url
        wm_target
      }
    }
  }
}

Unfortunately, Gridsome isn't able to pass javascript variables into GraphQL queries just yet. Which means I couldn't filter the mentions by post. This query returns all the mentions for all the posts. To get around this, I requested wm_target which gave me the url to the post linked to that specific mention. All I needed to do was filter out the ones I didn't need after I loaded the mentions. Then I had a nice, clean array of "comments" that I could loop over and render as I please.

computed: {
  comments() {
    return (
      this.$static.comments &&
      this.$static.comments.edges
        .filter(comment => comment.wm_target.match(this.postSlug))
        .map(comment => comment.node)
    );
  }
}

3. Re-build the site when new comments are posted

Because the comments get baked in at build-time, new mentions won't be rendered until I re-build the site. All I had to do there was set up a new pipeline trigger and pass the webhook URL to webmention.io. Whenever I get a new mention, webmention.io calls the webhook, GitLab triggers a build, and my site is redeployed. There's a potential 10-30 minute delay, but it's a small price to pay. Besides, the conversation is happening on Twitter, I'm just re-rendering it on this site.

Something Borrowed

The RSS feed is a great first step towards discoverability, but I want to get my posts in front of a bigger audience. This is something Medium gave me for free and one of the main reasons I switched to using it. But there is another way.

Dev.to is a similar platform to Medium, but with a lot of the junk removed. If I didn't want to own my own data, this would be the platform I'd use. But just because it's not my primary platform, doesn't mean I can't borrow it when I want to. Dev.to can automatically cross-post your items to their platform. All you have to do is pass it your RSS feed. It's a good job we've just added one of those!

If you're reading this post on Dev.to, then this process worked!

Something Blue

I'll be honest, this is where the analogy falls down. How about a lovely, blue footer?

I'm just getting back into writing again. I hope to continue to add little improvements to this blog and write about them as I go. If you end out using any of these techniques on your own sites, let me know!

Max Böck - October 7th, 2019

awesome, thanks for writing that!

Sam Beckham - October 7th, 2019

Thank you for introducing me to them 🙂 Also: It works!

Joe // 康乔 - October 7th, 2019

Nice post! I read all the way to bottom wondering what the “something blue” would be... to be fair I wasn’t disappointed! :)

Sam Beckham - October 7th, 2019

Haha, the "something blue" was originally Twitter, but it felt weird jumping right back to Webmentions again. I've got to say though, these are working much better than I originally anticipated. I'm rather impressed.