TechFeedTechFeed
Frontend

Text content did not match, 날짜, toLocaleString | 한국 시간이 서버와 어긋나면?

Text content did not match에서 서버와 브라우저 날짜 글자가 다르면 toLocaleString이 시간대와 로케일을 환경 기본값으로 쓴 상태다. Asia/Seoul을 박거나 첫 렌더는 고정하고 마운트 뒤에 바꾼다. 하이드레이션 일반 원인, DB 타임스탬프츠와 자리를 나눈다. Next.js, React, Intl, 버셀, 프론트엔드, 개발자 기준. 2026년 9월 Next.js·MDN 공식 문서.

by

Text content did not match에서 서버 줄과 클라이언트 줄이 날짜 글자면, toLocaleString이 서버 시간대와 브라우저 시간대를 따로 쓴 상태입니다. 로케일과 timeZone을 같은 문자열로 박거나, 첫 그림은 고정하고 로컬 시각은 마운트 뒤에 넣으세요.


버셀 함수는 보통 협정시로 돌고, 맥 브라우저는 한국 표준시로 그립니다. 같은 순간인데도 오후 3시와 오전 6시처럼 아홉 시간이 갈립니다. 넥스트 관리자 화면에 발행 시각을 그대로 찍으면, 개발 서버는 조용하고 프리뷰만 빨개집니다.


랜덤 값이나 window를 읽는 줄은 하이드레이션 일반 글에서 다룹니다. 날짜 문자열만 여기서 봅니다. 근거는 넥스트 하이드레이션 문서MDN toLocaleString에 있습니다.


콘솔에 날짜 글자만 다르면 어디를 보나

콘솔 diff의 Server와 Client가 시각 글자면 포맷 옵션부터 맞춥니다.


리액트는 서버가 보낸 HTML 글자와 브라우저 첫 렌더 글자가 한 칸이라도 다르면 그 노드를 버리고 다시 그립니다. 넥스트가 띄우는 제목이 Text content did not match, 또는 Text content does not match server-rendered HTML인 경우가 많습니다. 옆에 Server: "9/2/2026, 6:00:00 AM" Client: "2026. 9. 2. 오후 3:00"처럼 찍히면, 컴포넌트 트리가 아니라 날짜를 글자로 바꾼 한 줄이 범인입니다.


제가 쓰는 발행 목록도 같은 줄이 났습니다. 맥에서 개발 서버는 한국 표준시라 화면이 맞고, 버셀 프리뷰는 함수가 협정시라 아홉 시간이 밀렸습니다. 카카오 알림 시각을 관리 화면에 그대로 찍을 때도 같은 패턴입니다. 훅 규칙이나 잘못된 태그 중첩은 콘솔 문장이 다릅니다. 날짜 두 줄이 보이면 서식 함수만 보세요.


혼자 여러 사이트를 돌리면 로컬 맥과 배포 함수의 시계가 항상 같다고 믿기 쉽습니다. 집 컴퓨터는 시스템 설정이 서울이고, 배포 쪽은 협정시를 기본으로 둡니다. 그 차이를 모르고 발행 시각을 본문에 넣으면, 개발할 땐 아무 일도 없다가 프리뷰를 여는 순간 빨간 줄이 납니다. 토스 웹훅 수신 시각을 표로 보여줄 때도 같은 실수가 납니다.


먼저 기억할 것 | 로케일만 ko-KR로 바꿔도 시간대는 환경 기본값입니다. timeZone을 안 박으면 서버와 브라우저가 다른 시각을 그립니다.


서버는 협정시, 브라우저는 한국 표준시

함수는 UTC, 맥은 Asia/Seoul입니다. 옵션을 안 주면 환경 기본값을 씁니다.


new Date(iso).toLocaleString()은 인자를 안 받으면 그 프로세스의 로케일과 시간대를 씁니다. 노드 문서는 ICU가 깔린 런타임에서 이 서식을 지원한다고 적습니다. 버셀 서버리스는 기본이 협정시인 경우가 많고, 집 맥은 한국 표준시입니다. 같은 아이소 문자열을 넣어도 출력 글자가 달라집니다.


