next-themes로 다크 모드 구현 시 Extra attributes from the server: class, style 에러

김영준·2023년 11월 4일
3

에러 해결

목록 보기
2/4

nextjs 13 버전에서 next-themes를 이용해 다크 모드를 구현하던 중 아래와 같은 에러가 발생했다.

현재 providers와 ThemeButton 컴포넌트의 코드는 다음과 같다.

// Providers.tsx
'use client'

import { ThemeProvider } from 'next-themes'

const Providers = ({ children }: { children: React.ReactNode }) => {
  return <ThemeProvider attribute="class">{children}</ThemeProvider>
}

export default Providers
// ThemeButton.tsx
'use client'

import { useTheme } from 'next-themes'

const ThemeButton = () => {
  const { theme, setTheme } = useTheme()

  return (
    <button
      className="fixed bottom-2 right-2 rounded-md border p-2 text-xs"
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      테마 버튼
    </button>
  )
}

export default ThemeButton

에러 발생 원인

server에서 첫 렌더링 시 useTheme의 theme는 서버에서 알 수 없는 상태이기 때문에 undefined의 상태이고 ThemeProvider가 렌더링 되면 theme의 상태가 존재하므로 server와 client의 구조가 달라지게 되어 hydration 에러가 발생하는 것이었다.


hydration이란?

Next.js는 클라이언트에게 웹 페이지를 보내기 전에 Server side에서 Pre-Rendering을 한다.

그리고 Pre-Rendering으로 인해 생성된 HTML 문서를 클라이언트에게 전송한다. 이 때의 문서는 단지 웹 화면만 보여주는 HTML일 뿐이고, 자바스크립트 요소들이 등록되지 않은 상태이다.

이후에 리액트가 번들링 된 자바스크립트 코드들을 클라이언트에게 전송한다.
이 자바스크립트 코드들이 이전에 보내진 HTML DOM 요소 위에 한번 더 렌더링을 하면서 매칭이 된다. 이 과정을 hydration이라 한다.

suppressHydrationWarning의 값을 true로 설정하면 hydration 에러가 더 이상 발생하지 않는다는 글을 보았는데, 이와 같은 경우 hydration 에러를 무시하는 것과 다름이 없음으로 다른 방법을 찾아보았다.


해결 방안

Providers 컴포넌트에 현재 마운트 상태를 확인하는 상태 isMount를 false로 초기화하고 isMount가 false 일 때는 null을 리턴하고, true 일 때는 ThemeProvider를 리턴하도록 작성하면 해당 에러가 사라지는 것을 확인할 수 있다.

'use client'

import { useEffect, useState } from 'react'
import { ThemeProvider } from 'next-themes'

const Providers = ({ children }: { children: React.ReactNode }) => {
  const [isMount, setMount] = useState(false)

  useEffect(() => {
    setMount(true)
  }, [])

  if (!isMount) {
    return null
  }

  return <ThemeProvider attribute="class">{children}</ThemeProvider>
}

export default Providers

참고 문서

profile
프론트엔드 개발자

0개의 댓글