React의 useState는 useEffect와 달리 렌더링 시점부터 관여하는 훨씬 앞단의 훅이다.
그래서 SSR·Hydration 흐름 속에서 언제, 어디서, 어떻게 초기화되는지를 정확히 이해하면 Hydration-safe 코드를 작성할 때 훨씬 감이 잡힌다.
useState는 렌더 단계에서 실행된다React는 컴포넌트를 렌더링할 때 다음과 같은 과정을 거친다.
| 단계 | 설명 |
|---|---|
| 1 | 컴포넌트 함수를 실행해서 JSX를 얻는다. |
| 2 | 그 안에서 useState()가 호출되면, React는 내부적으로 상태 저장소를 만든다. |
| 3 | JSX 결과를 가상 DOM(Fiber Tree)에 저장하고, 실제 DOM으로 반영한다. |
즉, useState의 초기값은 렌더링 시점에 바로 평가(evaluate) 된다.
function Example() {
const [count, setCount] = useState(0); // ← 여기서 이미 실행됨
console.log("렌더링 중!");
return <div>{count}</div>;
}
서버든 클라이언트든, React가 이 컴포넌트를 렌더링하는 순간
useState(0)은 바로 호출되어 상태가 등록된다.
useState도 서버에서 한 번 실행된다SSR(Server-Side Rendering)에서는 React가 서버에서 컴포넌트를 실행합니다.
이때 useState 초기값이 평가되어 HTML이 생성된다.
예를 들어
function Greeting() {
const [name, setName] = useState("게스트");
return <p>{name}님 안녕하세요</p>;
}
서버에서 이 컴포넌트를 렌더링하면
"게스트님 안녕하세요"라는 HTML이 만들어지고 브라우저로 전달된다.
이때 React는
useState("게스트")의 초기값을 서버 메모리 안에서만 기억하고,
브라우저에는 실제로 state 정보는 전송하지 않는다.
즉, HTML만 보냄.
useState가 다시 초기화된다이제 브라우저가 HTML을 받아서 React를 실행할 때,
같은 컴포넌트가 다시 렌더링된다.
이 시점에서도 React는
useState("게스트")를 다시 실행하면서 초기 상태를 복원하려고 시도한다.
하지만 여기서 중요한 점
이 초기값은 서버에서 렌더링된 값과 반드시 동일해야 한다.
만약 서버와 클라이언트의 초기 상태가 다르면?
function Greeting() {
const [name, setName] = useState(
typeof window !== "undefined" ? localStorage.getItem("user") : "게스트"
);
return <p>{name}님 안녕하세요</p>;
}
서버에서는 "게스트"
클라이언트에서는 "나나"
→ Hydration 시점에 React는 DOM과 Virtual DOM이 다르다는 Hydration Mismatch Warning 을 띄운다.
이게 흔히 말하는 “Hydration-safe하지 않은 코드”이다.
Hydration-safe하게 useState를 사용하는 기본 원칙은 👇
const [theme, setTheme] = useState("light");
useState(window.innerWidth) — SSR에서는 window가 없으므로 오류useState(localStorage.getItem("theme")) — SSR에서는 접근 불가→ 이런 경우엔 useEffect를 써서 Hydration 이후에 값 갱신으로 옮겨야 함.
const [theme, setTheme] = useState("light");
useEffect(() => {
const saved = localStorage.getItem("theme");
if (saved) setTheme(saved);
}, []);
이렇게 하면 SSR 단계에서는 "light"로 렌더되고,
Hydration이 끝난 뒤에만 실제 localStorage 값을 반영하게 된다.
→ 서버와 클라이언트의 초기 일관성이 유지되어 경고가 사라짐.
useEffect와의 비교 정리| 구분 | 실행 시점 | SSR에서 실행 여부 | 주요 역할 |
|---|---|---|---|
useState | 렌더링 중 | ✅ 실행됨 (초기값만 평가) | 상태 초기화 |
useEffect | 렌더링 완료 후 (Hydration 끝난 뒤) | ❌ 실행 안 됨 | 브라우저에서 부수효과 처리 (fetch, DOM 조작 등) |
useState는 화면을 그릴 때 실행되고,
useEffect는 화면이 다 그려진 뒤 실행된다.
그래서 SSR 환경에서는useState의 초기값이 서버·클라이언트 간 동일해야 한다.