본문 바로가기
NextJS

[NextJS] React Hydration Error 해결하기

by limew 2024. 4. 8.

 

 

포폴을 nextjs로 만드는 중 위와 같은 에러를 만났다

Hydration failed because the initial UI does not match what was rendered on the server

 

초기 UI와 서버상의 렌더링결과가 match되지 않는댄다

 

 

공식 문서에서 해답을 찾을 수 있었다

While rendering your application, there was a difference between the React tree that was pre-rendered from the server and the React tree that was rendered during the first render in the browser (hydration).

 

오류가 발생하는 근본적인 이유는 

  • ssr에서 처음에 내려주는 UI(pre-render)와 브라우저에서 첫 번째로 렌더되는 UI 트리간에 차이가 있기 때문에 난 문제다.
  • 첫 번째 렌더는 React의 특징인 Hydration이라고 불린다.
  • 즉, React 트리가 DOM과 동기화되지 않아 발생하게 되는 문제다.

Hydration은

  1. server에 의해 pre-render된 html의 DOM tree를 렌더하고
  2. 이 tree에 이벤트 핸들러를 바인딩하는 역할을 한다.
    이후에 사용자는 interactive 가능하게 되는 것이다.

Hydration을 사용하면 좋은점

  1. 초기 로딩속도 감소
    클라이언트에서 렌더링될 때는 paint함수가 호출되서 실제로 페이지를 그리는게 아니라, pre-render한 dom tree와 매칭만 하는 것이다. 따라서 성능적으로도 더 좋다.
  2. 검색엔진 최적화

 

 

 

공식문서에는 아래와 같이 hydration error가 생기는 여러가지 원인을 알려주는데

그 중 2, 3번인 window, localstorage가 나에게 적용되는 원인이였다.

 

 

 

theme훅에서 localstorage에 저장된 이전의 theme을 가져오는 부분을 작성했는데

NextJS가 SSR이라서 document로부터 root를 알수 없고, window의 타입을 체크할 수 없다

 

즉, 서버사이드에서 pre-rendering된 React 트리와 브라우저에서 처음 rendering되는 React 트리가 달랐기 때문에 hydration 에러가 발생한 것이였다

const useThemeSwitcher = () => {
	const [theme, setTheme] = useState<any>(
		typeof window !== 'undefined' ? localStorage.theme : ''
	);
	const activeTheme = theme === 'dark' ? 'light' : 'dark';

	useEffect(() => {
		const root = window.document.documentElement;

		root.classList.remove(activeTheme);
		root.classList.add(theme);
		localStorage.setItem('theme', theme);
	}, [theme, activeTheme]);

	return {activeTheme, setTheme};
};

 

 

해결법은

브라우저와 연관된 부분( window, localstorage )은 useEffect를 사용해서

의도적으로 브라우저와 서버가 동일한 콘텐츠를 렌더링한 뒤에 보여준다.

 

const [isClient, setIsClient] = useState(false);

useEffect(() => {
  setIsClient(true);
}, []);
  
return (<div>{isClient && <div>{브라우저와 연관된 콘텐츠}</div>)}</div>);