back

Posts

ReactDOMClient.hydrateRoot사용 시, hydration 오류

2023-08-10

회사에서 프로젝트를 리팩토링하던 중, dangerouslySetInnerHTML을 사용한 컴포넌트에서 hydration 오류가 발생했습니다. 어떻게 해결했는지 공유해보려고 합니다.

1. Hydration이란?

Hydration은 서버에서 만들어진 HTML이 클라이언트로 넘어온 뒤, HTML과 JavaScript를 결합하는 과정입니다.

핵심 원칙:

  • 서버에서 생성된 HTML과 클라이언트에서 처음 렌더링되는 HTML은 완전히 동일해야 합니다
  • 만약 둘이 다르다면 hydration 오류가 발생합니다

2. 문제 상황

해당 컴포넌트는 다음과 같은 역할을 하고 있었습니다:

  1. Admin에서 HTML을 문자열 형태로 전송받습니다
  2. 해당 HTML 중 dataset.type이 설정된 태그는 클라이언트에서 CSS를 수정합니다
  3. 수정된 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 />);
    }
  });
}, []);

3. 원인 분석

이 코드에서 hydration 오류가 발생한 이유는 hydrateRoot를 잘못된 상황에서 사용했기 때문입니다.

hydrateRoot란?

React 공식 문서에서 설명하는 hydrateRoot의 역할은 다음과 같습니다:

createRoot()와 동일하지만, HTML 컨텐츠가 ReactDOMServer로 렌더링된 컨테이너를 hydrate 할 때 사용합니다. React는 기존 마크업에 이벤트 리스너를 연결하려 시도할 것입니다.

핵심 포인트:

  • hydrateRoot새로운 HTML을 만드는 것이 아닙니다
  • 이미 서버에서 만들어진 HTML에 이벤트 리스너만 붙이는 역할입니다

문제점

우리 상황에서는:

  • Admin에서 받은 HTML을 클라이언트에서 새롭게 CSS를 수정하고 있습니다
  • 즉, 새로운 렌더링 결과물을 만들어야 하는 상황이죠

따라서 hydrateRoot를 사용하는 것은 적절하지 않았고, 이로 인해 오류가 발생한 것입니다.

4. 해결 방법

해결 방법은 간단합니다. 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 />);
    }
  });
}, []);

정리

메서드사용 시점동작 방식
hydrateRoot서버에서 렌더링된 HTML이 이미 있을 때기존 HTML에 이벤트 리스너만 연결
createRoot클라이언트에서 새로운 HTML을 만들 때새로운 DOM을 생성하고 렌더링

Hydration 오류를 만났다면, 서버와 클라이언트의 렌더링 결과가 다른지, 또는 적절한 메서드를 사용하고 있는지 확인해보세요!