💎

Using Github as a comment system for your site

Anh-Thi Dinh
In this note, we will explore both and . However, I prefer giscus.

Why choose giscus over utterances?

  • giscus is actively maintained while utterances is not.
  • giscus utilizes discussions, whereas utterances uses issues.
  • giscus supports "reply" in comments, but utterances doesn't.
  • giscus provides wrappers for popular frameworks, unlike utterances, which only uses js (requiring manual implementation, though not overly complicated).

How to use giscus

If you are using plain JS, simply follow the official guide.
For those wanting to use it with Web Frameworks like React or Vue, refer to this .

Migrating from utterances

To integrate giscus into your website, follow the instructions provided in the official guide.
You can convert each issue to the corresponding discussion. Make sure what you choose in the config of Mapping between posts and discussions (giscus) or issues (utterances) is consistent. In my case, I choose "page title".
Before migrating, consider creating a new discussion with the exact name you will use for the comments. Then add some comments to that discussion. After reloading your page, you should see the comments. Remember to remove this test before migrating to avoid any conflicts.
In each issue, an option "Convert to discussion" is available at the bottom right.
If you wish to convert multiple issues at once:
  1. Ensure the issues are open.
  1. Create a new label (in the label overview page — github.com/username/your-repo/labels), for example, "comments".
  1. Return to the issues page, select all the pages you want, and then add the label "comments" to them.
  1. Navigate back to the label overview page, where you will see a button "Convert to discussions" next to the label "comments".

Using utterances

Simply follow the official guide.
For integration in Next.js or in any React site, follow these steps:
  1. Create a hook
    1. 1import { useEffect, useState } from 'react'
      2
      3const useScript = (params: any) => {
      4  const { url, theme, issueTerm, repo, ref } = params
      5  const [status, setStatus] = useState(url ? 'loading' : 'idle')
      6
      7  useEffect(() => {
      8    if (!url) {
      9      setStatus('idle')
      10      return
      11    }
      12
      13    const script = document.createElement('script')
      14    script.src = url
      15    script.async = true
      16    script.crossOrigin = 'anonymous'
      17    script.setAttribute('theme', theme)
      18    script.setAttribute('issue-term', issueTerm)
      19    script.setAttribute('repo', repo)
      20
      21    // Check if the script is already in the document?
      22    const existingScript =
      23      !!document.getElementsByClassName('utterances')[0] || ref?.current?.firstChild
      24
      25    if (existingScript) {
      26      setStatus('ready')
      27    } else {
      28      ref.current?.appendChild(script)
      29    }
      30
      31    const setAttributeStatus = (event: any) => {
      32      setStatus(event.type === 'load' ? 'ready' : 'error')
      33    }
      34
      35    script.addEventListener('load', setAttributeStatus)
      36    script.addEventListener('error', setAttributeStatus)
      37
      38    return () => {
      39      if (script) {
      40        script.removeEventListener('load', setAttributeStatus)
      41        script.removeEventListener('error', setAttributeStatus)
      42      }
      43    }
      44  }, [url])
      45  return status
      46}
      47
      48export default useScript
  1. Use this hook in a client component ('use client')
    1. 1'use client'
      2
      3// imports
      4
      5const Comments = () => {
      6  const comment = useRef(null)
      7
      8  const status = useScript({
      9    url: 'https://utteranc.es/client.js',
      10    theme: 'github-light',
      11    issueTerm: 'title',
      12    repo: 'dinhanhthi/dinhanhthi.com-comments',
      13    ref: comment
      14  })
      15
      16  return (
      17    <div className={cn(className, containerNormal, 'mt-8')}>
      18      {status === 'loading' && (
      19        <div>Loading comments...</div>
      20      )}
      21      <div ref={comment}></div>
      22    </div>
      23  )
      24}
      25
      26export default Comments