I added ActivityPub to this blog — Hong Minhee on Things
July 16, 2026I added ActivityPub to this blog
I built this blog with Jikji, a static site generator I wrote myself,<br>almost five years ago. Back then I barely knew TypeScript or modern web<br>tooling, and I'd never implemented ActivityPub. TypeScript and modern<br>web tooling are second nature to me now, and ActivityPub has become<br>central to my work. I maintain Fedify, for whatever that's worth, and<br>it bothered me that my own blog wasn't federated. So I fixed that.
The old stack: Jikji and PHP
This blog used to run on Jikji, a static site generator I wrote myself in<br>Deno. Calling it a static site generator is a bit of a stretch, though.<br>Like old Movable Type installations, it didn't just produce HTML; it<br>generated a bit of PHP too. That PHP existed almost entirely for HTTP<br>content negotiation: it read the browser's Accept-Language header and<br>chose among Korean mixed script, hangul-only Korean, English, and<br>Japanese. That's all it did.
I first considered adding a thin ActivityPub implementation directly in<br>PHP, since I was already using it. But I wasn't really writing that PHP by<br>hand; Jikji generated it for me, and I had no interest in hand-coding PHP<br>myself. Federating meant delivering a Create(Article) activity to<br>followers whenever a new post went up, which meant I'd need something like<br>a message queue. Bolting a message queue onto Jikji's generated PHP felt,<br>to me at least, like more complexity than it was worth maintaining. And<br>honestly, with Fedify already around, I had no desire to implement<br>ActivityPub from scratch again.
So I ripped out PHP entirely and decided to bring in Fedify instead.
The new stack: Astro and Netlify
The first decision was to drop Jikji and PHP for Astro, a JavaScript<br>framework built for static-content-heavy sites. I chose Astro largely<br>because it already had a @fedify/astro integration.
I reused as much of the existing CSS and HTML as I could. I'm happy with<br>the current design, and redoing it alongside everything else felt like<br>scope creep waiting to happen. Permalinks stayed exactly as they were.<br>I wanted to replace the stack underneath without visitors noticing<br>anything had changed at all.
For hosting, I went back and forth between Cloudflare Workers and Netlify,<br>and settled on Netlify partly because Fedify had never run there before,<br>and this seemed like a good excuse to add that support. I've hosted<br>static sites on Netlify plenty of times, but this was my first time<br>pairing it with edge functions. The idea of a mostly static site with a<br>few dynamic slices reminded me of the late-nineties web, when a site was<br>static HTML except for whatever lived in /cgi-bin/.
Publishing used to mean committing a Markdown file to Git, pushing, letting<br>GitHub Actions build the static site, and deploying it over SFTP. Now<br>GitHub Actions is out of the build pipeline entirely, since Netlify builds<br>the site itself. It ended up simpler overall.
I'm happy with Astro, and the migration went smoothly. It beats Jikji,<br>which I'd barely touched since building it five years ago. Jikji is now<br>archived; there's no reason left for me to keep maintaining it.
Fitting Fedify into Astro
Updating @fedify/astro
Once I actually tried to add Fedify to Astro, I ran into a problem:<br>@fedify/astro didn't support Astro 7, the current version. The Astro APIs<br>it relied on hadn't changed much internally, but the package's declared<br>compatibility range, and its tests, only went up to Astro 5. So before I<br>could federate the blog, I had to fix @fedify/astro first.
That meant more than widening a version range. The existing tests built a<br>fake Astro context and called the middleware directly, which couldn't<br>catch problems with Vite's SSR configuration, compatibility across<br>adapters, or request routing on a built server. So I wrote new<br>compatibility tests that pack @fedify/astro for real, install it into a<br>small Astro app, build and start the app, and send real HTTP requests to<br>it.
Those tests check, across Astro 5, 6, and 7, that HTML requests reach<br>Astro's pages, that ActivityPub and WebFinger requests are handled by<br>Fedify, and that Astro's 404 Not Found still applies to everything else.<br>For Astro 7 specifically, I also run the tests against Deno and Bun, not<br>just the Node.js adapter.
That work has already been merged upstream and will ship in<br>Fedify 2.4.0.
Static pages, dynamic endpoints
The Astro project as a whole builds with server output, but the existing<br>blog pages are still prerendered, same as before. WebFinger, the actor,<br>the inbox and outbox, the followers collection, and ActivityPub objects<br>are the exceptions: Fedify handles those dynamically, per request. The<br>middleware @fedify/astro provides looks at a request's URL and Accept<br>header and only intercepts what Fedify is meant to handle. The same URL<br>can return the existing Astro page for an HTML request and a Fedify-built<br>object for an ActivityPub one.
What visitors see is still, for all...