목요일

Next.js Hydration 불일치 에러 해결하기

🔍 검색 키워드: 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 컴포넌트가 다를 때 발생한다.

주요 원인들:

  1. 타임존/로케일 의존 데이터: new Date(), toLocaleDateString() 등이 서버와 클라이언트에서 다른 값 반환
  2. Math.random() 사용: 서버와 클라이언트가 다른 난수 생성
  3. 클라이언트 전용 hook 오남용: useEffect 없이 클라이언트 전용 코드 실행
  4. 브라우저 API 직접 접근: window, document 같은 브라우저 객체에 SSR 중 접근
  5. 조건부 렌더링 불일치: 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+ 서버 컴포넌트를 최대한 활용하면 이런 문제가 대폭 줄어든다. 클라이언트 컴포넌트는 진짜 필요한 부분만 사용하자.

댓글 없음:

댓글 쓰기