Nextjs에서 Portal을 만들던 중 다음과 같은 에러가 발생했습니다.
Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.
문제가 발생한 Portal의 코드는 아래와 같았습니다.
function Portal(props: PortalProps) {
const { children } = props;
if (typeof window !== 'object') {
return <></>;
}
const element = document.getElementById('portal');
if (!element) {
return <></>;
}
return ReactDOM.createPortal(children, element);
}
중간에 아래와 같은 window의 type을 검사하는 코드가 있는데
if (typeof window !== 'object') {
return <></>;
}
Next.js가 SSR 방식을 사용하기 때문에 이 코드가 없으면 아래의 오류가 발생했고 이를 해결하고자 window의 type 검사 코드를 넣으니 Hydration error가 발생한 것이었죠.
Server Error
ReferenceError: document is not defined
This error happened while generating the page. Any console logs will be displayed in the terminal window.
다시 본론으로 돌아와서 이 Hydration error가 발생한 이유를 알기 위해선 Hydrate가 무엇인지 알아야 합니다.
Hydrate를 간략하게만 설명하자면 서버사이드에서 rendering된 정적 페이지와 번들링된 js코드를 클라이언트에게 보낸 후 js코드가 HTML DOM 위에서 다시 rendering 되면서 서로 매칭되는 과정입니다.
즉 이 코드가 들어가면서
if (typeof window !== 'object') {
return <></>;
}
서버사이드에서 pre-rendering된 React 트리와 브라우저에서 처음 rendering되는 React 트리가 달랐기 때문에 발생한 에러였죠.
Next.js의 공식 사이트에서 해결 방법을 찾을 수 있었어요.
https://nextjs.org/docs/messages/react-hydration-error
문서를 참고하여 위에서 작성한 Portal 코드를 아래와 같이 수정했더니 문제가 해결되었습니다.
function Portal(props: PortalProps) {
const { children } = props;
const [element, setElement] = useState<HTMLElement | null>(null);
useEffect(() => {
setElement(document.getElementById('portal'));
}, []);
if (!element) {
return <></>;
}
return ReactDOM.createPortal(children, element);
}
서버사이드에서 document 객체에 접근하는 것을 막기 위해 문제가 되는 코드를 삽입한 것이었는데 클라이언트사이드에서 실행되는 useEffect 내에서 document 객체를 참조하면 문제를 해결할 수 있었습니다.
현규님 도움이 많이되었어요 감사합니다 ㅎㅎ