개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Hydration — SSR HTML에 React 기능을 연결하는 과정

2026년 5월 20일
ReactHydrationSSRCSR

서버 사이드 렌더링(SSR)을 사용하면 서버가 HTML을 완성하여 브라우저로 보냅니다. 브라우저는 이 HTML을 즉시 화면에 표시할 수 있어 초기 로드가 빠릅니다. 그러나 이 시점의 HTML은 정적인 마크업에 불과합니다. 버튼을 클릭해도 반응이 없고, 상태가 변경되어도 화면이 바뀌지 않습니다.

Hydration은 이 정적 HTML에 React의 이벤트 핸들러와 상태 관리 기능을 연결하는 과정입니다. Hydration이 완료된 이후에야 페이지가 완전히 인터랙티브하게 동작합니다.

SSR과 CSR의 차이

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 오류의 원인

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 } 옵션을 적용하여 해당 컴포넌트를 클라이언트에서만 렌더링하도록 처리하는 것입니다.

Selective Hydration (React 18)

기존 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을 완료하기를 기다리지 않고, 준비된 부분부터 바로 사용할 수 있게 됩니다.

← 이전 글Server Components — 서버에서 렌더링되는 React 컴포넌트
다음 글 →React 렌더링 전략 — CSR / SSR / SSG / ISR