Ignoring good advice and building my own blog (again)

For various reasons, I stopped blogging about ten years ago.

I ended up redirecting the energy into coding, and occasionally writing posts for the blogs of companies I worked for. But after Barricade was acquired by Sophos in 2016 I stopped writing almost entirely.

My current company, Clearword, is winding down and I'm lucky enough that I don't have to line up any work immediately, so I've decided to start writing again – and build some software along the way.

It's kind of a classic developer mistake to build a blog before they start writing. As Jeff Triplett writes:

"Write and publish before you write your own static site generator or perfect blogging platform. We have lost billions of good writers to this side quest because they spend all their time working on the platform instead of writing."

This is good advice. I am just preternaturally incapable of following it.

lolscorpion.jpg

The first time I build any significant software was when I cobbled together my own blogging system in college. It was styled with tables and rounded corner gifs and stitched together with PHP 4:

cult-redbrick.png
Mad skillz.

After college I used Wordpress, Blogspot and Medium as I moved away from writing web apps and into managing web infrastructure and writing backend services. For me, writing my own blogging system again ~20 years later feels like coming home.

#Anti-goals

When I was jotting down ideas, I put some of these under the heading "anti-goals" – stuff I wanted to consciously avoid.

I didn't want to use a hosted third-party platform. I've gone down that route a couple of times now, and the results have always been unsatisfactory.

I also wanted to explicitly avoid building anything that was throwing up newsletter popups, social logins, invasive analytics scripts, engagement tricks, progress bars and the inevitable creep of AI writing assistance.

On Ghost

One thing that I missed entirely in my first survey was Ghost. I remember looking into it a few years ago and not liking it for some (probably inane) reason. It's marketed as a professional publishing platform, with subscriber content, newsletters, etc., none of which is what I want. However, I ended up checking it out after I'd built a good fraction of my own blog and found they'd made similar choices to me, which means I think they are pragmatic and have excellent taste 🧑‍🍳.

Something else I wanted to avoid was writing everything in Markdown and committing to git. There are understandable reasons why many developers choose it – they do not (as a rule) like running databases. Markdown is text, and git is basically a database for text without all the hassle of a live process and SQL.

The idea of writing long-form English posts in Markdown is just anathema to me, even as a developer. It's barely a step above writing BBCode (shots fired!) and feels like giving up too much of the experience of writing. Fortunately this is the web, and we can all make different choices 🙂

What I do want out of my blog is still a work in progress. Partly what I want from it is for it to be a work in progress, not a publishing platform. I've spent the last fifteen years building software for other people, and I'm keen to get back to building software that is just for myself. Not that I won't share parts of it when I think I have something useful to share. However, I don't want to become a blogging platform maintainer. I don't want users. I don't want customers.

#Dual stack

Maybe it sounds obvious, but a blog is two software stacks – one for reading, and one for writing. It's much more obvious with static site generators, since you are perhaps using a text editor to write your posts, committing to git, and then the rest is taken up by a build process in Github, which creates the HTML, CSS, etc.

I only really considered this when I started thinking about what I wanted from a blog, and two stand out goals were a rich Notion-like authoring experience, but a fast, cacheable, low-Javascript experience for readers. These goals seemed to be in conflict.

Application developers build for multiple audiences all the time, but when you're building a B2B SaaS app that may just mean API consumers and logged in humans. Blogs are interesting because the largest visual component of the system should appear very similar for both sets of users, it's just that the author also needs a bunch of bells and whistles for managing the text.

For me, the ideal writing experience sits somewhere between the minimalism of Medium and the digital Swiss Army Knife documentation authoring of Notion (sans the slightly too in-your-face AI bits).

#The next social web

I used to be very online, and specifically very online on Twitter. That has waned in recent years, but I'm interested in where things are going with Mastodon and Bluesky. I have a preference for Mastodon, partly because Bluesky has more visible US political drama, and partly because I have an anarchist streak in me that wants to see social media unbundled.

As a result, I want to make the blog have a native understanding of at least ActivityPub. I love the idea that my blog could be followed on Mastodon (still working on this). It will probably be a little complicated to provide a legible variant of the posts, but perhaps not much more complicated than Atom/RSS.

Speaking of which, I also wanted to provide an Atom/RSS feed, even though I'm not convinced there's a large audience. I always wanted a nicely styled, standards-compliant feed though, so it's there.

#Passkeys

I follow Ricky Mondello on Mastodon, and they have a nuanced perspective on password managers and WebAuthn. As a result, passkeys were unusually present in my thoughts when I was building the system, and so I included passkey support using the excellent (if search-resistant) Better-Auth library.

