Integrating Webmentions Into NextJS Blog
Tags: nextjs, react, typescript, webmention
Published: 2021-06-26 11:24:15

---
title: Integrating Webmentions Into NextJS Blog
published: true
date: 2021-06-26 00:00:00 UTC
tags: nextjs, react, typescript, webmention
description: Verifying with Twitter as an example
canonical_url: https://bionicjulia.com/blog/integrating-webmentions-nextjs-blog
---

I've been meaning to check out [webmentions](https://indieweb.org/webmention.io) 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](http://webmention.io), an open-source project and hosting service for receiving webmentions and pingbacks on your site. [Webmention.io](http://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](https://brid.gy/about) 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](http://webmention.io), and following the installation instructions to make your site part of the indieweb. Similarly, set up a [Bridgy](https://brid.gy/) 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](https://twitter.com/bionicjulia/status/1396870759675580420).

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](http://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):

```html
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.

```json
{
  "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:

```jsx
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](/blog/setting-up-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.

```jsx
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:

```jsx
<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.

```jsx
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:

```jsx
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](https://twitter.com/bionicjulia) and have your tweet show up below! 😉