콘솔에 보이는 것먼저 할 일
같은 시각인데 아홉 시간 차이시간대 기본값이 갈림timeZone을 Asia/Seoul 또는 UTC로 고정
9/2/2026 과 2026. 9. 2. 처럼 구두점만 다름로케일 기본값이 갈림locale을 ko-KR로 고정
오전/오후 대 AM/PMhour12 기본값이 갈림hour12: false 또는 명시에 true
초만 1 차이렌더마다 new Date()를 다시 부름지금 시각은 마운트 뒤에만, 일반 원인 글
요일 이름만 영어/한글weekday 서식이 로케일을 따름weekday를 빼거나 locale을 박기

로케일만 맞춰도 시간대는 그대로입니다. 한글 로케일만 넣으면 구두점은 맞추지만, 서버가 협정시면 여전히 아홉 시간이 밀립니다. 두 칸을 같이 박아야 첫 그림이 같습니다. 초만 어긋나면 날짜 서식 칸이 아닙니다. 매 렌더 지금 시각을 다시 읽으면 일반 하이드레이션 글에서 다룹니다.


네이버나 카카오 쪽 웹훅 시각을 관리 목록에 넣을 때도 같습니다. 로그에 찍힌 값은 아이소 문자열인데, 화면에서 인자 없이 로케일 서식을 호출하면 프리뷰와 로컬이 다른 오후·오전을 보여 줍니다. 독자가 보는 건 관리자 화면의 그 한 줄입니다.


서버 협정시와 브라우저 한국 표준시가 같은 순간을 다른 글자로 그리는 장면
옵션을 안 박으면 함수는 UTC, 맥 브라우저는 한국 표준시로 날짜 글자를 만든다

로케일과 시간대를 같은 문자열로 박는다

locale과 timeZone을 둘 다 넘기면 서버와 브라우저가 같은 글자를 그립니다.


화면이 항상 한국 시각이면 Intl.DateTimeFormatko-KRAsia/Seoul을 넣습니다. MDN은 시간대 이름을 IANA 식별자로 적으라고 합니다. KST 세 글자는 구현마다 거절되거나 다른 뜻으로 읽힐 수 있으니 쓰지 마세요. hour12와 연·월·일 자릿수까지 적으면 구두점까지 맞춰집니다.


서버에서 이미 글자를 만들어 프롭으로 넘기는 방법도 같습니다. 서버 컴포넌트가 서식을 끝내면 클라이언트는 그 문자열만 그립니다. 브라우저에서 로케일 서식을 다시 호출하지 마세요. 호출하는 순간 브라우저 기본값이 끼어듭니다.


목록 페이지처럼 같은 서식을 여러 줄에 쓰면, 포맷터 객체를 모듈 바깥에서 한 번만 만드는 편이 낫습니다. 매 행마다 옵션 객체를 새로 만들면 실수로 시간대를 빠뜨리기 쉽습니다. 한국 시각이 답인 화면은 서울 식별자를 상수로 두고 모든 칸이 그걸 쓰게 하세요.


한국 표준시로 서식을 고정
const seoulPostedAt = new Intl.DateTimeFormat('ko-KR', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false, }); function PostedAt({ iso }) { const text = seoulPostedAt.format(new Date(iso)); return <time dateTime={iso}>{text}</time>; } // 나쁜 예: 인자 없는 toLocaleString() // return <time>{new Date(iso).toLocaleString()}</time>;

주의 | process.env.TZ = 'Asia/Seoul'은 프로세스 전역입니다. 크론 한 줄에는 통하지만, 요청마다 다른 시간대를 보여 줄 앱에는 맞지 않습니다. 서식 옵션으로 칸을 나누세요.


사용자 컴퓨터 시각은 마운트 뒤에만

사용자 로컬 시각이 필요하면 첫 렌더는 플레이스홀더, 마운트 뒤에 바꿉니다.


독자 노트북이 한국이 아닐 수 있습니다. 그때는 서버가 그 시간대를 모릅니다. 쿠키에 시간대를 심기 전에는, 첫 HTML을 서버와 똑같이 두고 브라우저가 붙은 뒤에만 로컬 글자로 바꿉니다. 넥스트 문서의 첫 해결도 useEffect로 클라이언트에서만 값을 넣는 순서입니다.


