ADAM DJ BRETT

Home / Blog / Enabling Webmentions in Eleventy (11ty)

Enabling Webmentions in Eleventy (11ty)

This post is all about learning and coding in public. We learn from each others mistakes and we get better by building together. Webmentions were, to borrow my own git commit message, "a massive pain" — so here is the honest version of how I finally got them working on this Eleventy (11ty) site.


What is a webmention? #

A webmention is a small, open W3C standard for one site to tell another, "hey, I mentioned you." When someone replies to, likes, or reposts my post on Bluesky, Mastodon, or their own blog, a webmention lets that interaction show up under my post, on my site, that I own. It is the IndieWeb answer to comments living inside someone else's silo.

I had been trying to do this on my own off and on for a couple of months but thanks to impliment webentions to no avail so when I saw Brennan's wonderful post, "How webmentions work on brennan.day", I got excited because I knew that I could finally accomplish adding webmentions thanks to Brennan's brilliance. From there I read everything I could find, and these were the most useful:

The flow I landed on #

After a lot of trial and error, here is the pipeline I settled on:

  1. Bridgy — bridges my social silos (Bluesky, Mastodon, GitHub, Tumblr) so their replies and likes become webmentions.
  2. webmention.io — the endpoint that receives and stores all the webmentions sent to me.
  3. Netlify — builds and deploys the static site where the all-important <link> tags live.
  4. A GitHub Actions workflow — pulls the stored mentions into the repo on a schedule and sends my outbound mentions.
  5. Next step — having Bridgy publish each new blog post out to my github.com README.

Let me walk through each piece.

1. webmention.io — the receiver #

webmention.io is the free, hosted endpoint by Aaron Parecki that listens for incoming webmentions. You sign in with your domain using IndieAuth, and then you tell the world where to send mentions by dropping two <link> tags into the <head> of every page. On my site those live in my SEO include:

<link rel="pingback" href="https://webmention.io/www.adamdjbrett.com/xmlrpc" />
<link rel="webmention" href="https://webmention.io/www.adamdjbrett.com/webmention" />

To sign in with IndieAuth I also added the endpoint links and a pile of rel="me" links (GitHub, Bluesky, Mastodon, ORCID, and so on) so the services can confirm that those profiles are really me:

<link rel="authorization_endpoint" href="https://indieauth.com/auth">
<link rel="token_endpoint" href="https://tokens.indieauth.com/token">
<link rel="me" href="https://github.com/adamdjbrett">
<link rel="me" href="https://bsky.app/profile/adjb.co">
<link rel="me" href="https://social.lol/@adjb">

The rel="me" links are the single most important and most fiddly part. The relationship has to be two-way: my site links to my GitHub, and my GitHub links back to my site. If either direction is missing, IndieAuth and Bridgy quietly refuse to work, and you are left staring at an empty webmentions section wondering what you did wrong. (I was.)

2. Bridgy — bring the silos home #

webmention.io only hears about mentions from sites that already speak webmention. Bluesky, Mastodon, and the like do not — so Bridgy acts as the translator. You connect each account once, and from then on Bridgy watches for replies, likes, and reposts and forwards them to my webmention.io endpoint as proper webmentions.

I also want to publish in the other direction — to syndicate my posts out to those silos. That is what Bridgy's publish links do, again as <link> tags in the head:

<link rel="publisher" href="https://brid.gy/publish/bluesky">
<link rel="publisher" href="https://brid.gy/publish/github">
<link rel="publisher" href="https://brid.gy/publish/mastodon">

I did not enable Tumblr at this time — I am keeping the surface area small while I get the core flow solid, and I would rather add a silo deliberately than wire up one I am not actively posting to.

3. Netlify — where the tags live #

There is no Netlify-specific magic here, and that is the point. Netlify builds my Eleventy site and serves the static HTML — which means it serves those rel="webmention", rel="me", and rel="publisher" link tags on every page. Bridgy and webmention.io both work by reading my live, deployed pages, so every webmention depends on Netlify having shipped the correct markup. Build it, deploy it, and the tags do their job.

The www vs. apex loop that nearly broke me #

This is the mistake that cost me the most time, so it gets its own section. When I first set things up I tried to be thorough and registered both adamdjbrett.com and www.adamdjbrett.com with webmention.io and Bridgy. That was a trap.

On Netlify my canonical host is www.adamdjbrett.com, and everything else 301-redirects to it:

http://adamdjbrett.com/*       https://www.adamdjbrett.com/:splat 301!
http://www.adamdjbrett.com/*   https://www.adamdjbrett.com/:splat 301!
https://adamdjbrett.com/*      https://www.adamdjbrett.com/:splat 301!

