📍React에서 Hydrate란?
✅Hydrate의 사전적 의미: 수화 (예 - 메마른 꽃에 물을 줘서 생기있게 만드는 것)
서버 사이드 렌더링된 HTML을 클라이언트 사이드에서 enhance하여 feature를 가진 App 형태로 완성시키는 것
enhance 내용 : 상태(state), interaction 등을 추가
📍hydrateRoot API
✅createRoot와 비슷
hydrateRoot 를 호출하여 서버 환경에서 이미 렌더링된 기존 HTML(domNode)에 React(reactNode)를 부착
import { hydrateRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, reactNode); // render, unmount가 포함된 객체 반환
// domNode : 서버 사이드 렌더링된 DOM Element
// reactNode : 클라이언트 사이드에서 생성된 JSX
- 부착 후 domNode 이하의 DOM을 React가 관리하게 됨
- root 엘리먼트에 대해 hydrateRoot를 1번만 호출 (Next.js 에서는 Next가 대신 호출)
📍주의사항
- hydrateRoot() 가 hydrate 시작 전, DOM element가 다르면 버그로 인식 (개발 환경에서 동일하지 않은 부분을 경고)
예) Next.js 에서 Styled-components 라이브러리를 사용할 때, css-in-js 특성 상 프리렌더링과 동시에 style이 적용되지 않으므로 hydration error가 발생
- 마크업이 동일하지 않는 경우는 거의 없다
- 모든 마크업을 비교하는 것은 리소스 소모가 큼
- 프리렌더링된 HTML이 없는 경우, hydrateRoot()를 사용할 수 없고, 대신 createRoot()를 사용해야 함
⭐Next.js는 root element render 시 createRoot() 대신 hydrateRoot()를 사용
- 코드 내에 createRoot() 없고 대신 hydrateRoot() 호출
📍root.render(reactNode)
✅root.render를 호출하여 Hydrate된 내부 컴포넌트를 React 컴포넌트(<App />)로 업데이트
root.render(<App />);
hydrate가 끝나기 전에 root.render를 호출하면 React는 서버에서 렌더링된 HTML을 모두 없애고 클라이언트에서 렌더링된 컴포넌트들로 완전히 교체
📍root.unmount(reactNode)
✅root.unmount를 호출하여 React root 하위의 렌더링된 트리를 삭제
- 루트 내부의 모든 컴포넌트를 umount하며, 루트 DOM 노드에서 React를 떼어내는 기능
- 모든 이벤트 핸들러와 state를 DOM에서 제거
- React로 이루어진 앱에서는 쓸 일이 거의 없음 (jQuery와 React가 혼용된 앱의 경우 사용할 수 있음)
root.unmount();
📍서버에서 렌더링된 HTML을 hydrate
✅pre-render 된 HTML을 React component인 <App /> 으로 교체하여 interactive한 App으로 Hydrate
- 앱 실행할 때 최초 1번만 수행
- 프레임워크를 사용중이라면 프레임워크가 알아서 수행 (Next.js 등)
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);
이 때, hydrateRoot에 전달한 React 트리(index.html)는 서버에서 만들었던 React Tree 결과물(App.js)과 동일해야 함
- 프리렌더링의 목적이 빠른 화면 로딩을 통해 유저의 경험을 개선하는 것이므로,
index.html(HTML + CSS) 의 화면과 App.js의 화면은 동일해야 함
- HTML + CSS만 먼저 뿌려주고, CSR로 완성된 App.js 에서 state와 interaction을 추가
📍문서 전체를 hydrate
✅앱을 React로만 작성한 경우, <html> 태그를 포함한 JSX로 전체 문서를 렌더링할 수 있음
- 이 때는, 전역 변수인 document를 hydrateRoot() 의 첫 번째 인자로 넘겨줌
(Next.js의 _document 역할)
App.js: React로만 작성된 앱
function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}
index.js: document를 받아 Hydrate 역할을 수행
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);
📍불가피한 hydration 에러 억제
현재 시간을 렌더링하는 앱의 경우, 빌드타임에 프리렌더링하는 현재 시간과 hydration 시의 현재 시간의 텍스트 컨텐츠가 다를 수 있음
이 때, suppressHydrationWarning attributes를 추가하여 warning을 방지할 수 있음
- 한 단계 아래까지만 적용되는 임시방편적 방법
- 텍스트 컨텐츠에 대해서만 사용 가능
export default function App() {
return (
<h1 suppressHydrationWarning={true}>
Current Date: {new Date().toLocaleDateString()}
</h1>
);
}
⭐서버와 클라이언트에서 다른 내용을 렌더링하려면, useEffect를 사용하면 됨
📍참고
https://react.dev/reference/react-dom/client/hydrateRoot
https://www.gatsbyjs.com/docs/conceptual/react-hydration/