서버 사이드 렌더링(SSR)을 사용하면 서버가 HTML을 완성하여 브라우저로 보냅니다. 브라우저는 이 HTML을 즉시 화면에 표시할 수 있어 초기 로드가 빠릅니다. 그러나 이 시점의 HTML은 정적인 마크업에 불과합니다. 버튼을 클릭해도 반응이 없고, 상태가 변경되어도 화면이 바뀌지 않습니다.
Hydration은 이 정적 HTML에 React의 이벤트 핸들러와 상태 관리 기능을 연결하는 과정입니다. Hydration이 완료된 이후에야 페이지가 완전히 인터랙티브하게 동작합니다.
CSR (Client Side Rendering)
──────────────────────────────────────────
브라우저 요청
│
▼
빈 HTML + JS 번들 수신
│
▼
JS 실행 → React 렌더링 → 화면 표시
(초기 화면까지 시간이 걸림)
SSR (Server Side Rendering)
──────────────────────────────────────────
브라우저 요청
│
▼
완성된 HTML 수신 → 즉시 화면 표시
│
▼
JS 번들 로드 → Hydration → 인터랙티브
(화면은 빠르지만 인터랙티브까지 시간 차이 존재)
SSR은 FCP(First Contentful Paint)를 빠르게 만들지만, Hydration이 완료되기 전까지는 사용자의 조작에 반응하지 않습니다. 이 구간을 TTI(Time to Interactive)라고 하며, FCP와 TTI 사이의 간격이 클수록 사용자 경험이 나빠질 수 있습니다.
FCP(First Contentful Paint): 브라우저가 첫 번째 텍스트나 이미지를 화면에 그리는 시점.
TTI(Time to Interactive): 페이지가 완전히 인터랙티브하게 동작하기 시작하는 시점.
Hydration은 서버에서 생성한 HTML과 클라이언트에서 React가 예상하는 DOM 구조가 일치해야 정상적으로 동작합니다. 두 구조가 다르면 React는 경고를 출력하고 전체 DOM을 클라이언트 렌더링 결과로 교체합니다.
흔한 원인은 다음과 같습니다.
첫째, 날짜나 랜덤 값처럼 서버와 클라이언트에서 다른 결과를 반환하는 코드를 렌더링에 사용하는 경우입니다.
// 오류: 서버와 클라이언트의 시간이 다름
function Clock() {
return <span>{new Date().toLocaleTimeString()}</span>;
}
둘째, 브라우저 전용 API(window, document)를 컴포넌트 최상위 레벨에서 사용하는 경우입니다. 서버에는 이 객체가 존재하지 않으므로 렌더링 결과가 달라집니다.
셋째, HTML 규격 위반으로 브라우저가 DOM을 자동으로 수정하는 경우입니다. 예를 들어 <p> 태그 안에 <div>를 중첩하면 브라우저가 DOM 구조를 변경합니다.
해결 방법은 서버와 클라이언트가 다른 값을 생성하는 로직을 useEffect 안으로 옮기거나, Next.js의 dynamic import에 { ssr: false } 옵션을 적용하여 해당 컴포넌트를 클라이언트에서만 렌더링하도록 처리하는 것입니다.
기존 Hydration은 전체 페이지를 한 번에 처리했습니다. 페이지의 한 부분이 복잡하면 전체 Hydration이 완료될 때까지 어느 곳도 인터랙티브해지지 않았습니다.
React 18은 Selective Hydration을 도입하여 이 문제를 해결합니다. Suspense로 감싼 컴포넌트 단위로 Hydration이 독립적으로 진행됩니다. 사용자가 특정 영역과 상호작용을 시도하면 해당 영역의 Hydration이 우선순위를 높여 먼저 처리됩니다.
<Layout>
<Header /> {/* 즉시 Hydration */}
<Suspense fallback={<Spinner />}>
<Comments /> {/* 준비되는 대로 독립적으로 Hydration */}
</Suspense>
<Suspense fallback={<Spinner />}>
<Recommendations /> {/* 준비되는 대로 독립적으로 Hydration */}
</Suspense>
</Layout>
Selective Hydration은 사용자가 느끼는 인터랙티브 응답 시간을 크게 단축시킵니다. 무거운 컴포넌트가 Hydration을 완료하기를 기다리지 않고, 준비된 부분부터 바로 사용할 수 있게 됩니다.