Integrating Webmentions Into NextJS Blog

I've been meaning to check out webmentions for a while now, as I had been debating between installing some kind of comments package for this blog or just using social to interact with visitors and readers.

I had a day off on Friday, and so decided to take the plunge and try implementing webmentions as a way to collate all of the Twitter interactions with my blog posts. It wasn't as straightforward as I thought it would be, so I've written this blog post for anyone who's trying to do the same with their NextJS blog.

First off, what even is a webmention? According to indieweb, a webmention "is a web standard for mentions and conversations across the web, a powerful building block that is used for a growing federated network of comments, likes, reposts, and other rich interactions across the decentralized social web".

Cool, but how does it work?

One of the easiest ways to get started with it is through webmention.io, an open-source project and hosting service for receiving webmentions and pingbacks on your site. Webmention.io is just a hosting service though, so the other part of the puzzle is to set up some kind of polling service that's constantly surveying the socials to check for any webmentions mentioning your site. Bridgy is one such service.

I'm sold - tell me about implementation!

Alright, so my end goal was to get a list of webmentions showing up at the bottom of each blog post, for that particular blog post. Here are all the steps that I took.

Step 1: Set up relevant accounts

Create an account on webmention.io, and following the installation instructions to make your site part of the indieweb. Similarly, set up a Bridgy account and follow the verification instructions. In both cases, I used my Twitter account to verify ownership of my domain.

Step 2: Check integrations work

The first thing to note is that Bridgy can take some time to properly crawl Twitter for your site mentions. To run a quick check that all integrations have been set up properly, find a Twitter post of yours that links to a blog post on your website, AND that includes some likes / comments / retweets. In my case, I used this tweet.

