QueenCards / ProjectAnalysis

플젝뿌셔
1 stars 0 forks source link

[09] Hydration 에러 발생 시 대처방법이 있나요? (슬, 현지) #10

Closed hyeyoonS closed 5 months ago

hyeyoonS commented 5 months ago

📎 질문

Hydration 에러 발생 시 대처방법이 있나요?

✏ 구술 답변 키워드 [MypetLog]

SSR을 포함한 앱을 개발하다보면, 클라이언트에서만 존재하는 데이터를 사용해야하는 순간이 온다. (예: 유저의 timezone에 맞춘 시간, local storage)

이 데이터들은 서버에서 값을 알 수 없으므로, 클라이언트에서 데이터를 처리해야 한다.

서버에서도 이 내용을 처리하려고 하면, 서버에서 랜더링된 결과와 클라이언트 내용이 달라진다. 즉, hydration mismatch가 날 수 있다.

위 문제를 해결하기 위한 대표적인 방식으로는, useEffect를 활용하는 것이다. useEffect로 컴포넌트가 완전히 랜더링 될 때 까지 실행을 지연시키는 것이다. 하지만 이 방식은 두가지 결함이 있다.

useEffect는 다시한번 랜더링 하여, 상황에 따라 유저는 컨텐츠가 버벅거리는것처럼 보일 수 있다. SSR로 페이지를 접속한 경우가 아닌, CSR로 페이지를 접속한 케이스에서도 useEffect는 불필요하게 돌 수 있다. 이 글에서는 useEffect 문제를 보완할 두가지 방법을 소개한다

  1. suppressHydrationWarning
  2. useExternalSyncStore

다른 방식 1 : suppressHydrationWarning

function UsingSurpressWarning() {
  const [state] = useState(() => (isServer() ? "server" : "client"));
  return <div suppressHydrationWarning={true}>{state}</div>;
}
// 서버에서는 'server'가 내려오고 클라이언트는 'client'를 랜더링하려 하여
// hydration mismatch가 일어나야 suppressHydrationWarning 무시한다.
이 flag는 hydration mismatch에 따른 에러를 무시하게 해준다.

또, 위 코드처럼 useState를 활용해 초기값을 가져온다면 effect 실행 전, 첫 랜더링 사이클에 즉각적으로 데이터 반영을 할 수 있다. 따라서, 가장 빠르고 덜 어색하다. 이 방법은 주로 단순한 텍스트 내용의 변경에 유용하며, DOM 구조의 변경에는 적용할 수 없습니다. 예를 들어, 서버에서는

Example

와 같이 렌더링되고, 클라이언트에서는
Example
와 같이 렌더링하려고 하면, 이러한 구조적 차이는 suppressHydrationWarning으로 경고를 억제할 수 없다.

B-2: useExternalSyncStore [useExternalStore]라는 리액트 훅을 사용하는 방법이다.

이 훅은 본래 외부 데이터와 내부 리액트 랜더링 라이프사이클을 연결해주는 훅인데, 이 훅의 또 다른 특징은 CSR에서 마운트되었는지 SSR+Hydration에서 마운트 되었는지 구분할 수 있다.

function UsingExteranlSync() {
  const date = useSyncExternalStore(
    emptySubscribe,
    () => {
      return `client__${Date.now()}`; // 클라이언트 스냅샷 함수.
    },
    () => {
      return null; // 서버 스냅샷 함수.
    }
  );
  return date != null ? date : null;
}
스크린샷 2024-05-08 오후 4 14 18

✏ 구술 답변 키워드 [ListyWave]

✏ 서술 답변 [MypetLog]

✏ 서술 답변 [ListyWave]

Hydration 에러 발생 원인

에러 해결 방법

function Header() { // 서버와 클라이언트에서 각각 다른 결과값을 가지는 isLogin()의 결과값을 그대로 분기 조건으로 사용했다. return (

{isLogin() && ( )}

); }

