어제의 나보다 성장한 오늘의 나

[Next.js] timezone 불일치로 인한 Hydration Failed 이슈 본문

Next.js

[Next.js] timezone 불일치로 인한 Hydration Failed 이슈

today_me 2025. 7. 5. 12:58

 

 

 

 

Hydration failed because the server rendered HTML didn’t match the client.
This can happen if a SSR-ed Client Component is used.

 

 

서버와 클라이언트 간의 HTML 불일치 때문에 해당 에러가 발생했습니다.
어떤 상황에서 이 문제가 발생했는지, 그리고 어떻게 해결했는지를 공유합니다.

 

 

기존 날짜 처리 방식

기존에는 다음 Util을 사용해서 날짜 데이터를 처리 합니다.

 

utils/date-format.ts

export function getFormattedLocaleDateString(date: string | Date) {
  if (typeof date === "string") {
    return new Date(date).toLocaleDateString();
  } else if (typeof date === "object") {
    return date.toLocaleDateString();
  }
}


toLocaleDateString() 는 Date 객체를 현지 표준 시간대와 언어 설정에 맞춰 문자열로 변환합니다.

즉, 사용자의 로컬 타임존에 따라 날짜를 다르게 포맷팅합니다.

 

그게 왜 문제가 될까??

클라이언트 컴포넌트의 경우 getFormattedLocaleDateString() 함수는 서버에서도 실행되고, hydration 시 클라이언트에서도 동일하게 실행됩니다. 이 때 불일치가 발생합니다.

  • 서버는 UTC 타임존 기준으로 날짜를 포매팅합니다.
  • 그러나 클라이언트는 사용자의 로컬 타임존(KST 등) 을 기준으로 포매팅합니다.

 

즉, new Date().toLocaleDateString()의 결과가 서버와 클라이언트에서 다르기 때문에 hydration 시 HTML mismatch가 발생한 것입니다.

 

  • 서버(UTC)에서는 2025. 1. 11.로 렌더링됨
  • 클라이언트(KST)에서는 2025. 1. 12.로 렌더링됨

 

결과적으로 서로 다른 날짜 문자열이 나와 hydration 에러가 발생한 것입니다.

 

근데  왜 서버는 UTC 기준인가?

 

Next.js의 서버는 Node.js 환경에서 실행되며, UTC 타임존을 사용합니다.

 

서버가 UTC 기준으로 동작해야 하는 이유는 다음과 같습니다.

  • 글로벌 사용자 기준으로 표준화된 시간 관리가 가능
  • 데이터 저장/변경 시 타임존 이슈 없이 일관성 있는 처리

생각 해보면 당연한 얘기죠.

 

해결 방법

원인은 파악했으니 문제를 해결하려면 SSR과 CSR 간 시간 표현 기준을 통일해야 합니다. 방법은 크게 두 가지입니다.

 

1. UTC 기준으로 포맷 고정하기

 

가장 심플한 방법입니다.

options에 timeZone을 추가하면 됩니다.

new Date().toLocaleString("ko-KR", {timeZone: "UTC"})

 

실제로도 많은 회사들이 UTC를 기준으로 모든 시간대를 통일하여 사용합니다.
글로벌 서비스인 Datadog, AWS 서비스도 기본적으로 UTC를 기준으로 하죠.

 

 

2. 날짜를 클라이언트에서만 포매팅한다.

'use client';

export function LocalizedDate({ date }: { date: string }) {
  const [formatted, setFormatted] = useState('');

  useEffect(() => {
    const d = new Date(date);
    setFormatted(d.toLocaleDateString());
  }, [date]);

  return <span>{formatted}</span>;
}

 

 

Hydration mismatch 문제는 서버에서 렌더링 한 것과 클라이언트에서 렌더링 한것이 다르기 때문에 일어납니다.

그러므로 useEffect를 활용하여 포매팅을 진행해서 보여주면 해결 할 수 있습니다.

useEffect는 브라우저가 HTML을 렌더링한 후 실행되는 side effect hook 이기 때문이죠. 

그리고 이 훅은 참고로 클라이언트에서만 실행 됩니다.

 

 

 

결론

2번 useEffect 방식도 hydration mismatch를 충분히 방지할 수 있지만, 초기 렌더링 시 잠깐 값이 비어 있거나 깜빡이는 현상이 발생합니다. 또한 모든 날짜 표시를 클라이언트에서 처리해야 하므로, 개발 관점에서 로직이 분산되고 유지보수가 어려워지는 단점이 있습니다.

 

그래서 우리는 서버와 클라이언트 모두에서 일관되게 동작하도록 UTC 기준으로 날짜 포맷을 통일하기로 했습니다 :)

Comments