Adding Webmentions to Astro

A new 'indie' way to add comments to a website.

Published on Dec 24, 2024 12:21 AM GMT+11

3 min read


I recently added a new way of curating some comments, to a blog that has never had the feature. Instead of using some JavaScript-heavy solution, like Disqus, I had taken the decision to use something called ‘Webmentions’.

Webmention is a ‘indie web’ tool, where it stems from a community need to hold social data on their own servers & domains - as opposed to siloed social networks. The tool itself does what it says on the tin - it aggregates mentions of a website on other websites, and sends them back to the website to include, or otherwise acknowledge.

Integrating it with Astro

When I first looked into Webmentions, I had initially thought that I had to write the integrations manually - as in, writing the polling code from various social media services. However, after reading various guides, like this guide from Kevin Lee Drum, I found that I could use two hosted services to make the process much easier.

First, I set up webmention.io, which provides the core Webmention service. After signing up, all I needed to do was simply add their HTML elements. to the Head.astro file.

<!-- Head.astro -->
<link rel="webmention" href="https://webmention.io/website.com/webmention" />
<link rel="pingback" href="https://webmention.io/website.com/xmlrpc" />

After that, I had to set up Bridgy, which allows me to bridge comments, likes and boosts from Mastodon. Setting it up was also pretty easy, however the issue was that I had to figure out a way to display comments and boosts.

Unlike the aforementioned guide, which used a static Astro website, I had made the choice to make the comments server-side rendered, which makes for a simpler integration - without the need to poll a serverless provider to rebuild the website.

As for the boosts, that was dead simple to set up, all I really needed to do was filter by the URL, and the ‘target’ (the type of Webmention) as repost-of.

<h1 class="text-lg md:text-2xl font-bold text-center">Activity</h1>
{
  webmentionChildren.filter(
    (c) =>
      c["wm-property"] === "repost-of" &&
      c["wm-target"] === `https://wale.au/blog/${id}`,
  ).length !== 0 ? (
    <p class="text-sm md:text-lg font-regular text-center">
      Boosted by
      {webmentionChildren
        .filter((c) => c["wm-property"] === "repost-of")
        .filter((c) => c["wm-target"] === `https://wale.au/blog/${id}`)
        .map((c, i, a) => (
          <Fragment>
            <a href={`${c["url"]}`} class="underline underline-offset-2">
              {c["author"]["name"]}
            </a>
            {i !== a.length - 1 && <span>, </span>}
          </Fragment>
        ))}
    </p>
  ) : (
    <p class="text-sm md:text-lg font-regular text-center">No boosts found.</p>
  )
}

What was more difficult was integrating comments, with error handling, however that was worked out eventually.

<h1 class="text-lg md:text-2xl font-bold text-center">Comments</h1>
<p class="text-sm md:text-lg font-semibold text-center">
  These comments are powered by <b>Webmentions</b>.
</p>

{
  webmentionChildren
    .filter((c) => c["wm-property"] === "in-reply-to") // Commments only.
    .filter((c) => c["wm-target"] == `https://wale.au/blog/${id}`)
    .map((c, _, a) =>
      a.length !== 0 ? (
        <div class="flex flex-col gap-4 flex-wrap justify-center">
          <figure class="flex flex-row gap-2 self-center items-center text-center">
            <img
              src={`${c["author"]["photo"]}`}
              alt={`Mastodon profile of ${c["author"]["name"]}`}
              width="32"
            />
            <figcaption class="text-sm md:text-lg font-bold">
              from <em>{c["author"]["name"]}</em>
            </figcaption>
          </figure>
          <h2 class="text-sm text-center">
            Published at{" "}
            <a href={`${c["url"]}`} class="underline underline-offset-2">
              {format(c["published"], "PPPP p")}
            </a>
          </h2>
          <blockquote class="p-4 border-l-4 flex flex-col gap-4 border-l-[#FF2121] bg-bg-300 dark:bg-bg-dark-700">
            <p class="text-sm md:text-lg font-normal">{c["content"]["text"]}</p>
            <a href={`${c["url"]}`} class="underline underline-offset-2 italic">
              Original URL
            </a>
          </blockquote>
        </div>
      ) : (
        <p class="text-sm md:text-lg font-regular text-center">
          No comments found.
        </p>
      ),
    )
}

Deploying

As I mentioned, I am deploying this with a server, so I ended up using fly.io. I have already been using this to deploy the rewritten website, especially with the existing Astro SSR support, so all I really need to do is run a couple of commands to deploy the new server.

fly secrets set WEBMENTION_API_KEY="xxxxxxxxxxxx"
fly deploy --build-secret WEBMENTION_API_KEY="xxxxxxxxxxxx"

Activity

No boosts found.

Comments

These comments are powered by Webmentions.