Next.js(Pages Router) v16에서 시간대 처리하는 방법
사용자의 로컬 시간대로 시간 표시하기
문제
애플리케이션이 사용자의 위치를 고려하지 않고 시간을 표시할 때 혼란이 발생합니다. "오후 3시"로 예약된 회의는 뉴욕, 런던 또는 도쿄에 있는 사용자에 따라 다른 시점을 의미합니다. 서버가 UTC 또는 자체 시간대로 시간을 렌더링하면 다른 지역의 사용자는 잘못된 현지 시간을 보게 되어 약속을 놓치거나 일정 오류가 발생합니다. 동일한 타임스탬프도 각 사용자의 시간대에 따라 다르게 해석되어야 합니다.
해결책
ISO 8601이나 유닉스 타임스탬프와 같은 범용 형식으로 타임스탬프를 저장한 다음, 클라이언트에서 사용자의 현지 시간대로 표시하도록 형식을 지정합니다. 브라우저의 국제화 API를 사용하여 사용자의 시간대를 감지하고 이를 react-intl의 형식 지정 함수에 전달합니다. 이렇게 하면 모든 사용자가 자신의 시간대에 맞게 조정된 시간을 볼 수 있어 이벤트 발생 시간에 대한 모호함이 제거됩니다.
단계
1. 클라이언트에서 사용자의 시간대 감지하기
브라우저의 Intl.DateTimeFormat().resolvedOptions().timeZone은 America/New_York 또는 Europe/London과 같은 사용자의 IANA 시간대 식별자를 반환합니다. 이 값에 접근하기 위한 커스텀 훅을 만듭니다.
import { useState, useEffect } from "react";
export function useUserTimeZone() {
const [timeZone, setTimeZone] = useState<string>("UTC");
useEffect(() => {
const detected = Intl.DateTimeFormat().resolvedOptions().timeZone;
setTimeZone(detected);
}, []);
return timeZone;
}
이 훅은 클라이언트에서만 실행되어 하이드레이션 불일치를 방지하며, 시간대가 감지될 때까지 기본값으로 UTC를 사용합니다.
2. FormattedDate를 사용하여 사용자의 시간대로 날짜 형식 지정하기
react-intl의 형식 지정 메서드에 timeZone 옵션을 전달하여 표시에 사용할 시간대를 제어합니다. 감지된 시간대와 함께 <FormattedDate> 컴포넌트를 사용합니다.
import { FormattedDate } from "react-intl";
import { useUserTimeZone } from "../hooks/useUserTimeZone";
interface EventDateProps {
timestamp: string;
}
export function EventDate({ timestamp }: EventDateProps) {
const userTimeZone = useUserTimeZone();
return (
<FormattedDate
value={new Date(timestamp)}
timeZone={userTimeZone}
year="numeric"
month="long"
day="numeric"
hour="numeric"
minute="2-digit"
timeZoneName="short"
/>
);
}
timeZone 속성은 서버나 UTC가 아닌 사용자의 현지 시간대로 날짜가 형식화되도록 보장합니다.
3. useIntl을 사용하여 명령적으로 시간 포맷팅하기
컴포넌트가 아닌 포맷팅된 문자열이 필요한 시나리오에서는 useIntl 훅을 사용하여 명령적 포맷팅 API에 접근하세요.
import { useIntl } from "react-intl";
import { useUserTimeZone } from "../hooks/useUserTimeZone";
interface MeetingTimeProps {
startTime: string;
}
export function MeetingTime({ startTime }: MeetingTimeProps) {
const intl = useIntl();
const userTimeZone = useUserTimeZone();
const formattedTime = intl.formatDate(new Date(startTime), {
timeZone: userTimeZone,
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
});
return <span title={startTime}>{formattedTime}</span>;
}
formatDate 메서드는 timeZone을 포함한 DateTimeFormatOptions를 두 번째 인자로 받아 타임스탬프가 표시되는 방식을 완전히 제어할 수 있게 합니다.
4. 서버 타임스탬프를 ISO 문자열로 전달하기
getServerSideProps에서 요청 시점에 데이터를 가져와 페이지 컴포넌트에 props로 전달합니다. 날짜를 ISO 8601 문자열로 직렬화하여 시간대 정보를 보존하세요.
import { GetServerSideProps } from "next";
interface Event {
id: string;
title: string;
startTime: string;
}
interface EventPageProps {
event: Event;
}
export const getServerSideProps: GetServerSideProps<
EventPageProps
> = async () => {
const event = {
id: "1",
title: "Team Meeting",
startTime: new Date("2025-02-15T15:00:00Z").toISOString(),
};
return {
props: { event },
};
};
export default function EventPage({ event }: EventPageProps) {
return (
<div>
<h1>{event.title}</h1>
<EventDate timestamp={event.startTime} />
</div>
);
}
ISO 문자열은 정확한 시간 순간을 보존하여 클라이언트 측 포맷팅이 어떤 시간대로든 정확하게 변환할 수 있게 합니다.