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"