Better-Auth has support for passkeys, but last I checked the user must first register with a password, which means passkeys are only a second-factor – not ideal. I haven't checked whether this can be easily patched yet, though it's on my list.

#Experiments and fun

I also wanted to have some fun, muck around and get deep into various parts of the stack. There's folk wisdom amongst software developers that code is a liability, but knowledge is an asset. The idea is that code requires maintenance, and the more of it you have the harder it is to manage effectively. It may even become an impediment to building what you need in the future. However, what you learn writing that code can have compounding value, making for better developers and better teams.

That's not to suggest that I'm going to religiously account for every tangent as a "learning opportunity" – I'm my only real audience here. I don't have to attend a standup and sheepishly tell people I spent a day or two making a kaleidoscopic rainbow button on a whim before discarding it as a bad idea. Or that while mucking around with SVG animations I accidentally created a noisy "O" effect on my 404 page and kept it because it reminds me of the Holy Grail from Fate/stay night.

A lot of the original idea was just writing down developer experience goals like "I want to style everything with Tailwind" or "I want to use shadcn for editor components" and then doggedly pursuing those goals in the face of all headwinds just because it's an aesthetic choice I like.

#Technical choices

I'm in two minds about whether to even write these down at this stage, but maybe a quick overview is informative about where I started versus where I ended up? No blanket technological judgements intended 🙂

It's odd to realize this, but as someone with a long history in operations, operational simplicity was not an explicit goal when I began – I was initially more interested in how quickly I could put it together.
I think that's reflective of the last half a year or mad dash building Oggam.

I started with Next.js and Supabase because that's what I picked for Oggam, and I found them to be very powerful as someone who has not done much frontend dev for the last 15 years. Ultimately, though, after a couple of weeks I switched out to Astro and SQLite.

I had started asking myself if the complexity of the Supabase self-hosted stack was something I actually needed for something as architecturally simple as a blog, and I felt Astro would give me more flexibility and control over the HTML that was being served. I'm also entertaining the idea that I can use Astro's static site building capabilities to regularly produce an entirely static failover/backup for the live version of the system. That would be pretty amazing.

On Supabase

I would pick Supabase again in a heartbeat if I were building another SaaS application. There are some things I would change in their stack around Edge Functions (the hard CPU time limits are frustrating to work around), but I was very lucky to have picked this just based on a handful of Hacker News mentions and a YC podcast.

If you embrace RLS and Edge Functions you can produce some sophisticated behaviours while keeping infrastructure costs very low.

I cycled through several text editors (TipTap, PlateJS, BlockNote) for authoring, but found that many of them required payment for the functionality I wanted, or were too opinionated, focused on collaborative editing, or looked tricky to make modifications.

I've been drawn to Lexical since I first found it, though since it's pitched as a framework for building editors I thought it would be overkill. I'd been steering clear of it in the name of keeping things easy, but in the end, a framework is actually what I needed.
Definitely a bigger investment of effort than the other editors, but has enabled me to be as idiosyncratic as I wanted in my choices, including rewriting chunks of it to use Tailwind and shadcn.

#Deploying

I'm not particularly misty-eyed over the days when hosting meant buying a server and hauling it into a rack myself, but there is something primordially right about being able to run everything on a single server, and copy files around.

At the moment the whole thing is deployed on a $6 Hetzner VPS via Github Actions, managed via Docker Compose and running behind nginx and Cloudflare. To me, this is a simple, powerful setup. With some tricks, it even allows me to get maybe-zero-noticeable-downtime deployments 😏

You can configure Docker compose to do rolling deploys (!):

    healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4321/health"]
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
deploy:
replicas: 1
update_config:
order: start-first
failure_action: rollback
delay: 0s
stop_signal: SIGTERM
stop_grace_period: 60s
logging:
options:
max-size: "10m"
max-file: "3"

However it did still result in 5-10 seconds or so of visible Origin Unreachable errors from Cloudflare. Since this is a blog though, not a complex multi-user web app, I can cheat a little by getting nginx to serve "stale" cached content for the posts while the app backend is down:

        # Enable caching globally
proxy_cache cache;
# Cache validity (adjust as needed)
proxy_cache_valid 200 301 302 10m;

# Serve stale content if upstream is down
# This does not serve stale if upstream is healthy.
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

There are still plenty of bugs, but the bugs are all of my own creation, and most of them affect only me, so it's kind of great?


There is currently no comments system. If you'd like to share an opinion either with me or about this post, please feel free to do so with me either via email ([email protected]) on Mastodon (@[email protected]) or even on Hacker News.