[nextjs] 개인blog에 dark mode적용 2

개잼·2023년 8월 14일
0
post-thumbnail

인사

안녕하세요.
이번에는 저번에 작성했던 dark mode 적용에 대해 재접속했을 시 기존에 사용했던 mode가 똑같이 적용되는 것을 소스코드로 어떻게 작성하는지를 소개해보겠습니다.


크게 2가지 방법이 있습니다. 하나는 localStorage를 이용하는 방법이고, 다른 하나는 cookie를 이용하는 방법입니다.
이 부분에 대해서 개인 선호 차이가 있을 수 있지만 저는 cookie 사용을 권장합니다.

Why ?
우선 기존의 react의 경우는 CSR(client side rendering)으로 client에서 localStorage를 읽어오는 과정에서 사용자가 dark mode를 적용 했음에도 rendering시간으로 인해 잠깐 light mode가 되었다가 dark mode가 되는 현상을 초래할 수 있기 때문입니다.
이 또한 적절한 rendering 방식을 이용하여 해결할 수 있겠지만 어찌됐건 rendering 하는데 약간의 시간이 필요함은 변함이 없습니다.

더불어 nextjs의 경우 기본적으로 SSR(Server side rendering)을 통해 component를 rendering하기 때문에 이에 맞는 적절한 방식을 선택해야 하는데요..

그래서 어쩔티비?
localStorage경우는 server component에서 호출이 불가능합니다.
즉 nextjs에서 SSR을 통한 rendering이 localStorage를 사용했을 때 불가능하단 이야기죠. 따라서 localStorage를 이용하시는 것 보단 cookie를 이용하는 것이 더 적절하다고 생각이 듭니다.


2. header.tsx

우선 저의 source code에서는 header에서 dark mode변경 유무에 대한 button이 있습니다.
더불어 뒤에 설명드릴 ThemeButton.tsx는 client component이기 때문에 앞서 설명드린 dark mode를 적절하게 사용하기 위해선 server component에서 cookie를 불러오는 것이 올바른 사용법입니다.

import Link from "next/link";
import IndicatorScroll from "./IndicatorScroll";
import ThemeButton from "./ThemeButton";
import { cookies } from "next/dist/client/components/headers";

const Header:React.FC = () => {
    let resCookie:string = cookies().get('mode')?.value ?? 'light';

    return (
        <nav 
            className="sticky top-0 backdrop-blur-md">
            <div className="flex justify-between p-10 font-play shadow">
                <div>
                    <Link href="/">SangEok</Link>
                </div>
                <div>
                    <span className="px-5"><Link href="/post">Post</Link></span>
                    <span className="px-5"><Link href="/about">About</Link></span>
                    <span className="px-5"><ThemeButton resCookie={resCookie}/></span>
                </div>
            </div>
            <IndicatorScroll />
        </nav>
    )
}

export default Header;
  • resCookie
    기존에 cookie에 지정한 'mode'에 대한 값을 가져옵니다.
    만약 해당하는 값이 없으면 resCookie에 light라는 값을 default value로 넣습니다.
    그 후 이 값을 ThemeButton에 props로 전달합니다.

3. ThemeButton.tsx

'use client';

import { useTheme } from "next-themes";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSun, faMoon } from '@fortawesome/free-regular-svg-icons'

import { useEffect } from "react";

export default function ThemeButton({resCookie} : themeCookie) {
    const {theme, setTheme} = useTheme();

    useEffect(()=>{
        setTheme(resCookie);
    }, [])

    return (
        <>
            <button onClick={()=>{
                setTheme(theme === 'dark' ? 'light' : 'dark');
                let storageTheme = theme === 'dark' ? 'light' : 'dark';
                document.cookie = `mode=${storageTheme}; max-age=${3600*24*400}`;
            }}>
                {
                    // theme에 값이 없으면 resCookie로 먼저 rendering하고, 있으면 theme 값으로 rendering하라.
                    theme === undefined ? (
                        resCookie === 'dark' ? (
                            <FontAwesomeIcon icon={faSun}/>
                        ) : (
                            <FontAwesomeIcon icon={faMoon}/>
                        )
                    ) :
                    theme === 'dark' ? (
                        <FontAwesomeIcon icon={faSun}/>
                    ) : (
                        <FontAwesomeIcon icon={faMoon}/>
                    )     
                }
            </button>
        </>
    )
}
  • use client
    useEffect, useTheme를 사용하기 위해 client component로 지정하였습니다.

  • useEffect
    theme의 값을 cookie에서 얻은 값으로 변경해줘야 함.

  • Button
    button click 시, cookie에 해당 mode에 대한 값과 cookie를 얼마나 유지할 것인지에 대한 값을 지정하고 cookie에 저장

  • theme === undefined ?
    theme에 대한 삼항연산자. theme의 값은 잠깐이나마 undefined로 될 수 있기에 이러한 예외에 대한 렌더링을 삼항연산자로 해결함.

profile
천천히 나아가는 중

2개의 댓글

comment-user-thumbnail
2023년 8월 14일

잘 읽었습니다. 좋은 정보 감사드립니다.

1개의 답글