SSR 의 빠른 first paint + SPA 의 interactivity 결합. 그러나 무료 아님 — hydration 이라는 cost. server 가 만든 HTML 위에 React 가 event handler 부착하는 단계. mismatch error, progressive hydration, Astro / Qwik 의 island architecture — 이 가이드는 모두 정리한다.
Hydration — 무엇을 하나
SSR 의 흐름:
1. server: React tree → HTML 문자열
2. browser: HTML 즉시 paint (사용자가 봄, 빠름)
3. browser: JS bundle download
4. hydration:
- server HTML 의 각 element 에 React fiber 매핑
- event handler (onClick 등) 부착
- useState / useEffect 활성
- "이제 interactive"
그 사이 (3 → 4 끝나기 전):
- UI 는 보이지만 클릭 등 반응 X (또는 부분 반응)
- "First Contentful Paint" 빠름 + "Time to Interactive" 늦음의 gap
→ hydration cost = SSR 의 trade-off.Hydration Mismatch — 가장 흔한 에러
function Greeting() {
return <p>Today is {new Date().toLocaleDateString()}</p>;
}
server (KST timezone): "Today is 2026-05-26"
client (KST timezone): "Today is 2026-05-26"
→ server 가 23:59 에 render 했고 client 가
자정 넘어서 render 하면 → "2026-05-27" 가 됨
→ mismatch error
Error: Hydration failed because the initial UI does not match what
was rendered on the server.
원인 type:
1. 시간 (Date.now, new Date)
2. random (Math.random, UUID)
3. browser-only API (window, localStorage, navigator)
4. 사용자별 (geolocation, cookies, IP-based)
5. 환경별 (server-only env vs client-only)Mismatch 해결 패턴
방법 1 — 그 component 를 client-only:
// Next.js
const ClientOnlyTime = dynamic(() => import("./Time"), {ssr: false});
방법 2 — useEffect 안에서만 dynamic value 사용:
function Greeting() {
const [date, setDate] = useState<string | null>(null);
useEffect(() => {
setDate(new Date().toLocaleDateString());
}, []);
return <p>Today is {date ?? "loading"}</p>;
}
// server render = "loading"
// client render = "loading" (match!) → 그 후 setDate → "2026-05-26"
방법 3 — suppressHydrationWarning (한 element 만):
<p suppressHydrationWarning>
{new Date().toLocaleDateString()}
</p>
// mismatch 경고 silent, client 값 우선
// 이 element 의 자식들만 — recursive 아님
방법 4 — server / client 같은 값 보장:
- 시간: server 에서 timestamp + client 에서 format
- random: useId hook (React 18+)
- locale: cookie 또는 URL 에서 일관Progressive / Selective Hydration (React 18+)
전통 hydration (React 17 까지):
page 의 모든 component 가 동시 hydrate
→ big page = 큰 main thread block
→ 그 동안 user interaction 무시
React 18 의 selective hydration:
Suspense boundary 가 hydration unit
- 처음 보이는 (above-fold) Suspense 부터 hydrate
- 안 보이는 (below-fold) 는 idle 시간 또는 보일 때까지 deferred
- 사용자가 below-fold 클릭하면 → 그 boundary 우선순위 ↑ → 먼저 hydrate
<Suspense fallback={<Skel/>}>
<Header /> ← 1st
</Suspense>
<Suspense fallback={<Skel/>}>
<Sidebar /> ← 2nd (idle 시 hydrate)
</Suspense>
<Suspense fallback={<Skel/>}>
<Comments /> ← 3rd (사용자 click 시 우선)
</Suspense>
→ "Time to Interactive" 가 component 별로 조절. 큰 page 도 부분적으로 즉시 반응.Island Architecture (Astro)
기본 가정 변경:
React/SSR: page 전체가 React app — 모두 hydrate 필요
Astro: page 가 정적 HTML — 일부 "island" 만 interactive
Astro 코드:
---
// frontmatter (build-time only)
const posts = await db.posts.findAll();
---
<html>
<body>
<h1>{posts[0].title}</h1> ← static HTML
<p>{posts[0].body}</p> ← static HTML
<LikeButton client:load /> ← React/Vue island (hydrate)
<CommentForm client:visible /> ← intersection 시 hydrate
</body>
</html>
→ static HTML 95% + interactive island 5% → JS 전송 매우 적음.
→ 블로그 / 마케팅 / 뉴스 사이트에 매우 적합.Qwik — Resumability (hydration 없음)
Qwik (2022+) 의 다른 접근:
- SSR HTML 에 모든 state + event handler reference 박음
- hydration 단계 = 0 (즉시 interactive)
- 사용자가 클릭 시점에만 그 handler 의 JS chunk lazy download
HTML: <button on:click="/chunks/handleClick">Like</button>
↑
click 시점에 fetch + 실행
장점:
- O(1) startup — page 크기에 무관
- 거대 application 도 instant interactive
단점:
- mental model 학습 곡선
- 각 handler 가 별도 chunk → 너무 작은 chunk 의 round-trip
- ecosystem 작음
→ "hydration cost" 의 근본 해결책. React 와 다른 길.흔한 함정
- component 안 if (typeof window) 분기 — server / client 다른 결과 → mismatch. useEffect 안에서.
- useState 의 initial 값에 dynamic 사용 — new Date() 등이 server / client 다름. lazy init + useEffect.
- localStorage 직접 접근 — server 에 없음. useEffect 또는 useSyncExternalStore.
- 큰 page 전체를 RSC 없이 hydrate — TTI 느림. Suspense boundary 또는 island 활용.
- suppressHydrationWarning 남용 — 진짜 mismatch 숨기면 production 에서 silent UI corruption.
마무리
Hydration 은 SSR 의 cost — server HTML 위에 React 부착하는 단계. 시간 / random / browser-only API 가 mismatch 의 주범. modern React 18 의 selective hydration + Astro island + Qwik resumability 가 그 cost 를 줄이는 다양한 접근.
실용 — Next.js / Remix 의 default 활용 + dynamic value 는 useEffect 안 + 큰 sub-tree 는 Suspense boundary 로 분리. mostly-static content 면 Astro island 검토.