위 코드는 클라이언트에서는 isLogin의 값이 true 혹은 false가 되지만 서버에서는 무조건 false가 반환되므로 react tree구조가 서로 다르다. useEffect를 사용해 isUserLogin값을 지정해주어 해결한다.
```tsx
function Header() {
 const [isUserLogin, setIsUserLogin] = useState(false)
 // isLogin()을 실행한 결과가 바뀐하면 isUserLogin 이라는 state를 변화시킨다.
  useEffect(()=>{
    setIsUserLogin(isLogin())
  },[isLogin()])

 // 그리고 return 문의 조건을 isUserLogin 으로 바꾸었다.
  return (
    <div>
      {isUserLogin && (
        <button onClick={logout}>logout</button>
      )}
    </div>
  );
}

useState를 사용해 만들어진 isUserLogin 상태는 초기값을 false(서버에서 실행했을 때와 같은 값)를 가지며, useEffect는 브라우저에서만 실행된 후 hydration과정을 거치기 때문에 에러가 발생하지 않는다.

Nahyun-Kang commented 5 months ago

보편적인 하이드레이션(Next.js에서 소개하는) 에러 발생 케이스와 대처 방법

하이드레이션 에러 발생 케이스

  1. 렌더링 로직에 window나 localStorage와 같은 브라우저 전용 API를 사용할 때 server와 client 환경의 조건을 다르게 하면 발생한다.
  2. HTML 태그가 잘못 중첩되었을 때 (ex. p 태그가 p 태그 안에 있을 때)
  3. typeof window !== 'undefined’ 타입 가드 로직을 렌더링 로직에 썼을 때 (왜 발생하는지,,)
  4. 시시각각 변화하는 시간 관련 함 (ex. Date() 생성자 함수와 같은 함수)를 렌더링 로직에 썼을 때
  5. 브라우저 익스텐션이 HTML을 변화시킬 때

하이드레이션 에러 대처 방법

olseul commented 5 months ago

SSR을 포함한 앱을 개발하다보면, 클라이언트에서만 존재하는 데이터를 사용해야하는 순간이 온다. (예: 유저의 timezone에 맞춘 시간, local storage)

이 데이터들은 서버에서 값을 알 수 없으므로, 클라이언트에서 데이터를 처리해야 한다.

서버에서도 이 내용을 처리하려고 하면, 서버에서 랜더링된 결과와 클라이언트 내용이 달라진다. 즉, hydration mismatch가 날 수 있다.

위 문제를 해결하기 위한 대표적인 방식으로는, useEffect를 활용하는 것이다. useEffect로 컴포넌트가 완전히 랜더링 될 때 까지 실행을 지연시키는 것이다. 하지만 이 방식은 두가지 결함이 있다.

useEffect는 다시한번 랜더링 하여, 상황에 따라 유저는 컨텐츠가 버벅거리는것처럼 보일 수 있다. SSR로 페이지를 접속한 경우가 아닌, CSR로 페이지를 접속한 케이스에서도 useEffect는 불필요하게 돌 수 있다. 이 글에서는 useEffect 문제를 보완할 두가지 방법을 소개한다

  1. suppressHydrationWarning
  2. useExternalSyncStore

다른 방식 1 : suppressHydrationWarning

function UsingSurpressWarning() {
  const [state] = useState(() => (isServer() ? "server" : "client"));
  return <div suppressHydrationWarning={true}>{state}</div>;
}
// 서버에서는 'server'가 내려오고 클라이언트는 'client'를 랜더링하려 하여
// hydration mismatch가 일어나야 suppressHydrationWarning 무시한다.
이 flag는 hydration mismatch에 따른 에러를 무시하게 해준다.

또, 위 코드처럼 useState를 활용해 초기값을 가져온다면 effect 실행 전, 첫 랜더링 사이클에 즉각적으로 데이터 반영을 할 수 있다. 따라서, 가장 빠르고 덜 어색하다. 이 방법은 주로 단순한 텍스트 내용의 변경에 유용하며, DOM 구조의 변경에는 적용할 수 없습니다. 예를 들어, 서버에서는

Example

와 같이 렌더링되고, 클라이언트에서는
Example
와 같이 렌더링하려고 하면, 이러한 구조적 차이는 suppressHydrationWarning으로 경고를 억제할 수 없다.

B-2: useExternalSyncStore [useExternalStore]라는 리액트 훅을 사용하는 방법이다.

이 훅은 본래 외부 데이터와 내부 리액트 랜더링 라이프사이클을 연결해주는 훅인데, 이 훅의 또 다른 특징은 CSR에서 마운트되었는지 SSR+Hydration에서 마운트 되었는지 구분할 수 있다.

function UsingExteranlSync() {
  const date = useSyncExternalStore(
    emptySubscribe,
    () => {
      return `client__${Date.now()}`; // 클라이언트 스냅샷 함수.
    },
    () => {
      return null; // 서버 스냅샷 함수.
    }
  );
  return date != null ? date : null;
}
스크린샷 2024-05-08 오후 4 14 18
wise-Ag commented 5 months ago

이제보니 개발초기에 hydration이 났었네요🤭 https://thrilling-taste-dd3.notion.site/hydration-error-d320e8b5c1f5431d96817e582ac54276?pvs=4