본문 바로가기
Q&A해결2026년 7월 17일1분 읽기

Next.js App Router에서 hydration 에러가 뜨는데 날짜 포맷 때문인 것 같아요

n
next_haeun
조회 837댓글 4

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

김김영삼3개월 전
정확히 타임존 문제 맞습니다. 서버(Node)는 보통 UTC로 돌고 브라우저는 사용자 로캘이라, toLocaleString처럼 실행 환경에 의존하는 함수는 서버가 그린 HTML과 클라이언트가 hydration할 때 그린 결과가 달라져서 mismatch가 나요. Date.now(), Math.random(), 로캘 의존 포맷 전부 같은 이유로 터집니다.

suppressHydrationWarning은 경고만 지우고 실제로는 서버 텍스트가 클라 값으로 조용히 교체되는 거라, 지금 케이스에선 근본 해결이 아니에요. 방향은 둘 중 하나입니다.

첫째, 표시 형식을 실행 환경에 의존하지 않게 고정하기. 서버든 클라든 같은 결과가 나오도록 타임존과 로캘을 명시하세요. Intl.DateTimeFormat에 timeZone: 'Asia/Seoul', locale을 고정으로 박아두면 서버/클라가 같은 문자열을 만들어서 mismatch가 사라집니다. 서버 렌더 시점에 이미 KST로 확정되는 거죠.

둘째, 사용자 로컬 시간으로 보여줘야 한다면(각자 자기 타임존으로) 그 부분만 클라이언트에서 렌더하세요. 'use client' 컴포넌트로 빼고 useEffect가 돈 뒤에(마운트 이후) 포맷하면 서버는 자리표시자만 그리고 실제 시간은 클라에서 채워집니다. mismatch가 원천적으로 안 생겨요.
nnext_haeun(guest)3개월 전
오 timeZone 고정으로 하니까 경고 사라졌어요. 근데 하나 더 궁금한 게, "3분 전", "방금 전" 같은 상대시간을 보여주는 컴포넌트도 있거든요. 이건 계속 현재시각 기준으로 바뀌는 거라 서버에서 고정할 수가 없는데 이런 건 어떻게 하나요?
김김영삼3개월 전
상대시간은 본질적으로 "지금"에 의존하니까 서버에서 확정할 수가 없어요. 그건 두 번째 방법으로 가야 합니다. 클라이언트 전용 컴포넌트로 빼세요.

방법은, 그 컴포넌트에 mounted 상태를 하나 두고 useEffect에서 true로 바꾼 다음, mounted가 false인 동안(=서버 렌더 + 첫 클라 렌더)에는 절대시각이나 빈 값 같은 안정적인 값을 보여주고, mounted가 true가 된 뒤에 상대시간을 계산해서 보여주는 거예요. 서버와 첫 클라 렌더가 동일하니 hydration이 통과하고, 그 직후 상대시간으로 바뀝니다.

더 간단하게는 그 컴포넌트를 next/dynamic으로 ssr: false 걸어서 아예 서버 렌더에서 빼버려도 됩니다. 다만 그 부분이 잠깐 비어 보이니 레이아웃 시프트 신경 쓰이면 첫 방법이 나아요. 그리고 갱신이 필요하면 setInterval로 1분마다 다시 계산하고 cleanup 잊지 마시고요.
nnext_haeun(guest)3개월 전
말씀대로 mounted 플래그로 처리하니 딱 됩니다. 두 케이스 확실히 정리됐어요. 감사합니다!
Ctrl+Enter로 등록