Next.JS hydration 스타일 이슈 파악하기

Next.JS를 사용해 웹을 만들어가다보면, 어느 순간 Hydration 이슈를 마주치게 된다. 이번엔 그 상황이 언제, 왜 생겨나는지를 파악해보고, 이걸 피해가를 방법을 알아보자.

Problem of Hydration

  1. 렌더링한 결과물이 어떤 컴포넌트인지 확인하고,
  2. 각 컴포넌트에 걸린 이벤트 들을 실제 DOM에 걸어주는 동작을 하게 된다.

하이드레이션이 잘못되었을 때, 우리가 마주하는 문제들은 거의 1번 과정이 잘못되어서 일어난다. 긴 말 하지 않고, 코드를 보자.

SSR 시에만 렌러링되는 어떤 컴포넌트가 있고

CSR 시에만 렌더링되는 다른 컴포넌트가 있다

그리고 항상 렌더링되는 또다른 컴포넌트가 있다.

이 페이지에 접속하면 어떻게 보일까?

이게 대체 무슨 일이람!

Why this happened?

서버사이드 렌더링은 잘 된것 같다.

대체 무슨 일이 발생한걸까?

Next.JS에서 내부적으로 사용하는 ReactDOM.hydrate 함수는 다음과 같은 일을 한다.

  1. 서버에서 받아온 DOM tree와 자체적으로 렌더링한 tree를 비교한다.
  2. 두 tree 사이의 diff를 얻어낸 뒤, 자체적으로(클라이언트사이드) 렌더링 한 tree에 맞춰 patch를 적용한다.

이 스텝을 따라가보자.

Digging — part 1.

땡!

리액트는 렌더링 된 트리가 아닌, 마운트 되기 전의 트리와 비교한다.
(훅을 사용한다면, useEffect가 실행되기 전의 상태를 가지고 비교한다.)
아마도 서버사이드 렌더링 결과와 환경을 맞춰 비교하기 위해서- 인 듯 하다.
때문에, 실제 비교가 일어나는 환경은 다음과 같다.

땡!

하이드레이트 함수의 설명을 잘 읽어보면 경고문이 있다.

There are no guarantees that attribute differences will be patched up in case of mismatches.

그러니까 저 경고의 함의는, 리액트 하이드레이션은 텍스트나 속성값은 비교하지 않는다. 리액트가 비교하는 트리는 아래와 같다. 결국 렌더링된 엘리먼트 타입과 순서만 비교한다는 뜻이다.

이제 조금 느낌이 오는가? 여기에 추가적인 논의를 이어가기 위한 표지를 좀 추가해보자.

그러니까 리액트의 입장에서는, 아래와 같은 판단을 하게 된다.

  1. $S0 === $C0
  2. $S1 는 제거되었군.
  3. $C0 는 Always 컴포넌트가 렌더링 된 결과물이군.

Digging — part 2.

  1. $S0에는 Always 컴포넌트를 적용시킨다.
    — 이때 $S0의 attribute(style)는 손대지 않는다. 왜냐하면, 하이드레이션 스텝은 이미 렌더링된 돔 트리가 어떤 컴포넌트에 해당하는지 파악하고 이벤트를 걸어주기 위한 스텝으로 의도되었기 때문이다. style=“color:red” 값이 그대로 살아있게 된다.
  2. componentDidMount() 를 호출한다 — 혹은 useEffect() 를 호출한다.
    <Always /> 컴포넌트의 앞에, <CSR /> 컴포넌트가 렌더링된다.

사실, 이 상태에서 클라이언트 사이드에서 재 렌더링이 발생할 경우, props를 업데이트 하게 되면서 바람직한 상태로 렌더링이 된다. 이 문제는 서버사이드 렌더링 후 하이드레이션으로 돔 렌더링이 끝나버리는 페이지에서만 발생한다.

코드는 멍청하지 않다. 그냥 로직을 따랐을 뿐.

SO HOW TO SOLVE IT?

정석적인 해결 방법

여기에도 두 가지 방법이 있다.

  1. return null 대신 return <div />
  2. return null 대신 visibility: hidden; 또는 display: none;

꼼수같은 해결 방법

리액트가 두 컴포넌트를 어떻게든 구분할 수 있게 해준다.

  1. 서로 다른 엘리먼트를 사용한다. 특히 이렇게 렌더링이 되다 말다 하는 컴포넌트의 루트를 div, span, section, p, 같은 대체 가능한 엘리먼트로 변경한다.
  2. 혹은, 사이 사이에 <div />가 아닌 요소를 삽입해준다.

2번은 이런 방식이다.

Conclusion

이런 데이터에 오락가락하는것까지 탓할 순 없다.

Frontend Developer, mainly using React.js

Frontend Developer, mainly using React.js