dolog
React와 Next.js, 그리고 Hydration 본문
React와 Next.js는 들어봤는데 Hydration이요?
저는 메인 프로젝트를 진행하면서 Hydration 에러를 만났었는데요. 당시에는 Hydration이라는 단어와 기술 자체가 생소했기 때문에 에러를 해결하기까지 다사다난했었습니다.
Hydration
먼저 Hydration을 검색하면 수화, 수분 보충, 수화 작용이란 말이 나옵니다.
당연한 얘기지만 웹 개발에서 Hydration은 실제로 수화작용, 수분 보충을 하기위해 사용하는 기술이 아닙니다.
그렇다면 왜 Hydration이란 단어를 선택했을까요? 웹 개발에서의 Hydration 정의를 알면 왜 선택했는지 짐작할 수 있습니다.
웹 개발에서 Hydration은 클라이언트의 JavaScript가 서버에서 랜더링한 html 요소에 이벤트 핸들러를 첨부하여 동적인 웹 페이지로 변환하는 기술을 말합니다.
조금 더 풀어서 설명하자면 서버에서 이미 렌더링 된 웹 페이지에 최소한의 JavaScript 코드가 연결되어 사용자에게 빈 페이지가 아닌 요소가 그려진 페이지가 보여지게 되고, 이후에 일반적인 인터랙티브(interactive)한 웹 페이지로 동작하는 과정이 Hydration 입니다.
즉, 정적인(건조한) html에 JavaScript 코드를 추가하여 촉촉하고 생기있게 동적인 웹 페이지를 만들어주는것이죠.
이러한 특징으로 해당 기술에 Hydration을 붙인거라 생각합니다.
사실 프로젝트 기획 당시까지만해도 렌더링 방식에 대해 깊이 고민하지 않아서 아예 모르는 기술이였습니다. 하지만 팀 회의때 Next.js의 서버 사이드 렌더링에 대해 토론하면서 정적 페이지와 동적 페이지를 분리해서 서버 사이드 렌더링과 클라이언트 사이드 렌더링의 각각의 장점만 뽑아서 좋은 서비스를 개발해보자하면서 관심이 가기 시작했습니다.
아무튼 좋은 마음을 가지고 개발을 진행하면서 초반에는 잠깐씩 나오다가, 시간이 지나자 아예 없어지지 않았던... Hydration 에러를 만나게 됐습니다.
어디서 굴러온(?) Hydration 이슈인가
처음에는 당최 저 에러가 어디서 발생했는지 감을 잡지 못했습니다. 보통은 에러가 나면 에러가 발생한 라인을 체크해주는데, 이 에러는 어디서 발생하는지 체크해주지 않았습니다. 그래도 에러 메시지를 읽으면서 어디서 발생한건지, 무엇때문에 발생한건지 더듬더듬 찾아갔습니다.
1.
Unhandled Runtime ErrorError: Hydration failed because the initial UI does not match what was rendered on the server.
초기 UI가 서버에서 렌더링된 것과 일치하지 않아 하이드레이션에 실패했습니다.
Warning: Expected server HTML to contain a matching <img> in <li>.
경고: <li>에 일치하는 <img>가 포함될 것으로 예상하는 서버 HTML
See more info here: https://nextjs.org/docs/messages/react-hydration-error
먼저 Header 컴포넌트에서 <Image> 태그 때문에 에러가 발생했다고 판단했습니다. 왜냐하면 당시 제가 구현한 컴포넌트 중 Header 컴포넌트에서만 <li>와 <Image> 태그를 사용하고 있었고, Header 컴포넌트의 <Image> 태그의 src를 조건부로 작성한게 서버랑 일치하지 않아서 발생한 문제겠거니하고 생각했습니다. 하지만 프로필 이미지를 설정하지 않았을 때 기본 프로필 이미지를 보여줘야 했기 때문에 조건부 렌더링을 대체할 방도가 없었습니다. 또한 여러 번 새로고침을 했을때는 에러가 사라져서 단순히 코드를 변경하고 저장하는 과정에서 일치하지 않아 나온 에러라고 생각했습니다.
그렇지만 다시 초기 렌더링을 진행하면 똑같은 에러가 발생해서 아예 최상위 layout.tsx에서 "use client"를 선언하고, 전체 페이지의 layout.tsx 마다 "use client" 선언해보는 등... 고쳐보려 했지만 에러는 계속 되었습니다.
그래서 공식 문서에 나와있는 케이스 중 3번째 케이스에 초점을 맞춰봤습니다.
3. Using browser-only APIs like window or localStorage in your rendering logic
렌더링 로직에 window 또는 localStorage와 같은 브라우저 전용 APIs 사용
'현재 로그인시 사용자의 정보를 localStorage에 저장하고 있고, 구글 로그인 때문에 window 객체를 사용했는데 이게 원인이였겠구나,
따라서 에러가 발생한 곳은 Header 컴포넌트가 아니라 로그인 파트의 컴포넌트에서 localStorage, window 객체를 사용한 부분에서 발생했나보다.'라고 판단했습니다.
하지만 Header 컴포넌트때와 같이 localStorage, window 객체를 사용해야만 하는 상황이였고, Next.js를 사용해 개발한 경험이 있는 팀원분께 여쭤봤을 때 window 객체를 사용해도 Hydration 에러가 나지 않았었다라는 답변을 받았었습니다.
결국 해결은 했지만...
어떻게 해결할까 고민하던 찰나 부트캠프 측에서 제공해주는 멘토링 시간이 있었습니다.
원인이라 생각한 컴포넌트들은 물론 모든 컴포넌트에 "use client"를 선언을 해줬고, 태그가 문제인가 싶어서 태그를 <div>로 모두 변경해보기도 했지만 해결할 수 없었던 에러에 관해 여쭤보았습니다.
멘토님께서는 '아 이거 Hydration 이슈예요. 서버 측 렌더링과 클라이언트 측 렌더링이 불일치해서 발생한거예요. '라고 말씀해주셨습니다.
처음엔 당황했습니다. '클라이언트 컴포넌트로 만들면 클라이언트 사이드 렌더링만 작동하는거 아니였나...?'라고 생각했기 때문이죠.
검색해본 결과, Next.js에서는 컴포넌트를 아무리 클라이언트 컴포넌트로 만들어줘도 기본적으로 서버에서 초기 렌더링을 하기 때문에 서버 측과 클라이언트 측의 렌더링 결과물이 다르면 발생할 수 밖에 없는 문제라는 걸 알게되었습니다.
https://nextjs.org/docs/app/building-your-application/rendering/client-components
Rendering: Client Components | Next.js
Learn how to use Client Components to render parts of your application on the client.
nextjs.org
이후 감사하게도 멘토님의 도움을 받아 React의 공식 문서를 찾으며 Hydration 에러를 해결할 수 있었습니다.
에러 메시지에 정답이 있었는데 바로 서버와 클라이언트 렌더링 결과물을 일치하게 해주는 것이였습니다.
// useClient.ts
const useClient = () => {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return isClient;
};
// useClient 훅을 불러와 hydration 에러가 발생하는 컴포넌트에서 사용
const isClient = useClient();
//...
{isClient && (isLogin || isGoogleLogin) ? // ...
사실 위의 코드가 완벽한 해결 방법은 아닙니다. 의도적으로 렌더링을 두 번하는 즉, 리렌더링을 발생시켜 서버와 클라이언트를 일치시키는 것이여서 결국 Hydration 에러는 해결했지만 웹 페이지의 속도가 저하될 수 있다는 점을 감안해야 했습니다.
추후 Hydration에 관련하여 찾아봤을 때 여전히 이 주제는 완벽한(?) 해결 방법이 없는 뜨거운 감자였습니다.
https://velog.io/@eunbinn/%EB%B2%88%EC%97%AD-why-efficient-hydration-in-javascript-is-so-challenging
[번역] 자바스크립트 프레임워크에서 효율적인 하이드레이션(Hydration)이 어려운 이유
자바스크립트 프레임워크에서 사용하고 있는 다양한 하이드레이션(Hydration)의 방법을 설명하며, 각각 어떤 한계점을 가지고 있는지 설명하고 있습니다.
velog.io
끝으로
다시 한번 Hydration 이슈를 정리하면서 참고한 화해 테크 블로그에서 컴포넌트의 서버 사이드 렌더링(SSR)을 비활성화 할 수 있다는 점을 알게되었습니다. 이 부분은 개인적으로 다시 리팩토링할 때 적용해보고 후기와 함께 돌아오겠습니다.
글을 읽어주신 모든 분께 감사합니다. :)
출처
1. https://blog.hwahae.co.kr/all/tech/13604
React의 hydration mismatch 알아보기 – 화해 블로그 | 기술 블로그
React의 hydration mismatch 알아보기 화해는 에러 트래킹 서비스인 Sentry를 사용하고 있는데요. 지난해 Next.js의 static export로 배포한 이벤트 페이지에서 hydration mismatch 에러가 쌓이기 시작했습니다.
blog-wp.hwahae.co.kr
(번역) 리액트 앱(SSR)의 Hydration 이해하기
리액트 세상에서 우리는 서버 사이드 렌더링(SSR), 클라이언트 사이드 렌더링(CSR), ReactDOM, ReactDOMServer와 같은 이해하기 어려운 다양한 용어들을 마주친다. 이 용어들을 빠르게 이해해보자. ReactDOM
velog.io
3. https://en.wikipedia.org/wiki/Hydration_(web_development)
Hydration (web development) - Wikipedia
From Wikipedia, the free encyclopedia Technique used in web development In web development, hydration or rehydration is a technique in which client-side JavaScript converts a static HTML web page, delivered either through static hosting or server-side rend
en.wikipedia.org
'톺아보기' 카테고리의 다른 글
자격증 시험 일정 (0) | 2024.03.23 |
---|---|
봐야 하는 것들 (0) | 2024.03.18 |
상태 관리, 어떻게 하세요?라는 질문이 들어온다면... (0) | 2024.03.10 |
이제야 용기내서 써보는 React Hook Form과 DRY vs WET (0) | 2024.03.10 |
프로그래밍과 프로그래밍 언어 (0) | 2023.10.31 |