Add this into the "resend for post" input on Bridgy (whilst you're logged into your account). This will start the process of identifying Tweet mentions (favourites, retweets etc.) of that particular blog post, and pull them into your site as webmentions. Remember those webmention header tags you added in Step 1? That's the link.

We can now make a fetch request to webmention.io to get the webmentions associated with our blog post. To try it out, type this into your browser (replacing the target URL with your own blog post):

https://webmention.io/api/mentions.json?target=https://bionicjulia.com/blog/creating-accordion-component-react-typescript-tailwind

If it worked (and you definitely have web interactions on that tweet!), you should see a JSON response that looks something like this. Note that what we want to access is links which is an array of webmentions.

{
  "links": [
    {
      "source": "https://brid.gy/repost/twitter/bionicjulia/1396870759675580420/1396870781737619459",
      "verified": true,
      "verified_date": "2021-06-25T11:26:00+00:00",
      "id": 1201134,
      "private": false,
      "data": {
        "author": {
          "name": "The Developer Bot",
          "url": "https://twitter.com/TheDeveloperBot",
          "photo": "https://webmention.io/avatar/pbs.twimg.com/6b9fb9e6c61bfc2e2bf7b938a6720320c16528b25776a2c6ef87915f460fafc6.jpg"
        },
        "url": "https://twitter.com/TheDeveloperBot/status/1396870781737619459",
        "name": null,
        "content": "Wrote a blog post on creating a simple accordion component in #reactjs , #typescript and #tailwindcss. 😃 bionicjulia.com/blog/creating-…",
        "published": "2021-05-24T16:48:41+00:00",
        "published_ts": 1621874921
      },
      "activity": {
        "type": "repost",
        "sentence": "The Developer Bot retweeted a tweet https://bionicjulia.com/blog/creating-accordion-component-react-typescript-tailwind",
        "sentence_html": "<a href=\"https://twitter.com/TheDeveloperBot\">The Developer Bot</a> retweeted a tweet <a href=\"https://bionicjulia.com/blog/creating-accordion-component-react-typescript-tailwind\">https://bionicjulia.com/blog/creating-accordion-component-react-typescript-tailwind</a>"
      },
      "target": "https://bionicjulia.com/blog/creating-accordion-component-react-typescript-tailwind"
    },
    {
      "source": "https://brid.gy/repost/twitter/bionicjulia/1396870759675580420/1396870783067058181",
      "verified": true,
      "verified_date": "2021-06-25T11:25:59+00:00",
      "id": 1201132,
      "private": false,
      "data": {
        "author": {
          "name": "Friday",
          "url": "https://twitter.com/friday_Js_bot",
          "photo": "https://webmention.io/avatar/pbs.twimg.com/f5c85fab1b8b41ea6fd3aa9b13a371e45d5a82fbbc3e52e8dbb9ed34eeeeac0c.jpg"
        },
        "url": "https://twitter.com/friday_Js_bot/status/1396870783067058181",
        "name": null,
        "content": "Wrote a blog post on creating a simple accordion component in #reactjs , #typescript and #tailwindcss. 😃 bionicjulia.com/blog/creating-…",
        "published": "2021-05-24T16:48:41+00:00",
        "published_ts": 1621874921
      },
      "activity": {
        "type": "repost",
        "sentence": "Friday retweeted a tweet https://bionicjulia.com/blog/creating-accordion-component-react-typescript-tailwind",
        "sentence_html": "<a href=\"https://twitter.com/friday_Js_bot\">Friday</a> retweeted a tweet <a href=\"https://bionicjulia.com/blog/creating-accordion-component-react-typescript-tailwind\">https://bionicjulia.com/blog/creating-accordion-component-react-typescript-tailwind</a>"
      },
      "target": "https://bionicjulia.com/blog/creating-accordion-component-react-typescript-tailwind"
    }
    //...
  ]
}

Step 3: Fetch webmention results in NextJS

Now that we know that's working, let's display the relevant web mentions in each of our blog posts.

The name of my component which renders the layout of one blog post is BlogLayout.tsx. What I want upon this component mounting, is to call the webmention API, pull in the results in JSON format, and assign the results array to a variable. For this, I used the useEffect and useState hooks like so:

const [mentions, setMentions] = useState<WebMention[]>()

useEffect(() => {
  fetch(`https://webmention.io/api/mentions.json?target=https://bionicjulia.com/blog/${slug}`)
    .then((response) => response.json())
    .then((result) => {
      setMentions(result.links)
    })
}, [])

Note that I've used a template literal to make this call dynamic depending on the page I'm on, through slug. If you're new to NextJS and wondering how to access this parameter, check out my previous post on how I set up my blog Setting up a NextJS Markdown Blog with Typescript . The 1 minute summary is that within your blog post template ([slug].tsx in my case), you need to pass params.slug to the getStaticProps method.

If you're using Typescript, here's how I defined my WebMention type.

export type WebMention = {
  source: string
  verified: boolean
  verified_date: string
  private: boolean
  data: {
    author: {
      name: string
      url: string
      photo: string
    }
    url: string
    name: string
    content: string
    published: string
  }
  activity: {
    type: 'link' | 'reply' | 'repost' | 'like'
    sentence: string
    sentence_html: string
  }
  target: string
}

Step 4: Display results in blog post

Next up, I want each blog post page to end with a section that displays all webmentions pertaining to that post. In the same BlogLayout.tsx file, I added this bit of JSX to my return statement:

<section>
  <h2>Web Mentions</h2>
  <p>Tweet about this post and have it show up here!</p>
  {mentions?.map((mention) => (
    <div className="flex">
      <CustomLink href={mention.data.author.url} className="mr-2">
        <Image
          src={mention.data.author.photo}
          alt={mention.data.author.name}
          width={25}
          height={25}
          className="rounded-full border border-lightest-green bg-lightest-green"
        />
      </CustomLink>
      {(!mention.data.content || mention.activity.type === 'repost') && (
        <p>
          {mention.data.author.name}{' '}
          <CustomLink href={mention.data.url}>{activityMap[mention.activity.type]}</CustomLink> this post.
        </p>
      )}
      {mention.data.content && mention.activity.type !== 'repost' && <p>{mention.data.content}</p>}
    </div>
  ))}
</section>

I played around with showing different outputs depending on the activity type of each mention, so feel free to display whatever makes sense to you.

The reference to activityMap was just my way of displaying the right verb for each activity type.

const activityMap = {
  link: 'linked to',
  reply: 'replied to',
  repost: 'retweeted',
  like: 'favourited',
}

Quick note: I used NextJS's Image component to render each webmention's author's picture. As this picture is hosted on webmention.io, I needed to explicitly tell NextJS that this was a recognised host. To do this, amend next.config.js and add the following:

images: {
  domains: ['webmention.io'],
},

Was this helpful? Have you tried implementing webmentions on your site? What do you think about them? Tell me on Twitter @bionicjulia and have your tweet show up below! 😉

71