개발 학습일지(TIL)

TIL : Next.js 13 Provider 문제 : 다크모드가 안 되는 이유

Veams 2023. 5. 19.

문제상황

개발환경

Next.js 13.4.2

TailwindCSS

next-themes 라이브러리를 활용

 

다크모드를 구현하려했다.

https://github.com/pacocoursey/next-themes

 

GitHub - pacocoursey/next-themes: Perfect Next.js dark mode in 2 lines of code. Support System preference and any other theme wi

Perfect Next.js dark mode in 2 lines of code. Support System preference and any other theme with no flashing - GitHub - pacocoursey/next-themes: Perfect Next.js dark mode in 2 lines of code. Suppor...

github.com

 

사용법이 다 나와있어서 설명문을 보고 그대로 적용하면 다 되는 줄 알았는데, 버튼이 대체 작동을 안 한다.

도대체 문제는 무엇인가. 아마 내가 잘못 적용했겠지.

버튼을 클릭 할 때 콘솔 로그를 찍어보니  theme에 저장된 값이 undefined로 나온다.

 

TailwindCSS의 문제인가

버튼 코드의 문제인가

localStorage의 문제인가

내 컴퓨터의 문제인가? 다른 사람이 구현한 코드는 또 잘 작동한다. 혼자 끙끙대길 수 시간 째.

 

// layout.js
export default async function RootLayout({ children }) {
  let session = await getServerSession(authOptions);

  return (
    <html suppressHydrationWarning>
      <body> 
        <Header />
          <Providers>{children}</Providers>
        <Footer />
      </body>
    </html>
  );
}
//providers.js

"use client";

import { ThemeProvider } from "next-themes";

export function Providers({ children }) {
  return (
          <ThemeProvider attribute="class">
              {children}
          </ThemeProvider>
    )
}
//DarkModeBtn.js
"use client";

import { useTheme } from "next-themes";

export default function DarkModeBtn() {
  
  const { theme, setTheme } = useTheme();

  return (
    <>
      <button
        className="
                    inline-flex items-center
               border-0 py-1 px-3 rounded text-base mt-4 md:mt-0
                focus:outline-none
                bg-gray-100
                hover:bg-gray-50
                hover:text-orange-500
                dark:bg-slate-600
                dark:text-slate-400
                dark:hover:bg-slate-700
                dark:hover:text-yellow-300
                "
        type="button"
        onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
      >
        {/* 라이트 모드 */}
        <svg
          xmlns="http://www.w3.org/2000/svg"
          className="visible dark:invisible dark:h-0 dark:w-0 h-5 w-5"
          viewBox="0 0 20 20"
          fill="currentColor"
        >
          <path
            fillRule="evenodd"
            d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
            clipRule="evenodd"
          />
        </svg>

        {/* 다크모드 */}
        <svg
          xmlns="http://www.w3.org/2000/svg"
          className="visible dark:visible dark:h-5 dark:w-5 h-0 w-0"
          viewBox="0 0 20 20"
          fill="currentColor"
        >
          <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
        </svg>
      </button>
    </>
  );
}

 

문제 원인 분석 및 알게 된점

 

export default async function RootLayout({ children }) {
  let session = await getServerSession(authOptions);

  return (
    <html suppressHydrationWarning>
      <body>
        <Header />
          <Providers>{children}</Providers>
        <Footer />
      </body>
    </html>
  );
}

export default async function RootLayout({ children }) {
  let session = await getServerSession(authOptions);

  return (
    <html suppressHydrationWarning>
      <body>
      <Header />
        <Providers>
          <DarkModeBtn />
          {children}
        </Providers>
        <Footer />
      </body>
    </html>
  );
}

다크모드 버튼은 기존에 Header 컴포넌트 내에서 구현되어 있는데,

혹시나 해서 다크모드 버튼을 layouts.js에서 provider 컴포넌트 안에 넣어서 작동해보니, 잘 작동하고 있었다.

-> 즉, 버튼 위치에 따라 작동 유무가 갈리는 것으로 보아 구현된 버튼에는 문제가 없는 것이다!

 

layout.js 에 대한 코드블럭 중에서 Header 컴포넌트 적용이 잘못되어 있던 것이다.

provider 컴포넌트 사용시, 컴포넌트 간의 구조에 대해서 이해하지 못한 것으로 발생한 문제이다.

 

 

공식문서를 보면 이에 대한 예시를 살펴 볼 수 있다.

https://nextjs.org/docs/getting-started/react-essentials#rendering-third-party-context-providers-in-server-components

 

Getting Started: React Essentials | Next.js

To build applications with Next.js, it helps to be familiar with React's newer features such as Server Components. This page will go through the differences between Server and Client Components, when to use them, and recommended patterns. If you're new to

nextjs.org

해법

이전 layout.js 코드는 다크 모드가 적용되려면 다크모드 버튼이 있는 Hedader 컴포넌트Providers(ThemeProvider) 컴포넌트 안에 감싸져 있어야 하는 것이다. 그래서 아래와 같이 다시 구성했다.

export default async function RootLayout({ children }) {
  let session = await getServerSession(authOptions);

  return (
    <html suppressHydrationWarning>
      <body>
        <Providers>
          <Header />{children}<Footer />
        </Providers>
      </body>
    </html>
  );
}

버튼은 이제 잘 작동한다. 이제 모드에 따라서 색상 전환 구성을 다르게 바꾸면 된다.

 

 

참고

https://www.patterns.dev/posts/provider-pattern

 

Provider Pattern

Make data available to multiple child components

www.patterns.dev

 

댓글