back

Posts

ReactDOMClient.hydrateRoot사용 시, hydration 오류

2023-08-10

회사에서 담당한 프로젝트를 리팩토링 하던 중, dangerouslySetInnerHTML을 사용한 컴포넌트에서 hydration 오류가 발생하여 이를 해결한 경험을 남겨본다.

우선, hydration이란?

hydration은 서버에서 만들어진 HTML이 클라이어트단으로 넘어온 뒤, HTML과 JS를 결합하는 과정이라 생각하면 된다. 이 과정에서 서버에서 생성된 HTML과 브라우저에서 처음 렌더링 되는 HTML은 동일해야하며, 만약 동일하지 않다면 hydration오류가 발생한다.

그래서 문제가 뭔데?

우선 해당 컴포넌트는 아래의 역할을 하고 있다.

  1. Admin에서 HTML을 문자열 형태로 전송
  1. 해당 HTML중 dataset.type이 설정된 태그는 클라이언트측에서 CSS수정
  2. 수정된 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(
        ReactDOMClient.hydrateRoot(
          dom,
        		<CSSModifyComponent />
        ))
	}
},[])