리액트 외부 저장소 훅은 서버 스냅샷을 하이드레이션 순간에 씁니다. 세 번째 인자로 서버와 같은 글자를 넘기면 첫 그림이 맞고, 구독 이후에 브라우저 시각으로 바뀝니다. 빈 구독을 넣어도, 지금 시각처럼 외부 값이 렌더마다 바뀌는 칸을 서버 스냅샷으로 잠글 수 있습니다.


자리 표시는 레이아웃이 흔들리지 않게 비슷한 길이로 두는 게 좋습니다. 빈 칸이면 목록 높이가 한 번 접혔다가 펴집니다. 시각 확인 중처럼 짧은 한글을 넣고, 나중에 같은 칸에 실제 시각을 덮어 쓰세요. 상대 시각 삼 분 전도 이 순서입니다. 첫 HTML에 삼 분 전을 넣으면 서버가 그린 분과 브라우저가 그린 분이 갈립니다.


첫 그림은 고정, 로컬 시각은 이후에
import { useEffect, useState, useSyncExternalStore } from 'react'; function LocalPostedAt({ iso }) { const [text, setText] = useState(''); useEffect(() => { setText(new Date(iso).toLocaleString('ko-KR')); }, [iso]); return <time dateTime={iso}>{text || '시각 확인 중'}</time>; } function LiveClock() { const label = useSyncExternalStore( () => () => {}, () => new Date().toLocaleString('ko-KR'), () => '시각 확인 중' ); return <time>{label}</time>; }
서버 HTML과 같은 첫 그림을 두고 마운트 뒤에 로컬 시각으로 바꾸는 작업
사용자 시간대는 서버가 모른다. 첫 글자를 맞춘 뒤 브라우저에서만 바꾼다

경고 숨기기는 time 한 칸만

suppressHydrationWarning은 time 요소 한 칸용입니다. 부모에 걸면 자식은 그대로 경고합니다.


넥스트와 리액트 문서가 예시로 드는 탈출구가 <time dateTime="..." suppressHydrationWarning />입니다. 의도적으로 서버와 브라우저 글자가 다를 때, 그 텍스트 노드 한 칸의 경고만 끕니다. 한 단계만 적용되고, 자식 트리까지 덮지 않습니다. 카드 전체에 걸면 날짜 칸은 그대로 빨갭니다.


수단언제한계
Intl에 locale+timeZone화면이 항상 한국 시각해외 독자 로컬 시각은 못 맞춤
서버에서 글자를 만들어 전달목록·RSS·OG처럼 값이 이미 있음클라이언트가 다시 서식하면 재발
useEffect 이후 교체독자 컴퓨터 시간대가 필요첫 페인트는 자리 표시
useSyncExternalStore 서버 스냅샷지금 시각처럼 외부 값이 계속 바뀜구독 함수를 비워도 스냅샷은 필수
suppressHydrationWarningtime 한 칸이 의도적으로 다름한 단계만, 잘못된 HTML은 못 숨김

경고만 끄고 서식을 그대로 두면, 사용자는 깜빡이는 잘못된 시각을 한 번 봅니다. 관리 화면처럼 한국 시각이 답이면 옵션을 박는 쪽이 맞습니다. 문서도 이 속성을 탈출구로만 적습니다. 잘못된 태그 중첩이나 번역 확장 프로그램이 글을 고친 경우는 이 속성으로 숨기지 마세요. 날짜 한 칸이 의도적으로 다를 때만 씁니다.


time 한 칸에만 경고를 끈다
// 의도적으로 브라우저 로컬 시각을 바로 그릴 때만 function PostedAt({ iso }) { return ( <time dateTime={iso} suppressHydrationWarning> {new Date(iso).toLocaleString('ko-KR')} </time> ); } // 아래는 날짜 칸을 가리지 못한다 // <div suppressHydrationWarning> // <time>{new Date(iso).toLocaleString()}</time> // </div>

저장 칸·일반 하이드레이션과 자리 분리

DB 타임스탬프츠와 HTML 첫 그림은 다른 칸입니다.


포스트그레에 시각을 어떻게 넣는지, 아이소 문자열 끝에 Z를 붙이면 모니터링이 아홉 시간 미래로 읽는지, 그건 타임스탬프츠 글입니다. 이미 있는 아이소 값을 화면에 글자로 바꿀 때만 이 글에서 봅니다. 저장을 고쳐도 로케일 서식 기본값이 갈리면 콘솔은 그대로입니다. 로그 시각이 미래로 보이면 저장 칸을 먼저 열고, 화면만 빨개지면 서식 칸을 엽니다.


