프로젝트에서 테이블을 만들던 중 다음과 같은 에러가 발생했다.
Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.
문제가 되는 코드는 아래와 같은 코드였다.
export const Td = ({ content }: TdProps) => {
if (typeof window === "undefined") return null;
return (
<td>
{content && (
<Image
src={`${process.env.NEXT_PUBLIC_STATIC_LINK}/ic_circle.png`}
alt="circle"
width={20}
height={20}
/>
)}
</td>
);
};
Td 컴포넌트는 클라이언트에서 content라는 값을 받아와 처리하기 위해 서버 컴포넌트에서 렌더링이 되면 null을 리턴 시켰다.
if (typeof window === "undefined")
그런데 중간에 위와 같이 props의 타입을 검사하여 조건부 렌더링을 실행할 때 Hydration 과정에서 오류가 발생한 것이다.
React에서는 Hydration 할 때, DOM 구조가 일치되어져 있다고 가정하는데, 조건부 렌더링 과정에서 DOM 구조가 달라져 해당 에러를 뱉었던 것이다.
조금 더 자세히 말하자면, Server Component가 렌더링이 시작되는 타이밍은 서버 내부, 즉 Node.js에서 실행이 된다.
처음 예상으로 node.js에서는 window가 없기 때문에 위와 같이 코딩하는게 맞다고 생각 했지만 저 부분이 렌더링 과정에서 생기는 오류라는 것이다....😢
<!-->server-->
...
<td></td>
...
서버에서 렌더링 된 경우는 window가 없기 때문에 위와 같다.
<!-->client-->
...
<td><img src="ImgURL" alt="circle" /></td>
...
하지만 클라이언트에서 HTML로 변환될 시 다음과 같아지게 된다.
이 부분에서 서버에서 렌더링된 결과물과 클라이언트에서 렌더링 된 결과물이 달라서 나타난 오류이다.
그렇다면 어떻게 고칠 수 있을까?
해결 방법은 간단하다.
'use client'
export const Td = ({ content }: TdProps) => {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
return (
<td>
{content && (
<Image
src={`${process.env.NEXT_PUBLIC_STATIC_LINK}/ic_circle.png`}
alt="circle"
width={20}
height={20}
/>
)}
</td>
);
};
위 처럼 클라이언트 컴포넌트로 바꾸고, useEffect를 통해 마운트 시점을 제어해주면 된다.
useEffect 같은 경우는 해당 컴포넌트가 전부 마운트가 되고 실행되기 때문에, hasMounted의 초기 상태가 false라면 useEffect가 실행되기 전엔 그대로 false이므로 Td 컴포넌트는 null을 리턴하게 된다.
<!-->server-->
...
<td></td>
...
<!-->client-->
...
<td></td>
...
그렇게 되면 Hydration 될 때 DOM이 동일하다고 판단되어 정상적으로 실행된다.
참고
https://www.joshwcomeau.com/react/the-perils-of-rehydration/
'Next.js' 카테고리의 다른 글
Next.js AWS EC2에 배포하기(1) (2) | 2024.11.09 |
---|---|
내 사이트 성능 개선하기(2) (2) | 2024.07.23 |
내 사이트 성능 개선하기 (0) | 2024.07.19 |
Next.js와 Tailwind CSS를 이용해 로컬 폰트 설정하기 (0) | 2024.03.02 |
Hydration 이해하기 (0) | 2023.09.17 |