So when a webmention target pointed at the bare adamdjbrett.com, it would bounce through a redirect to the www version — while my data, my rel tags, and my page URLs all spoke www. The two hostnames never lined up. Mentions sent to one domain would not match posts living on the other, verification would chase a redirect, and I ended up with what my git log politely calls a "loop." My workflow was even fetching from WEBMENTION_DOMAIN: adamdjbrett.com while the site served www, so the fetch came back empty and looked like nothing was wrong.

The fix was to pick one canonical host and use it everywhere — www.adamdjbrett.com, because that is what Netlify serves. I changed the workflow and the scripts to default to the www domain:

- WEBMENTION_DOMAIN: adamdjbrett.com
+ WEBMENTION_DOMAIN: www.adamdjbrett.com
- const domain = process.env.WEBMENTION_DOMAIN || "adamdjbrett.com";
+ const domain = process.env.WEBMENTION_DOMAIN || "www.adamdjbrett.com";

I also taught my URL normalizer to reject malformed and bare-www targets so a stray hostname could not sneak back in and re-create the mismatch:

const hasValidUrlHost = (hostname) => {
  const host = hostname.toLowerCase();
  const isIpAddress = /^[0-9.]+$/.test(host) || host.includes(":");
  return Boolean(host) && host !== "www" && (host.includes(".") || host === "localhost" || isIpAddress);
};

The lesson: decide on a single canonical domain before you touch webmentions, and make webmention.io, Bridgy, your rel tags, and your workflow all agree on it. Trying to support both the apex and www does not make you more robust — it just gives the redirect a chance to eat your mentions.

4. The GitHub Action — fetching and sending #

This is the part that took the most work. webmention.io stores my mentions, but I want them committed into the repo as data so Eleventy can render them at build time (no client-side JavaScript, no extra requests for my readers). So I wrote a small Node script, scripts/fetch-webmentions.mjs, that hits the webmention.io API and writes the results to _data/webmentions.json:

const endpoint = new URL("https://webmention.io/api/mentions.jf2");
endpoint.searchParams.set("domain", domain);
endpoint.searchParams.set("token", token);
endpoint.searchParams.set("per-page", "100");

A scheduled GitHub Actions workflow runs this on a cron, then commits the JSON back to the repo if anything changed:

- name: Refresh feeds, status, and webmentions
  env:
    WEBMENTION_IO_TOKEN: $
    WEBMENTION_DOMAIN: www.adamdjbrett.com
  run: npm run update:webmentions

- name: Send outbound webmentions
  continue-on-error: true
  run: npm run send:webmentions

That second step, scripts/send-webmentions.mjs, walks my posts, finds the outbound links, and sends a webmention to any site that advertises an endpoint — so I am a good neighbor and notify the people I link to, not just collect the mentions sent to me.

I learned the hard way to verify the setup instead of trusting it. I added a check:webmention script that fails the build if any of the link tags go missing — the rel="webmention" endpoint, the rel="me" links, the Bridgy publish links. The token also matters: an early run failed silently because I had the wrong secret name, which is exactly the kind of mistake a check script catches. If you only learn one thing from my pain, let it be this: write a verifier.

5. Rendering them in Eleventy #

With the data in _data/webmentions.json, two small Eleventy filters do the work of matching mentions to the right post and grouping them by type:

eleventyConfig.addFilter("webmentionsForUrl", ...);   // mentions of this page
eleventyConfig.addFilter("webmentionsByTypes", ...);  // replies vs likes/reposts

Then a Nunjucks include, webmentions.njk, renders replies and mentions as a list and likes, reposts, and bookmarks as a row of avatars beneath each post:




Because all of this happens at build time, my readers download plain HTML. No tracking, no third-party scripts, no slow comment widget.

What broke (so you do not have to repeat it) #

  • Registering both adamdjbrett.com and www. Pick one canonical host (mine is www, because that is what Netlify serves) and make everything agree. Supporting both just lets the redirect swallow your mentions — see the section above.
  • One-way rel="me" links. The back-link from each profile is mandatory. Both directions, or nothing.
  • The wrong secret name. My fetch ran, returned nothing, and looked "fine." It was reading an empty token. Name your secrets carefully and verify them.
  • Patience. Bridgy polls on its own schedule and webmention.io needs your tags live first. After deploying, I had to give the silos time to crawl before anything appeared. It is not instant.

Next step #

The piece I am building toward now is having Bridgy publish each new blog post out to my github.com README, so my profile becomes one more place my writing flows automatically — owned by me, syndicated everywhere. More on that once I have it working (and broken, and working again).

Webmentions were a pain, but owning my own conversations turned out to be very much worth it.

Tags : website 11ty webmentions

Webmentions

No webmentions yet.

Previous

Publishing Your Eleventy Blog on the AT Protocol with Standard.site and Sequoia

A step-by-step guide to connecting your Eleventy static site to the AT Protocol using standard.site and sequoia-cli — own your content on the open web.