Next 14 App Router 쓰고 있고요, 게시글 목록에 작성일을 new Date(post.createdAt).toLocaleString()으로 찍어서 보여주는데 콘솔에 이 경고가 뜹니다.
Text content did not match. Server: "2026. 7. 16. 오후 11:14:22" Client: "2026. 7. 17. 오전 8:14:22"서버가 뱉은 값이랑 브라우저가 그린 값이 다르다고 하는데, 딱 봐도 시간대(타임존) 차이인 것 같아요. 서버는 UTC고 제 브라우저는 KST라서 그런 거겠죠? 근데 이걸 어떻게 맞춰야 할지 모르겠습니다.
급한 대로 suppressHydrationWarning을 붙이면 경고는 사라지던데, 이게 근본 해결은 아닌 것 같아서요. 제대로 된 방법이 궁금합니다.
댓글 4
suppressHydrationWarning은 경고만 지우고 실제로는 서버 텍스트가 클라 값으로 조용히 교체되는 거라, 지금 케이스에선 근본 해결이 아니에요. 방향은 둘 중 하나입니다.
첫째, 표시 형식을 실행 환경에 의존하지 않게 고정하기. 서버든 클라든 같은 결과가 나오도록 타임존과 로캘을 명시하세요. Intl.DateTimeFormat에 timeZone: 'Asia/Seoul', locale을 고정으로 박아두면 서버/클라가 같은 문자열을 만들어서 mismatch가 사라집니다. 서버 렌더 시점에 이미 KST로 확정되는 거죠.
둘째, 사용자 로컬 시간으로 보여줘야 한다면(각자 자기 타임존으로) 그 부분만 클라이언트에서 렌더하세요. 'use client' 컴포넌트로 빼고 useEffect가 돈 뒤에(마운트 이후) 포맷하면 서버는 자리표시자만 그리고 실제 시간은 클라에서 채워집니다. mismatch가 원천적으로 안 생겨요.
방법은, 그 컴포넌트에 mounted 상태를 하나 두고 useEffect에서 true로 바꾼 다음, mounted가 false인 동안(=서버 렌더 + 첫 클라 렌더)에는 절대시각이나 빈 값 같은 안정적인 값을 보여주고, mounted가 true가 된 뒤에 상대시간을 계산해서 보여주는 거예요. 서버와 첫 클라 렌더가 동일하니 hydration이 통과하고, 그 직후 상대시간으로 바뀝니다.
더 간단하게는 그 컴포넌트를 next/dynamic으로 ssr: false 걸어서 아예 서버 렌더에서 빼버려도 됩니다. 다만 그 부분이 잠깐 비어 보이니 레이아웃 시프트 신경 쓰이면 첫 방법이 나아요. 그리고 갱신이 필요하면 setInterval로 1분마다 다시 계산하고 cleanup 잊지 마시고요.