넥스트 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>
),
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 구조가 일치하게됨
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에 관한 포스트를 해봐야 겠다.