랜덤 아이디, localStorage 테마, p 안의 div, 번역 확장 프로그램은 하이드레이션 미스매치 쪽입니다. 콘솔에 날짜 두 줄이 없으면 이 글로 오지 마세요. Invalid hook call은 훅 디스패처가 비어 있는 줄이라 중복 리액트 글입니다.


  • [ ] 콘솔 Server/Client 줄이 날짜 글자인지 확인했다
  • [ ] toLocaleString 호출에 locale과 timeZone이 있는지 봤다
  • [ ] 로컬은 되고 프리뷰만이면 함수 시간대를 의심했다
  • [ ] 사용자 로컬 시각이면 첫 렌더를 고정하고 마운트 뒤에 바꿨다
  • [ ] suppressHydrationWarning은 time 한 칸에만 걸었다
  • [ ] Z 접미사·타임스탬프츠 저장 글과 커밋을 나누었다

화면 날짜 서식과 데이터베이스 타임존 저장을 나누어 점검하는 작업
화면 글자가 어긋난 줄과 저장 타입이 어긋난 줄은 고치는 파일이 다르다

참고 자료


내부 연계: 하이드레이션 미스매치, 타임스탬프츠와 Z 접미사, Invalid hook call 중복 리액트, Cannot find module, Invalid src와 remotePatterns


인용한 동작은 2026년 9월 공개 문서 기준입니다.


자주 묻는 질문

로케일만 ko-KR로 바꿨는데도 아홉 시간이 어긋납니다.

로케일은 구두점과 오전/오후 표기만 바꿉니다. 시간대는 프로세스 기본값입니다. timeZone: 'Asia/Seoul'을 같이 넣으세요. KST 세 글자는 쓰지 마세요.


로컬 개발은 되고 버셀 프리뷰만 빨개집니다.

맥은 한국 표준시, 함수는 협정시인 경우가 많습니다. 인자 없는 toLocaleString은 그 차이를 그대로 글자로 남깁니다. 옵션을 박으면 프리뷰와 로컬이 같은 줄을 그립니다.


div에 suppressHydrationWarning을 걸었는데 경고가 남습니다.

속성은 한 단계만 적용됩니다. 글자가 다른 time 노드에 직접 걸어야 합니다. 카드 래퍼에 걸면 자식 날짜는 그대로 미스매치입니다.


사용자 컴퓨터 시간대로 보여 주고 싶습니다.

서버는 그 값을 모릅니다. 첫 렌더는 서버와 같은 글자나 자리 표시로 두고, useEffect 또는 useSyncExternalStore 서버 스냅샷 뒤에 로컬 서식으로 바꾸세요. 쿠키에 시간대를 심기 전까지는 이 순서가 안전합니다.


3분 전 같은 상대 시각을 첫 화면에 넣으면요?

렌더 시각이 서버와 브라우저에서 다릅니다. 상대 글자는 마운트 뒤에만 계산하세요. 초 단위로 어긋나는 줄은 날짜 로케일 문제가 아니라, 지금 시각을 렌더에서 읽어서입니다.


타임스탬프츠로 저장하면 이 에러가 사라지나요?

저장 타입은 화면 글자와 다른 칸입니다. 컬럼이 맞아도 toLocaleString 기본값이 갈리면 콘솔은 그대로입니다. Z 접미사 오해는 타임스탬프츠 글, 날짜 하이드레이션은 이 글입니다.


Text content did not match에서 날짜 글자가 갈리면 서식이 서버 시간대와 브라우저 시간대를 따로 쓴 상태입니다. locale과 timeZone을 박거나, 첫 그림은 고정하고 로컬 시각은 마운트 뒤에 넣으세요. 관련 글: 하이드레이션 미스매치, 타임스탬프츠, 중복 리액트.


Text content did not matchtoLocaleString하이드레이션한국 표준시timeZoneIntlNext.jsReact버셀프론트엔드개발자날짜

관련 도구

함께 보면 좋은 문제 해결

EXPLORE / Frontend

이어서 읽어보기

전체 토픽 둘러보기