Next.js SSR + 클라이언트 상태 동기화 문제(하이드레이션 오류)

김현준·2025년 5월 25일
0

넥스트JS 이모저모

목록 보기
11/23

문제 상황

  • 서버 캐시와 클라이언트 캐시를 공유하기 위해 dehydrate + HydrationBoundary 전략을 사용
  • 특정 컴포넌트(LogBookMarkButton)에서 하이드레이션 오류 발생
export default async function LatestLogConentSection({ currentPage }: LatestLogConentSectionProps) {
  const queryClient = getQueryClient();

  await queryClient.prefetchQuery({
    queryKey: logKeys.list({ currentPage: +currentPage, pageSize: 13 }),
    queryFn: () => getLogs({ currentPage: +currentPage, pageSize: 13 }),
  });
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      //LatestLogConent 안에 LogBookMarkButton 들어있음
      <LatestLogConent currentPage={currentPage} />
    </HydrationBoundary>
  );
}

원인 분석

  1. 북마크 버튼 컴포넌트 내부에서는 로그인 정보나 북마크 여부를 클라이언트에서만 불러오는 코드(useQuey, useMutation)를 사용하고 있는 상황

  2. Next.js는 처음 페이지를 서버에서 먼저 렌더링한 뒤, 그 결과를 브라우저에서 다시 하이드레이션해서 연결()

    • 서버에서는 아직 로그인 여부를 모름 → 북마크가 안 된 것처럼(false) 렌더됨
    • 클라이언트에서는 로그인 상태를 인식함 → 북마크가 된 것처럼(true) 렌더됨
  3. 결국 서버가 만든 HTML과 클라이언트가 만든 화면이 다름

    • 리액트가 서로 다른 것 같다라고 판단 → 하이드레이션 에러 발생

쉽게 말하면

  • 북마크 버튼 안에서 로그인 상태나 북마크 여부를 브라우저에서만 불러오는데,
  • 서버는 이걸 몰라서 잘못된 화면을 먼저 만들고,
  • 브라우저는 그걸 다시 바꾸려고 해서 에러가 난 거다.

해결

const DynamicLogBookMarkButton = dynamic(() => import('./LogBookMarkButton'), { ssr: false });
  • ssr: false를 통해 해당 컴포넌트는 클라이언트에서만 렌더링되도록 처리
  • 리액트가 서버-클라이언트 DOM mismatch를 비교하지 않게 되어 하이드레이션 오류 제거

후기

  • 이런 하이드레이션 오류는 상태 기반 UI를 SSR 환경에서 사용할 때 자주 발생
  • dynamic(..., { ssr: false })는 단순하지만 매우 강력한 도구이며, 이 컴포넌트는 클라이언트에서만 렌더돼야 한다는 의도를 명확히 드러낼 수 있다.
profile
기록하자

0개의 댓글