Having put the blog on Cloudflare Pages and R2, the next itch was to stop it being an island. A static site is a lovely thing to own, but on its own it just sits there: no "someone replied", no way for anyone to follow it without an RSS reader they probably don't have. The IndieWeb answer to that is a handful of small, open standards, and colophon speaks them. This is what I turned on, roughly in order of how much faff each one is.
One note on scope before we start: colophon does the whole loop now, sending these interactions, collecting them, and rendering the replies under your posts. So this isn't plumbing for a someday feature; by the end the conversation actually shows up on the page. The order of operations is what matters, and a couple of the steps are easy to get subtly wrong.
WebSub: real-time feeds, no account
The cheapest win. A normal RSS/Atom feed is polled, so a reader finds your new post whenever it
next gets round to checking. WebSub flips that: you ping a hub
on publish, the hub pushes to every subscriber at once. Pure config, no signup, in colophon.yaml
under your site:
federation:
feeds: [rss, atom, json]
websub:
hubs:
- https://pubsubhubbub.appspot.com/ # Google's public hub
The ping only fires on a real public deploy, not a local build,WEBSUB … ping ok, and confirm there's a rel="hub" link in rss.xml/atom.xml and a hubs
array in feed.json.
Sending webmentions: also no account
A webmention is the open web's version of an @-mention: when I link to your post, my site POSTs yours a note saying "this URL mentions you", and your site can fetch mine back to verify it. Sending needs nothing set up, but it has to run after a publish, because the source URLs have to be live for the receiver to verify them:
colophon webmention send --env production --dry-run # report, don't POST
colophon webmention send --env production # actually send
It walks the built output, takes each page's canonical URL as the source, collects the cross-origin
outbound links as targets, discovers each target's endpoint, and POSTs. It keeps a sent-cache.colophon/cache/webmention-sent.json
Receiving: webmention.io
To collect replies, likes and reposts, point a receiver at webmention.io, a hosted endpoint that catches them for you:
federation:
indieweb:
webmention:
receiver: https://webmention.io/blog.i0.pm/webmention
With that set, colophon emits a <link rel="webmention">blog.i0.pm regardless of which page is mentioned.
Showing the responses
Collecting them is only half of it; the point is to show the replies and likes under the post. colophon does this two ways, and the choice is a proper trade-off:
federation:
indieweb:
webmention:
receiver: https://webmention.io/blog.i0.pm/webmention
display: { mode: live } # or: asset
token: "{env:WEBMENTION_IO_TOKEN}" # only used by asset mode
assetself-hosts the data: acolophon webmention fetchpulls your mentions back through the API token, caches them, and bakes a small JSON file per post that ships with the site. The responses are served from your own origin, work without hammering anyone else, and degrade gracefully. The cost is that they only refresh when you fetch and redeploy (a cron job, or a decoupledwebmention publishthat pushes just the mentions without rebuilding the site).liveskips all of that: the reader's browser fetches the receiver directly on each page load, so a new reply shows up the next time anyone opens the post, no rebuild, no token, no cron. The cost is honesty about two things, it needs JavaScript (so the responses don't exist for a no-JS reader), and it points your visitors' browsers at a third party.
There's a third path you don't pick directly: a no-JS theme. The engine can bake the responses
into the HTML at build time from the same asset-mode data, so a theme that ships no JavaScript
(the bundled minimal does exactly this) still renders replies and likes, server-side, no client
script. The JS themes use a placeholder the script fills; the no-JS ones get it pre-rendered. Same
data, the theme decides how it arrives.
I went with live. This is a personal blog, not a fortress; I'd rather a reply appear the
moment it lands than run a cron to refresh a cache, and the responses are an enhancement below the
article, not the article itself. I've left the token: line in the config anyway,WEBMENTION_IO_TOKEN (keyring COLOPHON_WEBMENTION_IO_TOKEN, exported by the publish wrapper),asset later is a one-word change rather than a dig through the docs.
POSSE: publish here, syndicate everywhere
Webmentions handle the conversation; the other half of owning your content is
POSSE, Publish on your Own Site, Syndicate Elsewhere. The canonical
copy lives here, and a copy goes out to the silos where people actually are, each one linking back.
colophon has a syndicate step for it, deliberately separate from publish so the original is
always live first:
federation:
syndication:
- id: bsky
driver: bluesky
handle: "{env:BLUESKY_HANDLE:-}"
app_password: "{env:BLUESKY_APP_PASSWORD}" # an app password, not your login
# and on the environment:
environments:
- name: production
syndicate: [bsky] # only listed targets fire; preview/draft envs omit it, so they never post
There are four drivers, and the choice comes down to who holds the keys: mastodon and bluesky post directly to your account with a token (or a Bluesky app password) you control; bridgy posts on your behalf via Bridgy so colophon holds no silo credentials at all; and command runs any program you like for anything without a built-in driver. I started with Bluesky, direct, my app password, one account to reason about.
The flow mirrors the webmention one, run it after a publish so the canonical URL is live:
colophon syndicate --env production --dry-run # preview; posts nothing, writes nothing
colophon syndicate --env production --allow-publish # post the copies, record the ledger
The ledger.colophon/syndication.jsonsyndicate: falsesyndicate: [bsky]syndicate_text:{env:VAR}.
One thing not to confuse: this is syndication (push a copy out). It's a different job from making the site itself followable from the fediverse, that's Bridgy Fed, which hangs off the webmention receiver, not the syndication list. Same neighbourhood, different houses.
Signing in as your own domain
One detail worth getting right. webmention.io doesn't have its own password; you sign in as your
own website using IndieAuth, via
indielogin.com. For that to work, the URL you log in with and an identity
provider it trusts (GitHub is the easy one) have to link to each other with rel="me", at the
same URL on both ends.
colophon emits the rel="me" link from your author's urls:, so set that in
authors/<id>.yaml
# authors/jmylchreest.yaml
urls:
- https://github.com/jmylchreest
GitHub does the other half for you: the website field on your profile is published with
rel="me" automatically. So your site points at GitHub, GitHub points back at your site, the loop
closes, and indielogin.com is happy to authenticate you through GitHub.
The catch is which page carries the rel="me". The link rides on colophon's author h-card, and
the loop only closes if the page you log in with is the same one GitHub links back to. Make sure
your GitHub website field and the URL you sign in with are the same, and that it's a page that
actually renders the author card. On current colophon (v0.0.8) the author page does, so:
- GitHub profile website:
https://blog.i0.pm/authors/jmylchreest/ - Sign in to webmention.io with: the same
https://blog.i0.pm/authors/jmylchreest/
Match those two and the login goes straight through. Mismatch them, even by a trailing path, and you get "we couldn't find any way to authenticate you", which just means the two pages didn't point at each other.
What you get
So, the full loop: feeds that push instead of waiting to be polled, outgoing webmentions firing on every publish, a receiver banking every reply and like against the domain, and those responses rendering live under each post, the moment someone opens it. If you've replied to this from your own site or boosted it on the fediverse (via Bridgy Fed), you should be looking at the evidence a little further down the page.
It's a strange and rather nice feeling, wiring a static site you fully own into a conversation with the rest of the web, without renting a single feature from anyone. More of the web should work like this.
Related
- A Colophon Blog on Cloudflare Pages and R2 — the Pages + R2 setup this builds on.
- Rebuilt the Blog. Again. — why the blog runs on colophon in the first place.
