[Next.js] react-hydration-error

dee·2022년 12월 16일
0

Next.js

목록 보기
2/3
post-thumbnail

doWork를 Next.js로 마이그레이션 중, snack bar 컴포넌트를 createPortal를 써서 구현하려고 했다. 그러던 중 만난 아래와 같이 document is not defined... '왜 오류가 났을까?'하며 원래 코드부터 분석하기로 했다.


🤔 document를 왜 모를까??


내가 작성한 코드는 아래와 같다. React에서 createPortal을 써봤다 익숙한 모습이다. 두번째 인자로 넘겨준 컨테이너 DOM 노드에서 오류가 나고 있다.
🧷 createPortal 개념이 궁금하다면 여기로!!

// Snack Bar 1
const SnackBar = (props: TProps) => (
  <>{createPortal(<SnackBarContainer {...props} />, document.querySelector('#snack__bar') as HTMLDivElement)}</>
);

Next.js는 서버사이드 렌더링으로 컴포넌트를 미리 렌더링하여 html을 만들게 되는데 이 때 클라이언트의 window, document를 모르기 때문에 생겨나는 문제였다. 그렇기 때문에 DOM이 생성되었는지 확인할 필요가 있다. 렌더하기 전에 typeof로 window나 document를 체크하여 에러를 해결할 수 있다는 방법을 찾았고 아래와 같은 코드로 다시 작성하여 npm run dev를 하였다.

// Snack Bar 2
const SnackBar = (props: TProps) => {
  const element = typeof window !== 'undefined' && document.querySelector('#snack__bar');

  return element ? createPortal(<SnackBarContainer {...props} />, element as HTMLDivElement) : null;
};

결과는....성공...이 아닌 다시 에러가 났다ㅠㅠ. 이번 에러 내용을 살펴보면 Hydration이 실패했다고 뜬다.


🤔 Hydration Error가 뭐야??

https://nextjs.org/docs/messages/react-hydration-error 에 가서 자세한 내용을 살펴보니 pre-rendering된 React 트리와 브라우저에서 첫번째 렌더링하는 동안 렌더링된 React 트리 사이의 차이가 발생하기 때문에 나는 오류였다. 이는 더 찾아보니 개발 모드에서 React는 hydration하는 동안에 mismatch가 일어나면 경고한다고 한다. 드물게 일어나는 일이긴 하지만 퍼포먼스 측면에서 중요하기 때문에 주의할 필요가 있다.


🧐 그럼 이제 위를 토대로 'Snack Bar 2'코드로 문제를 분석해보자.

  1. pre-rendering때는 element가 false로 해당 컴포넌트가 null이 반환되었다.
  2. 브라우저에서 첫번째 렌더링하면서 이제 window를 알게 되었고 element는 document를 찾아 해당 id의 요소를 갖게 되었다. 이에 컴포넌트는 createPortal 함수의 값을 반환하게 된다.
위 비교 결과 >>>> 1의 값 !== 2의 값
  • 최종 결과
    html과 인터렉션 기능을 연결하는 hydration 과정에서 html이 다르니 혼란이 올 것이고 이에 mismatch라고 React가 판단. 최종적으로 hydration이 실패하게 된 것이다.

😎 Hydration error 해결하기

이제 Hydration Error가 나지 않도록 코드를 수정해보자. mismatch 에러가 나지 않도록 useEffect를 사용하여 해결하였다. DOM이 mount된 이후에 element의 상태값을 업데이트하여 Snack bar가 그려지도록 하였다. 아래와 같이 구현함으로써 document 요소를 알 수 있고 Hydration error도 해결이 되었다.

const SnackBar = (props: TProps) => {
  const [element, setElement] = useState<HTMLElement | null>(null);

  useEffect(() => {
    setElement(document.getElementById('snack__bar'));
  }, []);

  if (!element) {
    return null;
  }

  return createPortal(<SnackBarContainer {...props} />, element);
};

🍑 공부 일기

hydration 원리를 이해할 때 사실 Next.js가 그런식으로 특화되어있나 보다라고 생각했다. 하지만 알고보니 서버사이드로 렌더링을 하게되면 일어나는 현상이였다. 이런 부분을 보면 오류를 경험해보면서 이런 원리들을 속속히 알아갈 필요가 있다고 느낀다.


참고
https://velog.io/@hyeonq/Next.js-Hydration-failed-error
https://nextjs.org/docs/messages/react-hydration-error
https://reactjs.org/docs/react-dom.html#hydrate

profile
웹 프론트엔드 개발자

0개의 댓글