- 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).
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
giscus-component
. 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:
- Ensure the issues are open.
- Create a new label (in the label overview page —
github.com/username/your-repo/labels
), for example, "comments".
- Return to the issues page, select all the pages you want, and then add the label "comments" to them.
- Navigate back to the label overview page, where you will see a button "Convert to discussions" next to the label "comments".
Simply follow the official guide.
For integration in Next.js or in any React site, follow these steps:
- Create a hook
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
- Use this hook in a client component (
'use client'
)
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