새 블로그 개발일지 - 7: utterances 를 이용한 댓글 기능 구현

포스트에 댓글기능을 어떻게 구현할까

댓글기능 구현하기

댓글을 직접 구현하기에는 귀찮은점이 적지 않다. 그래서 서드파티 툴을 사용하려고 하는데, 무엇이 좋을지 검색을 해보았다.
utteranc.es 에서 제공하는 댓글기능을 이용하려고 한다. utterances는 github의 issue기능을 이용한 댓글기능을 제공한다. 나는 현재 개발중인 깃헙 레포가 퍼블릭으로 공개되어 있기때문에 그대로 이용할 예정이다. 개발 코드가 프라이빗이라면 따로 댓글기능을 위한 레포를 생성해주어야 한다.
설치페이지로 이동해서 설치해준다.
notion image
notion image
깃헙 앱에 Vercel과 untterances 두개가 설치된 모습이다. 이제는 utterances 세팅을 해주어야 한다.
 
notion image
[github id]/[repo name] 형식으로 인풋에 입력해준다.
 
notion image
블로그 포스트와 이슈를 매핑하는 방법에 대한 내용이다. 내 경우에는 페이지의 타이틀을 이용해서 이슈를 매핑하기로 했다.
이제 설정을 다 했으니 코드에서 작업만 해주면 끝이난다.
 
// components/Comment/Comment.tsx import { useState, useEffect } from 'react'; import useRootState from '../../core/hooks/useRootState'; const COMMENT_THEME = { light: 'github-light', dark: 'github-dark' }; const Comment = () => { const [container, setContainer] = useState<Element | null>(null); const { mode } = useRootState((state) => state.theme); useEffect(() => { if (!container) return; const script = document.createElement('script'); script.src = 'https://utteranc.es/client.js'; script.async = true; script.setAttribute('repo', 'byseop/devlog-v2'); script.setAttribute('issue-term', 'title'); script.setAttribute('label', 'comment'); script.setAttribute('theme', `github-${mode}`); script.setAttribute('crossorigin', 'anonymous'); container.appendChild(script); }, [container]); useEffect(() => { if (!container) return; const message = { type: 'set-theme', theme: COMMENT_THEME[mode] }; const commentIframe = container.querySelector('iframe'); commentIframe?.contentWindow?.postMessage(message, 'https://utteranc.es'); }, [mode]); return <div className="comment-container" ref={setContainer} />; }; export default Comment;
코드는 단순하다. 컨테이너를 생성해두고 그 곳에 스크립트를 심는 방식이다. 스크립트가 실행된 이후에는 iframe으로 댓글기능이 활성화되고 심어두었던 스크립트는 삭제된다.
테마가 변경되었을때는 해당 iframe에 postMessage 를 이용하여 스타일을 변경해준다.
 
// components/Post/Post.tsx // ... // ... return ( <div className={`post-wrapper ${className}`}> <div className="article-header"> {cover && ( <div className="cover-wrap"> <div className="cover"> <Image src={cover.external.url} alt="" fill /> </div> </div> )} {title && ( <div className="post-title-wrap"> <h1>{title.title[0].plain_text}</h1> {subTitle && <h2>{subTitle.rich_text[0].plain_text}</h2>} </div> )} </div> {postData?.data.notionPage && ( <div className="post-content-wrap"> <NotionRenderer recordMap={postData.data.notionPage} darkMode={mode === 'dark'} components={{ Code }} /> </div> )} <Comment /> // <- 코멘트 추가 </div> ); }; export default Post;
코드 하단에 <Comment /> 컴포넌트를 추가해주었다.
 
notion image
notion image
그리고 깃헙 이슈에는 이런식으로 이슈가 생성된다.
 

스타일 이슈

utterances 를 잘 사용하던중 포스트 페이지에 진입할때마다 스타일코드가 중복으로 쌓이는것을 발견했다. 아마도 스크립트가 실행되면서 헤드에 스타일코드를 생성해주는것 같다. 근데 문제는 utterances 에서 따로 destroy 함수를 제공하지 않는다는 것이다.
이 문제를 해결하기 위해서 간단한 코드를 추가하였다.
// components/Comment/Comment.tsx import { useState, useEffect } from 'react'; import useRootState from '../../core/hooks/useRootState'; const COMMENT_THEME = { light: 'github-light', dark: 'github-dark' }; const Comment = () => { // ... logic useEffect(() => { return () => { const style = document.head.getElementsByTagName('style')[0]; if (style.innerHTML.includes('utterances')) { style.remove(); } }; }, []); return <div className="comment-container" ref={setContainer} />; }; export default Comment;
이런식으로 컴포넌트가 언마운트 될 때 스타일코드를 찾아서 remove 해주는 방식으로 해결하였다.
 

마무리

역시 남이 만들어 놓은걸 가져다 쓰는게 최고인가요?