👨‍💻 Wesley Moore

Building and Launching My New Link Blog, linkedlist.org (Twice)

I’ve started a new tech focused link blog over at linkedlist.org. “Not another tech blog”, I hear you groan, and rightly so. However my intention is not to cover topics that are already well reported upon like Apple, Google, Microsoft, the latest drama at OpenAI, and other stuff like that. Instead, I plan to focus more open-source, programming, hardware, software, Linux, Rust, retro computing etc. There’s some more details in the welcome post.

In this post I’m going to cover the process I took to the build the site (twice) and some of the considerations that went into it—for a site with only a handful of pages there was a surprising amount of them.

When building Linked List my reference was Daring Fireball by John Gruber, which I’ve enjoyed reading for close to two decades now (aside from some of the recent takes on the EU DMA). The site is simple on the surface but it’s clear that John has put a bunch of thought into a number of different aspects of the site.

The high-level things I wanted were:

The First Build

I initially toyed with the idea of using micro.blog to host the site as it can host blogs, micro or not, and has a lot of built-in support for cross-posting to services like Mastodon. Ultimately I decided it wasn’t quite the right fit for what I was aiming for.

Concluding I’d need to build it myself I reached for my go-to static-site-compiler: Zola. As part of the micro.blog experiment I took a liking to their Alpine theme, which as it was open-source I used as the base style for the Zola site.

My first hurdle was that I wanted a particular URL hierarchy:

linkedlist.org/YYYY/MM/DD/post-slug

This proved a challenge to achieve with Zola. There was an open issue about this sort of structure, which led me https://github.com/scouten/ericscouten.travel. I was able to replicate the approach used there to achieve what I wanted.

Creating a new post was a bit involved though as it required creating each of the intermediate directories if they didn’t exist, as well as an _index.md file with particular content in each newly created directory. I automated this with a Ruby script, which was also able to pre-populate the front matter for a new link post.

Cross-posting to Mastodon

For cross-posting I wanted to use the site’s feed as the source and have the tool post newly published items. There are dozens of these RSS to Mastodon tools on GitHub. I evaluated 16 of them against a handful of requirements:

No runtime[2]

This ruled out the vast majority, as many were written in Python or JavaScript/TypeScript. I don’t want to have to deal with operating tools using these languages as I find handling their dependencies and breakage due to upgrades annoying.

Conditional Requests

It feels like table stakes that a tool that is polling a HTTP resource will make conditional requests using headers like If-Modified-Since or If-None-Match so that it will only be fetched and processed if modified. The vast majority of the tools I evaluated just fetched the feed every time they polled it though.

Robust Against Duplicate Posts

I want to reduce the possibility of something causing a flood of posts or duplicates. Some of the tools I evaluated did alright here, but many did not.


None felt like they covered all these, so I took the code I had written for the Read Rust tooter and reworked it to use a feed as the data source instead of a database. I spent a fair bit of time making it support multiple feeds and feed formats, as well multiple posting targets. I plan to open-source it in the future.

I protect against duplicate posts and post floods by:

Before the scope creep in the cross-posting tool occurred I added a JSON Feed to Linked List as these was a bit easier to consume than the Atom feed. Only problem was Zola didn’t support JSON Feed. I solved this with a jaq script described in my previous post.

With the cross-posting tool mostly feature complete I deployed it to a Raspberry Pi Zero W, running on my desk. Every 15 mins cron fires it up to check for, and post new items in the feed.

Polish

There was a long tail of smaller things that I implemented before the initial launch, such as:

Launch & Rewrite

Finally, on 11 October I soft-launched the site with a post on Mastodon. Over the next couple of weeks I published posts to the site, eventually realising I’d never added pagination to the home page. I went to add it that weekend and discovered that it was going to be very difficult owing to how I’d achieved the URL hierarchy with Zola.

Zola had got the site up, but I took this (as well as some of the earlier friction) as a sign it was time for a custom approach. Over the next few evenings and some weekend time I rewrote it in Rust using Axum as the HTTP server layer.

Since things were more under my control now I took the opportunity to:

I also had to do a bit of extra work to support things you get for free in a Zola/static site:

On 30 October I deployed the new version of the site. It renders the same Markdown files as the Zola site, so publishing new posts is still just rsyncing the files.

The Markdown content is loaded from disk at start up and rendered out of RAM. It doesn’t currently cache the rendered result, but most responses are generated in 1ms or less anyway. A filesystem watcher is used to notice when the files are changed and automatically reload them.

With the rewrite out of the way I now have more time for more regular posting to the site, I hope you’ll follow along.


  1. Gruber actually calls the collection of link posts on Daring Fireball the Linked List, although I was only tangentially aware of this when I embarked on this project. My main motivation was that I liked the cross-over with the data structure and that I already had the linkedlist.org domain, originally registering it in 2011.

  2. I.e. a native binary that can be run without having to install an interpreter or similar first.

Stay in touch!

Follow me on the ⁂ Fediverse, subscribe to the feed, or send me an email.