본문 바로가기
🤪 뜨거운 맛 오류 일기

hydration failed because the initial UI does not match what was rendered on server ! / 서버에서 랜더링되는 것과 브라우저 상에서의 미스매치 때문에 발생한 hydration 에러 해결일지

by 따따시 2023. 2. 13.

 

레시피 글쓰기 작업을 하는데 요런 에러가 발생했었다

 

에러창 잠깐 닫고 버튼같은거 누르면 다 정상 작동을 하는데

에러메세지를 보니 하이드레이션을 하는 과정 중에 서버에서 랜더한 UI와 매치가 되지 않는다는 것!

 

구글에 hydration에 대해서 이것저것 찾아보고 

next 공식문서에 들어가 저 에러가 어떤 경우에 발생하는 것인지 연구해봤다

(요건 넥스트 공식문서에 나온 리액트 하이드레이션 에러)

https://nextjs.org/docs/messages/react-hydration-error

 

react-hydration-error | Next.js

React Hydration Error While rendering your application, there was a difference between the React tree that was pre-rendered (SSR/SSG) and the React tree that rendered during the first render in the Browser. The first render is called Hydration which is a f

nextjs.org

 

공식문서에서 말하는

hydration-error는 왜 발생하는지 이유

 

While rendering your application, there was a difference between the React tree that was pre-rendered (SSR/SSG) and the React tree that rendered during the first render in the Browser. The first render is called Hydration which is a feature of React.

This can cause the React tree to be out of sync with the DOM and result in unexpected content/attributes being present.

 

나의 발해석: 프리랜더링되는 리액트 트리와 브라우저에서 일어나는 랜더링 사이에서 차이점이 발생하여 에러가 발생한다

 

 

 

 

 

 

 

 

 

 

 

 

에러 만난김에 공식문서 예제 뜯어보기

 

function MyComponent() {
  // This condition depends on `window`. During the first render of the browser the `color` variable will be different
  const color = typeof window !== 'undefined' ? 'red' : 'blue'
  // As color is passed as a prop there is a mismatch between what was rendered server-side vs what was rendered in the first render
  return <h1 className={`title ${color}`}>Hello World!</h1>
}

이 예제를 보면(이건 에러가 발생하는 코드임)

color가 window가 undefined가 아니면(있다는거자너) red를, undefined면 blue를 설정하도록 조건을 걸어놨는데

브라우저에서는 window라는 객체가 있어서 red가 되지만, server에서 랜더링한 경우에는 color가 undefined니 blue가 될것이다

브라우저에서 랜더링한거랑 서버에서 랜더링 한거랑 차이가 나니 에러가 발생하는 것

(마운트 되고 난 후에 red로 바뀌는거라서)

 


* 말장난같아서 찾아본 typeof window == 'undefined'의 의미

 

(1) 왜 이런식으로 식을 쓰는거지?

if(typeof window == 'undefined') means

 

1. onClick없이 실행된 코드라는걸 확인할 수 있다. 

2. 서버사이드 함수라고 확실히 할 수 있다.

 

(2) 그래서 뭘 얻고 싶은거지?

윈도우 객체가 존재하지 않다 = undefined

undefined가 아니다? => window객체가 존재한다.

 

(ㅋㅋㅋㅋㅋㅋㅋ아니 왜케 말장난같지 ㅠ)

 

(3) 더 궁금해서 스텍오버플로우에서 찾아본 답변

 

This can be used to detect whether code is running in a typical browser environment (e.g. an environment with a browser DOM) or in some other JS environment since the window object exists in a typical browser JS,

but does not exist in something like node.js or even a webWorker in a browser.

 

If the window object does not exist, then

typeof window === 'undefined'

so the code you asked about:

if (typeof window !== 'undefined') 

will execute the if block if the window object does exist as a top level variable.

In the specific code you linked, it is to keep from executing browser-targeted code that references DOM objects like document if the plugin happens to be used in a non-browser environment.


 

아무튼 각설하고 

이런 미스매치를 해결하기 위한 코드는 이런식으로 작성하면 된다고 넥스트에서 알려줌

 

// In order to prevent the first render from being different you can use `useEffect` which is only executed in the browser and is executed during hydration
import { useEffect, useState } from 'react'
function MyComponent() {
  // The default value is 'blue', it will be used during pre-rendering and the first render in the browser (hydration)
  const [color, setColor] = useState('blue')
  // During hydration `useEffect` is called. `window` is available in `useEffect`. In this case because we know we're in the browser checking for window is not needed. If you need to read something from window that is fine.
  // By calling `setColor` in `useEffect` a render is triggered after hydrating, this causes the "browser specific" value to be available. In this case 'red'.
  useEffect(() => setColor('red'), [])
  // As color is a state passed as a prop there is no mismatch between what was rendered server-side vs what was rendered in the first render. After useEffect runs the color is set to 'red'
  return <h1 className={`title ${color}`}>Hello World!</h1>
}

 

자 해석을 해보장

 

첫번째 랜더를 할때 미스매치 되는것을 예방하기 위해서 너는 useEffect를 사용하면 된다.

(와이? useEffect는 오즥 브라우저에서만 실행이 되기 때문 ㅇ_< , 또한 이건 hydration하는 동안에 실행이 된다)

 

처음 설정한 저 blue라는 디폴트 벨류는 pre-rendering을 하는 동안에 쓰여질겨, (브라우저에서 = hydration)

 

hydration을 하는 동안에 useEffect가 호출이 될건데, window라는 객체는 useEffect안에서 사용을 할 수 있음

우리는 우리가 브라우저안에 있다는것을 이미 알기 때문에 따로 window를 췌킹할 필요가 음슴

너가 뭔가 window로부터 무언갈 읽고 싶으면 yeah~ is fine~

setColor를 useEffect 안에서 부름으로써, 랜더는 hydrating 후에 시작이 된다

이것은 browser만의 specific한 value를 이용가능하게 해준다. 

=> 요렇게 함으로써 color가 prop으로써 패스가 될거고, 이것은 미스매치가 일어나지 않는다.

 

 

 

이렇게 hydration에 대해서 어느정도 감을 잡고

콘솔창을 열어 에러메세지를 더 세부적으로 뒤져봤다

 

 

 

그리고 발견한 넘

잡았당 요놈!!!

 

 

바로바로 <br>은 <select>의 자식쉐끼로 올 수 없다는 것!!!!!!!!!!!!!!

 

 

 

저 에러메세지를 발견하자마자 씐나게 리턴문으로 가서 문제의 <br/>을 잡아냈다

 

바로 요부분에서 나고 있었음

 

저렇게 <select>안의 <br/>을 제거해주니 에러가 사라졌다

 

 

댓글