Astro SSR 하이드레이션 미스매치: 서버/브라우저 타임존 불일치 (UTC vs KST)
💡 TL;DR — Astro SSR에서 날짜 포맷 렌더링 시 서버(UTC)와 브라우저(KST) 타임존이 달라 하이드레이션 미스매치가 발생했다.
toLocaleDateString에 timeZone: 'Asia/Seoul' 옵션을 명시하여 해결했으며, 하이드레이션 경고가 0건으로 개선되었다.
🚨 문제 상황
⚖️ 기대 동작 vs 실제 동작
- 기대: 서버에서 렌더링한 날짜 문자열과 브라우저에서 하이드레이션 시 생성하는 날짜 문자열이 동일해야 한다.
- 실제: 서버는 UTC 기준으로
2026-04-12를 렌더링하고, 브라우저는 KST 기준으로2026-04-13을 렌더링한다.
한국 시간 자정~오전 9시 사이에 서버와 클라이언트의 날짜가 하루 차이 나면서 하이드레이션 미스매치가 발생한다.
🔍 조사 과정
- 에러 메시지 분석 콘솔 경고의 Server/Client 값 차이를 보고 날짜 값 자체가 다르다는 것을 확인했다.
날짜 로직이 아닌 렌더링 환경의 차이가 원인일 가능성이 높다고 판단했다.
2. 서버 로그에서 렌더링 시점의 날짜 확인
SSR 핸들러에 console.log(new Date().toISOString())를 추가하여 서버 시간을 확인했다.
서버는 UTC 기준으로 동작하고 있었다.
2026-04-12T18:30:00.000Z 시점에 toLocaleDateString()은 4/12/2026을 반환했다.
3. 브라우저 환경 재현
같은 시각에 브라우저에서 new Date().toLocaleDateString('ko-KR')을 실행했다.
브라우저는 시스템 타임존(Asia/Seoul)을 사용하므로 UTC+9가 적용되어 2026. 4. 13.을 반환했다.
여기서 원인 발견: toLocaleDateString() 호출 시 타임존을 명시하지 않으면 서버와 클라이언트에서 각각 다른 타임존이 적용되는 것이 근본 원인이었다.
🧩 근본 원인
JavaScript의 Date.prototype.toLocaleDateString()은 타임존을 명시적으로 지정하지 않으면 실행 환경의 시스템 타임존을 사용한다.
SSR 서버는 보통 UTC로 설정되어 있고, 사용자 브라우저는 로컬 타임존(한국의 경우 Asia/Seoul, UTC+9)을 사용한다.
문제가 되는 코드:
// 타임존 미지정 — 서버(UTC)와 브라우저(KST)에서 다른 결과
const formatted = new Date(post.date).toLocaleDateString('ko-KR');
UTC 기준 2026-04-12T18:00:00Z는 KST 기준으로 2026-04-13T03:00:00+09:00이다.
같은 시각인데 날짜가 하루 차이 나므로, 서버 HTML과 클라이언트 하이드레이션 결과가 달라진다.
🛠️ 해결 방법
세 가지 대안을 검토한 후 대안 A를 선택했다.
Before:
const formatted = new Date(post.date).toLocaleDateString('ko-KR');
After (대안 A 적용):
// 서버와 클라이언트 모두 동일한 타임존을 명시적으로 사용
const formatted = new Date(post.date).toLocaleDateString('ko-KR', {
timeZone: 'Asia/Seoul',
});
🔄 PAAR: SSR 날짜 렌더링 타임존 불일치 해결
-
Problem Astro SSR에서 날짜를 렌더링할 때 서버(UTC)와 브라우저(KST)의 타임존이 달라 하이드레이션 미스매치가 발생했다.
한국 사용자에게 날짜가 깜빡이며 바뀌는 UX 문제를 일으켰고, 콘솔 경고가 쌓여 다른 에러를 가리는 부작용도 있었다.
-
Analyze 세 가지 대안을 비교했다.
**A. ****
toLocaleDateString**에timeZone옵션 명시 — 변경 범위가 최소이고, 별도 라이브러리 없이 해결 가능하다. 다만 모든 날짜 포맷 호출마다 옵션을 넣어야 한다.B. 서버 환경변수
TZ=Asia/Seoul설정 — 코드 변경이 없지만, 서버 전역 타임존을 바꾸면 로깅, cron 등 다른 시스템에 영향을 줄 수 있다.C. 날짜를 서버에서만 문자열로 처리 — 하이드레이션 문제를 원천 차단하지만, 클라이언트에서 날짜 재계산이 필요한 경우 유연성이 떨어진다.
-
Action 대안 A를 선택했다.
코드 변경이 명확하고, 서버 인프라에 의존하지 않으며, 날짜를 다루는 유틸 함수 하나에 타임존을 집중할 수 있어 유지보수가 가장 용이했다.
-
Result 하이드레이션 미스매치 경고가 0건으로 감소했다.
날짜가 깜빡이며 바뀌는 UX 문제가 완전히 해결되었다.
브라우저 콘솔에서 불필요한 경고가 사라져 실제 에러를 더 빨리 발견할 수 있게 되었다.
🛡️ 재발 방지
- 날짜 포맷 유틸 함수
formatDate(date: Date)를 만들어timeZone: 'Asia/Seoul'을 기본 포함시켰다.
프로젝트 내 모든 날짜 렌더링을 이 함수를 통해 처리하도록 통일했다.
- ESLint 규칙으로
toLocaleDateString직접 호출 시 경고를 표시하도록 설정했다.
formatDate 유틸 사용을 유도하여 타임존 누락을 방지한다.
- SSR 테스트에 타임존별 렌더링 일치 여부를 확인하는 케이스를 추가했다.
TZ=UTC와 TZ=Asia/Seoul에서 각각 렌더링 결과가 동일한지 검증한다.
💎 교훈
- SSR 환경에서
Date객체를 사용할 때는 항상 타임존을 명시적으로 지정해야 한다.
“서버와 클라이언트는 같은 코드를 다른 환경에서 실행한다”는 점을 항상 의식해야 한다.
toLocaleDateString,toLocaleTimeString등 locale 관련 메서드는 타임존에 민감하다.
SSR에서 이런 메서드를 사용할 때는 반드시 timeZone 옵션을 포함시켜야 한다.
- 하이드레이션 미스매치는 단순히 콘솔 경고로 끝나지 않는다.
사용자에게는 콘텐츠가 깜빡이는 UX 문제로 체감되고, React가 전체 트리를 재렌더링하므로 성능에도 영향을 미친다.
📚 References
- Astro 공식 문서: On-demand Rendering (SSR)
- MDN: Date.prototype.toLocaleDateString() —
timeZone옵션 설명 포함 - React 공식 문서: hydrateRoot — Hydration Mismatch 에러 가이드
- Next.js: Text content does not match server-rendered HTML — 하이드레이션 에러 해결 가이드
함께 보면 좋은 콘텐츠
-
Engineering Opinion2026 카카오 신입공채 코딩 테스트 후기
2025년 하반기에 카카오에서 대규모 공채가 열렸다.
- Engineering Case Study
Geohash 기반의 캐싱으로 API 중복 호출 방지하기
“땅따먹기” 라는 게이미피케이션 기능을 적용한 러닝 앱 서비스 [요이땅]을 개발하며 가장 큰 고민이 있었다.
- Engineering Guide
React Complier에 대하여
2025년 10월 1일 React 19.2가 정식으로 출시가 되었다. 더불어 10월 7-8일에 “React Conf 2025” 가 진행되면서 최신 React에 대한 새로운 주제가 많이 발표되었다.