[Next.js] Hydration failed : Expected server HTML to contain a matching 에러

SB·2023년 7월 15일
1

Next.js - Hydration failed 에러

넥스트 js 로 블로그 프로젝트를 만들던 중 'use client'를 사용하는 컴포넌트에서 발생한 에러이다.

Error: Hydration failed because the initial UI does not match what was rendered on the server.
Warning: Expected server HTML to contain a matching <div> in <p>

문제는 마크다운을 <div className // >로 감싸서 커스텀을 하다가 발생하였다. 위와같은 hydration 오류는 클라이언트에서 렌더링된 초기 UI결과와 서버에서 렌더링된 UI 결과가 다른 구조를 가지고 있어 발생한 오류였다.


  img: ({ src, alt, ...props }: ImageProps) => {
    return (
      <div className='flex justify-center'>
        <Image src={src} alt={alt} {...props} width={500} height={350} />
      </div>
    );
  },
 
  ...
  
   blockquote: ({ children }: { children: React.ReactNode }) => (
    <div className='quote none text-gray-900 dark:text-gray-200 border-l-4 border-purple-500 pl-4'>
      {children}
    </div>
  ),

에러메시지를 자세히 알아보면 이 에러는 주로 React 컴포넌트에서 클라이언트-서버 하이드레이션(hydration) 과정에서 발생하는 일치하지 않는 HTML 구조로 인해 발생한다. 여기서 Hydrate를 간단하게 설명하자면

hydrate는 React에서 사용되는 메서드로, 서버 측에서 렌더링된 정적 페이지와 번들링된 JavaScript 코드를 클라이언트에 전송한 후, JavaScript 코드가 HTML DOM 위에서 한번 더 렌더링을 하면서, 각자 자기 자리를 찾아가며 매칭되는 과정이다.


<공식문서 참고>
https://nextjs.org/docs/messages/react-hydration-error


해결방법으로는

next/dynamic을 통한 lazy loading을 이용하는 해결하는 방법이다.

next/dynamic을 사용하여 react-markdown 모듈을 동적으로 로딩 할 수 가 있는데, 여기서 next/dynamic을 활용하면 필요한 컴포넌트만 클라이언트에서 로딩이 되고 SSR을 비활성화하면 서버 측에서 해당 컴포넌트를 렌더링하지 않고 번들에 포함되지 않아 번들 크기가 줄어들고 초기 로딩 속도가 개선되는 장점이 있다.


const DynamicReactMarkdown = dynamic(() => import("react-markdown"), {
  ssr: false, // 클라이언트에서만 렌더링
});

주요 포인트: DynamicReactMarkdown은 import("react-markdown")을 통해 동적으로 react-markdown 모듈을 로드 -> ssr: false 옵션을 통해 서버 사이드 렌더링(SSR)을 사용하지 않도록 설정

이렇게 하면 해당 컴포넌트가 클라이언트에서만 렌더링되므로, 서버와 클라이언트 간의 HTML 구조가 일치하게됨


마크다운 렌더링 되는 부분은 DynamicReactMarkdown 태그로 감싸주었다.
  
export default function MarkdownViewer({ content }: { content: string }) {
  return (
    <DynamicReactMarkdown
      rehypePlugins={[rehypeRaw]}
      className='prose max-w-none item-center text-gray-900 dark:text-gray-100'
      remarkPlugins={[remarkGfm]}
      components={components as any}
    >
      {content}
    </DynamicReactMarkdown> // Markdown 렌더링의 동작을 제어
  );
}
  

이렇게 해결 된줄 알았으나 빌드과정에서 타입에러가 떠서 코드를 다시 작성했다. ㅜㅡ
그리고 dynamic 메서드를 사용해면 해당 컴포넌트 렌더가 두번 되는 이슈가 발생한다고 하는데 좀 더 고민해봐야할 문제인 것 같다.

이 기회로 hydrate 개념에 관해 좀 더 자세히 알아보는 계기가 된 것 같다.
추후에 Next.js의 Hydrate에 관한 포스트를 해봐야 겠다.

profile
developerr

0개의 댓글