Ian Obermiller

Part time hacker, full time dad.

Comments with Utterances on Next.js

Posted

Utterances is a neat little script and GitHub app that adds commenting to your blog backed by comments on GitHub issues. The script is lightweight, at the time of writing clocking in at 14KB compressed.

The official instructions say to include a script tag in your HTML, like so:

<script
  src="https://utteranc.es/client.js"
  repo="ianobermiller/ianobermiller.com"
  issue-term="pathname"
  theme="preferred-color-scheme"
  crossorigin="anonymous"
  async
></script>

You can put this same snippet in a React component in Next.js, but won't work as expected. If you navigate directly to a post that includes the script, it will work because the script will be rendered on the server. If you click a link within the application however, like from an index page, the comments will not load. This is because React doesn't dynamically render script tags.

Instead, you need to create a script tag inside of useEffect, assign the attributes, and append it to an element. It looks like this:

function Comments() {
  const ref = useRef<HTMLDivElement | null>(null);
  useEffect(() => {
    const scriptElement = document.createElement('script');
    scriptElement.async = true;
    scriptElement.crossOrigin = 'anonymous';
    scriptElement.src = 'https://utteranc.es/client.js';

    scriptElement.setAttribute('issue-term', 'pathname');
    scriptElement.setAttribute('label', 'comment');
    scriptElement.setAttribute(
      'repo',
      'ianobermiller/ianobermiller.com',
    );
    scriptElement.setAttribute(
      'theme',
      'preferred-color-scheme',
    );

    ref.current?.appendChild(scriptElement);
  }, []);

  return <div ref={ref} />;
}

First off, we'll separate this into a component to keep our main Post component clean. Next, we'll create a ref that will be assigned to an empty div which will be the container for the script tag. Then, inside of useEffect, we create the actual script tag, assign standard props (async, crossOrigin, and src) directly, and use setAttribute for the utterances specific props.

And that's it! You can see the result below this post.