React로 서버사이드 렌더링(SSR)을 하다 보면 Hydration, Streaming SSR, Flight 같은 단어들이 등장한다.
이 글에서는 React가 SSR 이후 어떻게 브라우저에서 살아나는지, 그리고 그 내부 구조가 어떻게 발전하고 있는지를 단계별로 정리해 보겠다.
서버사이드 렌더링(SSR)은 React를 실행시켜 HTML을 만들어 서버에서 먼저 보낸다는 뜻이다.
사용자는 자바스크립트가 로드되기 전에도 완성된 화면을 볼 수 있다.
하지만 이 HTML은 단순한 정적 마크업일 뿐,
버튼을 눌러도 동작하지 않고 useState, useEffect 같은 훅도 실행되지 않는다.
그래서 필요한 과정이 바로 Hydration(하이드레이션) 이다.
Hydration은 클라이언트가 서버로부터 받은 HTML을 다시 React 앱으로 되살리는 과정이다.
즉,
// 서버가 보낸 HTML
<div id="root"><button>좋아요</button></div>
// 클라이언트에서 실행
hydrateRoot(document.getElementById('root'), <App />);
이 시점에서 React는
기존 DOM과 새로 렌더링된 가상 DOM이 동일한가?를 비교하며 reconciliation(재조정)을 수행한다.
만약 서버에서 렌더된 결과와 클라이언트 렌더 결과가 다르면
React는 경고를 띄우거나 DOM을 재생성한다.
React는 16 버전 이후부터 Fiber 구조를 사용해 렌더링을 관리한다.
Hydration 시점에는 기존 DOM 트리를 기반으로 새로운 Fiber Tree를 구성하지만,
HTML을 다시 그리지 않고 이미 존재하는 DOM 노드를 참조로 재활용한다.
이 덕분에 React는 초기 렌더링 속도를 크게 개선할 수 있고,
SSR로 렌더된 화면을 깜빡임 없이 자연스럽게 이어받는다.
React 18의 StrictMode는 Hydration 과정에서도 중요한 역할을 한다.
개발 모드에서만 동작하며, 컴포넌트를 의도적으로 두 번 초기화한다.
왜 ?
부수효과(Side Effect) 가 의도치 않게 중복 실행되거나
Hydration 타이밍과 충돌하는 문제를 조기에 찾아내기 위해서다.
즉, React는
이 코드가 Hydration-safe하게 동작하는가?를 미리 검증하기 위해
useEffect·useState 등의 초기화를 두 번 수행한다.
프로덕션에서는 한 번만 실행되므로 성능 저하는 없다.
기존 SSR의 문제는 모든 데이터가 준비될 때까지 서버가 HTML을 보내지 못한다는 점이었다.
React 18에서는 이 문제를 해결하기 위해 Streaming SSR이 도입됐다.
<Suspense> 컴포넌트를 사용하면,
서버는 준비된 부분부터 HTML을 스트리밍으로 전송하고,
아직 준비되지 않은 부분은 fallback UI로 채운다.
<Suspense fallback={<Loading />}>
<UserProfile />
</Suspense>
서버는 이런 식으로 HTML을 순차적으로 흘려보낸다:
Loading UI 전송UserProfile HTML 추가 전송이 구조 덕분에 사용자는 로딩 중에도 페이지가 즉시 반응하는 경험을 얻는다.
React 19에서는 Flight 프로토콜을 중심으로
Partial Hydration(부분 하이드레이션) 이 실험되고 있다.
기존에는 클라이언트 전체를 한 번에 hydrate해야 했지만,
Flight는 서버에서 렌더링된 결과를 JSON 형태로 나누어 전송하고,
클라이언트는 필요한 부분만 복원한다.
// 서버: 컴포넌트 트리를 JSON으로 분해하여 전송
// 클라이언트: 특정 상호작용 발생 시 관련 부분만 Hydrate
이 구조는