My blog engine now speaks Bluesky too

2026-08-044 min readBloggingEngine UpdatesWebDevOSSTransparency

Last time I wrote about the engine I ended by saying I'd be watching to see if anyone actually replied to that first toot. People did, which was nice. Now, after confirming that the idea works in principle I thought "why not expand" which is why I added the other distributed social protocol with traction: Bluesky. So, here's what I did about it, plus the rest of what changed since the last engine update.

Comments from two networks, in one thread

The comment section under a post now pulls replies from Mastodon and Bluesky and blends them into a single list, sorted by time. One thread, in the order people actually said things. Each comment carries a small badge telling you which network it came from, and clicking through takes you to the original.

You can reply from either side. Both land in the same place.

I went back and forth on whether to keep them visually separate and decided against it. Two half-empty comment sections look like a dead blog twice over, and more importantly the conversation genuinely is one conversation - somebody on Bluesky is often reacting to the same sentence as somebody on Mastodon, they just don't know it. Merging them is the honest representation.

The mechanics are the same as before: no comment database, no third-party widget, nothing loads until you click. Neither network needs you to have an account to read the replies, which is the part that makes this work at all. Bluesky's posts come through as plain text, so there's not even any HTML to sanitize on that side - the engine builds the links itself from the metadata rather than trusting anything.

Moderation carries over too. There's still a small file where I can hide a reply, and it now takes IDs from both networks in the same list. Hiding one still hides whatever hangs off it, because a reply continuing a removed reply quotes it by implication.

Search that runs in your browser

The site used to have a Google search box on it. That seemed to be at odds with what else I'd been doing, so it's gone.

What replaced it is a search index the build generates from every post - title, tags, summary, and the full text - which your browser downloads the first time you type in the box and then searches locally. No hosted search service, no query of yours leaving the page, nothing to log. It's a fairly dumb ranking (title beats tags beats summary beats body, and every word has to appear somewhere) but for a personal blog that's plenty.

The pictures got a lot smaller

I was uploading photos straight out of Lightroom, which meant some posts were shipping four megabyte images to render at 800 pixels wide. The build now downscales anything oversized and re-encodes photographic PNGs as JPEG, which on some of the older photo posts cut the page weight by most of it.

It's deliberately timid about this. Screenshots, diagrams, anything with transparency, anything already small - all left alone, because those are exactly the images where a JPEG re-encode makes things worse. And a conversion never deletes the original, so if it ever gets a call wrong I change one line in the Markdown and it's back.

While I was in there: if I put two or three images on consecutive lines, they now render as a row that reflows on mobile instead of a vertical stack you have to scroll past.

Dark mode, and some smaller things

There's a theme toggle in the header now, cycling between following your system, forced light, and forced dark. The theme resolves before the page paints, so you don't get that white flash on a dark screen. While doing that I also pulled the accent colour out of my own avatar, so the site and the face at the top of it stopped clashing.

The rest, quickly: posts show an estimated reading time. Every tag has its own RSS feed, so you can follow just the photography and skip the engine posts. Articles moved to /posts/, tags to /tags/, with redirects so nothing anyone linked to broke. Off-site links open in a new tab. Tags can carry an emoji. And the favicon is real artwork now rather than an emoji in an SVG, because it turns out Safari won't draw those and the tab was simply blank on every iPhone - which I'd never noticed, being an Android person.

Deploys are encrypted now too. They used to be plain FTP, which is embarrassing to type out but was true.

The engine stopped being only mine

The less visible work, and maybe the more interesting: everything personal to me has been pulled out of the code.

The site's name, my handles, the footer text, how many posts to a page - all of that lives in one config file now. Every prompt the models get sits in a separate file as plain text, so changing what the AI is told doesn't mean touching Python. My drafts, my voice notes, and my credentials aren't in the repository at all.

The thing that pushed me here was realising that if someone forked the engine, the parts most likely to keep publishing under my name were the ones nobody sees - the author field buried in the structured data, the alt text on the social preview card. Those are now config, and the README is written for someone who isn't me.

So it's a thing another person could run without much archaeology. Whether anyone will is another question, but the option exists.

This post is the first one the engine will announce to both networks at the same time. If you're reading a reply below with a Bluesky badge on it, then it worked.

Comments

Replies come from Mastodon and Bluesky. Reply on Mastodon or Bluesky to join in.