🔍 검색 키워드: Next.js hydration 불일치, SSR 하이드레이션 에러, hydration mismatch 해결, Next.js 렌더링 차이
Next.js Hydration 불일치 에러 해결하기
증상
Next.js 앱을 개발하거나 프로덕션에 배포했을 때, 브라우저 콘솔에 다음과 같은 에러가 나타난다:
Error: Hydration failed because the initial UI does not match what was rendered on the server.
또는 더 자세한 메시지:
Warning: Text content did not match. Server: "Monday" Client: "Sunday"
페이지는 렌더링되지만, 콘솔에 에러가 쌓이고 JavaScript가 제대로 작동하지 않을 수 있다. 특히 동적 콘텐츠나 클라이언트 전용 데이터가 있을 때 자주 발생한다.
원인
Hydration 불일치는 서버에서 렌더링한 HTML과 클라이언트에서 처음 렌더링한 React 컴포넌트가 다를 때 발생한다.
주요 원인들:
- 타임존/로케일 의존 데이터:
new Date(),toLocaleDateString()등이 서버와 클라이언트에서 다른 값 반환 - Math.random() 사용: 서버와 클라이언트가 다른 난수 생성
- 클라이언트 전용 hook 오남용:
useEffect없이 클라이언트 전용 코드 실행 - 브라우저 API 직접 접근:
window,document같은 브라우저 객체에 SSR 중 접근 - 조건부 렌더링 불일치:
if (isMobile)등의 조건이 서버와 클라이언트에서 다름
해결방법
1. useEffect로 클라이언트 전용 렌더링 (권장)
export default function Page() {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <div>로딩 중...</div>;
}
return <div>{new Date().toLocaleDateString()}</div>;
}
이렇게 하면 서버에서는 "로딩 중..." 렌더링, 클라이언트에서 hydrate 후 날짜 표시 → 불일치 없음.
2. suppressHydrationWarning 사용 (임시방편)
export default function Page() {
return (
<div suppressHydrationWarning>
{new Date().toLocaleDateString()}
</div>
);
}
⚠️ 경고만 무시하고 실제 불일치는 해결 안 함. 간단한 경우만 사용.
3. 동적 콘텐츠는 서버에서 처리
// app/page.jsx (서버 컴포넌트)
import { getServerData } from '@/lib/api';
export default async function Page() {
const data = await getServerData();
return <div>{data.title}</div>;
}
서버에서 필요한 데이터를 모두 fetch → 클라이언트에서는 같은 데이터 사용 → 불일치 자동 해결.
4. 브라우저 API는 useEffect 안에서만
export default function Page() {
const [scrollY, setScrollY] = React.useState(0);
React.useEffect(() => {
const handleScroll = () => setScrollY(window.scrollY);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <div>스크롤: {scrollY}px</div>;
}
5. 타임존 문제 해결
// lib/date.js
export function getCurrentDate() {
// 서버와 클라이언트 모두 동일한 timezone 사용
const now = new Date();
return now.toISOString().split('T')[0];
}
// app/page.jsx
import { getCurrentDate } from '@/lib/date';
export default function Page() {
const dateStr = getCurrentDate();
return <div>{dateStr}</div>;
}
정리표
| 상황 | 원인 | 해결법 |
|---|---|---|
new Date().toString() 다름 |
서버/클라이언트 타임존 차이 | ISO 문자열 사용 또는 useEffect로 지연 |
Math.random() 불일치 |
난수 생성 차이 | 서버에서 난수 생성 후 props로 전달 |
window.location 에러 |
SSR 중 브라우저 API 접근 | useEffect 안에서만 접근 |
| 모바일/데스크톱 렌더링 다름 | 미디어쿼리 판정 차이 | CSS media query 사용 또는 useEffect 지연 |
| 조건부 렌더링 불일치 | 클라이언트 상태 기반 조건 | 서버/클라이언트 동일 조건 또는 useEffect 지연 |
TIP: Next.js 13+ 서버 컴포넌트를 최대한 활용하면 이런 문제가 대폭 줄어든다. 클라이언트 컴포넌트는 진짜 필요한 부분만 사용하자.
댓글 없음:
댓글 쓰기