
포폴을 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은
- server에 의해 pre-render된 html의 DOM tree를 렌더하고
- 이 tree에 이벤트 핸들러를 바인딩하는 역할을 한다.
이후에 사용자는 interactive 가능하게 되는 것이다.
Hydration을 사용하면 좋은점
- 초기 로딩속도 감소
클라이언트에서 렌더링될 때는 paint함수가 호출되서 실제로 페이지를 그리는게 아니라, pre-render한 dom tree와 매칭만 하는 것이다. 따라서 성능적으로도 더 좋다. - 검색엔진 최적화
공식문서에는 아래와 같이 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>);