React 18 Server Component 실험 중 마주친 hydration 이슈

문제 상황

Next.js 13의 app 디렉토리를 실험적으로 도입하면서 Server Component를 적용했다. 배포 후 콘솔에 hydration mismatch 경고가 반복적으로 발생했다.

// app/posts/[id]/page.tsx
export default function PostPage({ post }) {
  return (
    <article>
      <time>{new Date(post.createdAt).toLocaleDateString()}</time>
      <h1>{post.title}</h1>
    </article>
  );
}

서버에서는 UTC 기준으로 날짜를 렌더링하고, 클라이언트는 사용자 로컬 timezone을 적용해서 불일치가 발생했다.

해결 과정

  1. 서버에서 직렬화 처리: Date 객체를 ISO string으로 변환해 props로 전달
  2. 클라이언트 전용 컴포넌트 분리: 날짜 포맷팅은 'use client' 지시어를 사용한 별도 컴포넌트로 분리
// components/FormattedDate.tsx
'use client';

export default function FormattedDate({ isoString }) {
  const [formatted, setFormatted] = useState('');
  
  useEffect(() => {
    setFormatted(new Date(isoString).toLocaleDateString());
  }, [isoString]);
  
  return <time suppressHydrationWarning>{formatted}</time>;
}

결론

Server Component는 강력하지만 서버/클라이언트 경계에서 데이터 직렬화에 주의가 필요하다. Date, Function, Symbol 같은 non-serializable 값은 반드시 문자열이나 JSON으로 변환해야 한다. ChatGPT가 출시된 지 2주밖에 안 됐는데, 이런 새로운 패러다임의 디버깅 자료는 아직 부족해서 공식 문서에 많이 의존하고 있다.