ReactDOMClient.hydrateRoot사용 시, hydration 오류
2023-08-10회사에서 담당한 프로젝트를 리팩토링 하던 중, dangerouslySetInnerHTML
을 사용한 컴포넌트에서 hydration 오류가 발생하여 이를 해결한 경험을 남겨본다.
우선, hydration이란?
hydration은 서버에서 만들어진 HTML이 클라이언트단으로 넘어온 뒤,
HTML과 JS를 결합하는 과정이라 생각하면 된다.
이 과정에서 서버에서 생성된 HTML과 브라우저에서 처음 렌더링 되는 HTML은 동일해야 하며, 만약 동일하지 않다면 hydration 오류가 발생한다.
그래서 문제가 뭔데?
우선 해당 컴포넌트는 아래의 역할을 하고 있다.
- Admin에서 HTML을 문자열 형태로 전송
- 해당 HTML 중
dataset.type
이 설정된 태그는 클라이언트 측에서 CSS 수정- 수정된 CSS를 포함해서
dangerouslySetInnerHTML
을 사용하여 렌더링
기존의 로직은 아래와 같이 ReactDOMClient.hydrateRoot
을 사용하고 있었다.
useEffect(() => {
const target: NodeListOf<HTMLElement> =
document.querySelectorAll(".target_class_name");
target.forEach((dom) => {
if (dom.dataset.type === "특정타입") {
ReactDOMClient.hydrateRoot(dom, <CSSModifyComponent />);
}
});
}, []);
해당 코드에서 hydration 오류가 발생한 이유는 ReactDOMClient.hydrateRoot
를 사용했기 때문이다.
React에서 설명하는 ReactDOMClient.hydrateRoot
는 다음과 같다:
createRoot()
과 동일하지만, HTML 컨텐츠가 ReactDOMServer로 렌더링된 컨테이너를 hydrate 할 때 사용합니다. React는 기존 마크업에 이벤트 리스너를 연결하려 시도할 것입니다.
즉, hydrateRoot
는 새로 렌더링된 결과물을 만드는 것이 아닌, 미리 만들어진 결과물에 이벤트 리스너만 부착하는 것이다.
따라서 새로운 HTML을 렌더링하기 위해서 hydrateRoot
를 사용하는 것은 적절하지 않으므로 오류가 발생했던 것이다.
해당 오류의 해결법은 간단한데,
ReactDOMClient.createRoot
를 사용하여 클라이언트 측에서 HTML을 다시 구성해주면 된다.
useEffect(() => {
const target: NodeListOf<HTMLElement> =
document.querySelectorAll(".target_class_name");
target.forEach((dom) => {
if (dom.dataset.type === "특정타입") {
const targetDom = ReactDOMClient.createRoot(dom);
targetDom.render(<CSSModifyComponent />);
}
});
}, []);