DEV Community

Vincent Tang
Vincent Tang

Posted on

Installing Blog Comments on your GatsbyJs/React Site using Utterances

Alt Text

Adding a commenting system into a blog platform is really useful. I wanted something that didn't require a lot of configuration, was open source, and had data stored in a visible way in the event I needed to move around blogging platforms. I also didn't want something externally controlled like Disqus, as they've been known to inject ads into your site.

Enter Utterances. It's based on Github's search API, so that means it's free and open-source!
It uses Github's issue tracker to create an entry, and all comments in that entry are tied directly to a blog post

How does it work?

So for instance, say I take my blog post on Building a Custom Podcast Site using Gatsby, ReactJs, Netlify, and AmazonS3

The article lives here:

There is something called a "slug" or a "pathname". It's basically everything that is after the main website name, in this case, www.vincentntang.com. Here's the slug for the article:

  • /custom-podcast-site-gatsby-react

If I add a comment to that blog post, here's what it looks like on the page:

Alt Text

I have it configured so that the utterance bot creates an issue in this repo, tied to that "slug":

This is the default out of the box setting with Utterances; the setting I used is Issue title contains page pathname

Also there's no code in this repo, it just has Utterances installed on the repo, per step 2 on installation on the utterances webpage. This means you install a widget on the repo and grant Utterances read/write permissions to create issues in there.

So Utterances creates an issue here, and each comment in this issue gets mapped directly into your blog post's comments.

Alt Text

You can check out the issue tracker created here:

The main benefit of using Utterances is you fully own the comments on your site since it lives in your repository. Its secondary benefit is it fights spam since users have to authenticate in Github first to make a comment. There are no anonymous comments, and it creates a trust system for both the commenter and the blogger

How do you configure this in GatsbyJs React?

It took me way too long to figure out this configuration. I'm writing the blog I wish I had read when I tried getting Utterances setup.

Utterances works by adding a script tag into your blog post template. What it does is it adds an iframe into your site, with the comments associated with your blog post.

In the installation readme, it suggests adding this script here:

<script src="https://utteranc.es/client.js"
        repo="[ENTER REPO HERE]"
        issue-term="pathname"
        theme="github-light"
        crossorigin="anonymous"
        async>
</script>
Enter fullscreen mode Exit fullscreen mode

For the repo=[ENTER REPO HERE], this got me tripped up. I thought that it meant the full URL of the repo, in this case:

But the actual configuration for the repo=[ENTER REPO HERE] is:

  • vincentntang/vincentntang.com-comments

So the full configuration for the script element looks like this for my current setup:

<script src="https://utteranc.es/client.js"
        repo="vincentntang/vincentntang.com-comments"
        issue-term="pathname"
        theme="github-light"
        crossorigin="anonymous"
        async>
</script>
Enter fullscreen mode Exit fullscreen mode

Hold up a moment though! There's actually more to this, you need to reference a <div> for where this script tag injects it's <iframe> comments. This is where I had to read through a lot of obscure blogs to find out how to set this up in GatsbyJs and React.

The best way to handle this is to create a seperate React Component for installing Utterances. I called my component Comments.js and wrote it in React Class Components. Here's how I add the script configuration in React:

import React, {Component} from "react";
import ThemeContext from '../context/ThemeContext';
export default class Comments extends Component {
  static contextType = ThemeContext;

  constructor(props){ 
    super(props);
    this.commentBox = React.createRef(); // Creates a reference to inject the <script> element
  }
  componentDidMount () {
      const theme = this.context;
      const utteranceTheme = theme.dark ? "github-dark" : "github-light";
      let scriptEl = document.createElement("script");
      scriptEl.setAttribute("src", "https://utteranc.es/client.js");
      scriptEl.setAttribute("crossorigin","anonymous");
      scriptEl.setAttribute("async", true);
      scriptEl.setAttribute("repo", "vincentntang/vincentntang.com-comments");
      scriptEl.setAttribute("issue-term", "pathname");
      scriptEl.setAttribute( "theme", utteranceTheme);
      this.commentBox.current.appendChild(scriptEl);
  }

  render() {
    return (
        <div className="comment-box-wrapper container pt-7">
          <h1 className="mb-0">Comments</h1>
          <hr className="my-0"/>
          <div ref={this.commentBox} className="comment-box"/>
          {/* Above element is where the comments are injected */}
        </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

This Comments class component uses React.createRef() to create a reference for how the Utterances <script> element is injected. There's a <div ref={this.commentBox}/> for the div element that adds the comment HTML from that <script> injection.

Also, I use both a dark, and a light theme mode in my blog. Utterances has a configuration for setting a theme, github-light and github-dark being the most common configurations. I use React's Context API to make this variable globally available, so I know when a user toggles the dark/light mode in my blog.

This is where the code in ComponentDidMount comes into play:

const theme = this.context;
const utteranceTheme = theme.dark ? "github-dark" : "github-light";
// .....
scriptEl.setAttribute( "theme", utteranceTheme);
Enter fullscreen mode Exit fullscreen mode

If you don't have a dark/light theme in your app, feel free to modify the above code to a dark theme:

scriptEl.setAttribute( "theme", "github-dark");
Enter fullscreen mode Exit fullscreen mode

or a light theme:

scriptEl.setAttribute("theme", "github-light");
Enter fullscreen mode Exit fullscreen mode

That should be everything you need for setting up Utterances! You'll also need to call the <Comments> component too in your blog template. I put mine right below the information about the author

import React, {Component} from "react"

export default class PostTemplate extends Component {
  render(){
    return (
      <Layout>
        <article>
          <div
            className="post"
            dangerouslySetInnerHTML={{ __html: postNode.html 
           }}
          />
        </article>
      <UserInfo gatsbyImg={vincentBlue} />
      <Comments/>
    </Layout>
  }
}
Enter fullscreen mode Exit fullscreen mode

Feel free to checkout out how I setup the <Comments> component in my codebase:

As a final note, there are some things you should know about themes in Utterances:

You can't change the comment theme when a user toggles light/dark mode, it's set depending on what theme is set when the user navigates to the page. You have to completely unmount the Comments Component and create a new seperate instance of it if you want to have toggleable dark/light modes.

Oldest comments (3)

Collapse
 
dance2die profile image
Sung M. Kim

Thanks for sharing the article on utterance!
I will use that next time I migrate the blog or create a new site :)

Collapse
 
rifqirosyidi profile image
Rifqi Rosyidi

Nice articles. :)

However i have some issue with my comment system because some of translation in slug which render different path to utterrances for example i have http://example.com/article-one , and also i have the same article but with different language for http://example.com/id/article-one , so the comment will different for each path, any suggestion?

Collapse
 
m4ss1ck profile image
M4ss1ck

I had the same issues setting issue-term to "pathname" or "url", but I solved it using "og:title" instead, since every page og:title got translated, unlike paths and urls, which stays basically the same (I guess "title" would work too)