본문 바로가기

프론트엔드

Nextjs블로그에 Utterances댓글 달고 다크모드 적용하기

 

 

블로그에 댓글기능을 추가하고 싶어서 알아보던 중, 깃헙의 utterances를 사용했다.

이유는 깃헙에 로그인을 해야 댓글을 달 수 있고, 마크다운을 사용할 수 있고, 직접 구현하지 않고 삽입해서 바로 사용가능하기 때문이다.

Create Repository & Install Utterances

  • 우선 댓글용 레포지터리를 새로 생성한 뒤,
  • Install Utterances에서 앱을 설치하고 새로 생성한 레포지터리를 연결한다.
  • 그럼 아래 스크립트가 생성된다.
<script
  src="https://utteranc.es/client.js"
  repo="레포지트리명"
  issue-term="pathname"
  theme="github-light"
  crossorigin="anonymous"
  async
></script>

Comment 컴포넌트 생성

  • 리액트는 xss보안 이슈 때문에 위 스크립트가 적용된 컴포넌트를 생성해줘야한다.
import React, { useEffect } from "react"

const COMMENTS_ID = 'comments-container';

export default function Comment() {
  useEffect(() => {
      const script = document.createElement('script');
      script.src = 'https://utteranc.es/client.js';
      script.setAttribute('repo', "레포지트리명");
      script.setAttribute('issue-term', 'pathname');
      script.setAttribute('theme', 'github-light');
      script.setAttribute('crossorigin', 'anonymous');
      script.async = true;

      const comments = document.getElementById(COMMENTS_ID);
      if (comments) comments.appendChild(script);

      return () => {
          const comments = document.getElementById(COMMENTS_ID);
          if (comments) comments.innerHTML = '';
      };
  }, []);

  return (
      <div id={COMMENTS_ID} />
  );
};

Comment 컴포넌트 삽입

  • 원하는 위치에 Comment 컴포넌트를 삽입한다.
  • 보통 블로그에서 사용하려면, 상세페이지에서 블로그 글 하단에 삽입할 것이다.

다크모드 구현하기

본 블로그는 next-themes(next-themes)라이브러리 + tailwindCSS를 사용하여 다크모드가 구현되어 있고, Utterances 댓글의 다크모드 구현도 마찬가지로 해당 라이브러리를 통해 아주 아주 간편하게 구현되어 있어서 Utterances의 다크모드 역시 손쉽게 구현할 수 있었다.

👉🏽 1분만에 Nextjs에서 다크모드 구현하기 (next-themes + tailwindCSS)

utterances에서 제공하는 테마

👉🏽 🔮utterances 에서 확인할 수 있다.

Comment 초기 Theme 설정

  • 만들어둔 Comment컴포넌트에서 useTheme Hook을 사용하여 분기처리만 해주면 된다.
  • theme이 아닌, resolvedTheme을 통해 분기처리를 한 이유는 enableSystem을 true로 설정했기 때문에, theme값(현재 적용된 theme)을 사용할 경우 그 값은 'light'나 'dark'가 아닌 'system'으로 반환될 수 있고, 이럴 경우 원하는 결과가 나오지 않을 수 있기 때문이다. (상기 기재한 바와 같이, 수동으로 토글로 다크모드 전환을 하지 않을 경우 기본값은 'system'이 됨).
    • resolvedTheme을 사용하면 활성 테마가 "system"인 경우 시스템 기본 설정이 "dark"인지 "light"인지를 반환하기에, 실제 적용된 테마에 따라 분기처리가 가능하다.
  • 참고로 theme값은 로컬스토리지에 저장되기 때문에, 사용자가 재방문 시 마지막 설정된 값으로 적용된다.
'use client';

import { useTheme } from "next-themes";
import React, { useEffect } from "react"

const COMMENTS_ID = 'comments-container';

export default function Comment() {
    const { resolvedTheme } = useTheme();
    const utterancesTheme = resolvedTheme === 'light' ? "github-light" : "photon-dark" ;

    useEffect(() => {
        const script = document.createElement('script');
        script.src = 'https://utteranc.es/client.js';
        script.setAttribute('repo', "carrotpieOwO/ha0peno-comment");
        script.setAttribute('issue-term', 'pathname');
        script.setAttribute('theme', utterancesTheme);
        script.setAttribute('crossorigin', 'anonymous');
        script.async = true;

        const comments = document.getElementById(COMMENTS_ID);
        if (comments) comments.appendChild(script);

        return () => {
            const comments = document.getElementById(COMMENTS_ID);
            if (comments) comments.innerHTML = '';
        };
    }, []);


  return (
      <div id={COMMENTS_ID} />
  );
};

동적으로 theme 변경하기

  • 다크모드 버튼 컴포넌트에서 수동으로 theme이 변경될 시, utterances theme을 바꿔준다.
  • utterances는 iframe으로 생성되기 때문에 window.postMessage()메서드를 사용해서 iframe에 메세지 이벤트를 전달해야 한다.
  • 이를 활용하여 HTMLIframeElement.contentWindow속성을 통해 iframe에 접근하여 테마가 변경될 때 set-theme이벤트를 전달하도록 하였다.
"use client"
import { useTheme } from "next-themes"

export default function DarkModeBtn() {
    const { systemTheme, theme, setTheme } = useTheme()
    const currentTheme = theme === "system" ? systemTheme : theme
    
    
    useEffect(() => {
        const isComment = document.querySelector('iframe.utterances-frame');
        if (isComment) {
            const utterancesTheme = theme === 'light' ? "github-light" : "photon-dark" ;
            const utterancesEl = document.querySelector('iframe.utterances-frame') as HTMLIFrameElement;

            (
                utterancesEl?.contentWindow?.postMessage(
                { type: "set-theme", theme: utterancesTheme },
                "https://utteranc.es/"
            )
            )
        }
    }, [systemTheme, theme])



    return (
        <div>
            <button 
                className="bg-pink-100 dark:bg-white flex items-center transition duration-300 focus:outline-none shadow"
                onClick={() => {
                    setTheme( currentTheme === 'dark' ? 'light' : 'dark')
                }}
            >
                {
                    currentTheme === 'dark' ? 
                        <div>
                           ...다크모드 버튼
                        </div>   
                    :
                        <div
                           ...라이트모드 버튼
                        </div>  
                }
            </button>
        </div>
    )
}