Next.js 에서 Hydration Mismatch 가 뜬 이유 (서버-클라이언트 값 불일치)

Melon Coder·2026년 7월 24일

Trouble shooting

목록 보기
15/15

배경

회사 홈페이지 내 다운로드 탭에 로그인 여부에 따라 다른 화면을 보여주는 기능을 추가했더니, 콘솔에 이런 에러가 떴다.

Hydration failed because the server rendered HTML didn't match the client.

서버에서 그려준 HTML 과 클라이언트가 처음 그린 결과가 달라서 React가 통째로 다시 그리는 상황이 발생했다.

원인

인증 상태를 관리하는 AuthProvider 에서 로딩 상태의 초기값을 이렇게 잡고 있었다.

const [loading, setLoading] = useState(() => !!getToken())

getToken()localStorage를 읽는데, 문제는 이 함수가 서버와 클라이언트에서 다른 값을 내고 있었다.
왜냐면 서버에는 localStorage가 없기 때문에 항상 false, 클라이언트에서는 로그인한 적이 있으면 true를 반환하고 있기 때문.
React는 클라이언트의 첫 렌더와 서버 결과가 같아야 hydration에 성공하는데 이 초기값부터 달라서 오류가 발생했던 것.

따라서 초기값을 서버와 클라이언트 모두 동일하게 만들고, 실제 값은 마운트 이후에 확정시키도록 수정했다.

const [loading, setLoading] = useState(true)

useEffect(() => {
  (getToken() ? restore() : Promise.resolve()).finally(() =>
setLoading(false))
}, [])

첫 렌더는 항상 loading값을 true로 해놓고, 이후 실제 값으로 리렌더 하는 방식으로 했다.
초기 짧은 로딩 깜빡임은 생기지만 서버/클라이언트의 값이 mismatch 되지 않는다.

정리

SSR 컴포넌트의 초기 렌더는 브라우저 API (loacalStorage, window, 화면 크기 등) 에만 의존하면 안된다.
그 정보가 필요하면 초기값은 서버도 계산 가능한 값으로 고정해두고, 실제 값은 useEffect 안에서 확정하는 패턴을 쓰면 됨.

profile
About me: https://resume-seven-beige.vercel.app/

0개의 댓글