Next.js(Pages Router) v16에서 시간대를 처리하는 방법
사용자의 로컬 시간대로 시간 표시
문제
애플리케이션이 사용자의 위치를 고려하지 않고 시간을 표시하면 혼란이 발생합니다. "오후 3시"로 예약된 회의는 뉴욕, 런던, 도쿄 중 어디에서 보는지에 따라 다른 순간을 의미합니다. 서버가 UTC 또는 자체 시간대로 시간을 렌더링하면 다른 지역의 사용자는 잘못된 로컬 시간을 보게 되어 약속을 놓치고 일정 오류가 발생합니다. 동일한 타임스탬프는 각 사용자의 시간대에 따라 다르게 해석되어야 합니다.
솔루션
ISO 8601 또는 Unix 타임스탬프와 같은 범용 형식으로 타임스탬프를 저장한 다음 클라이언트에서 사용자의 로컬 시간대로 표시하도록 형식을 지정합니다. 브라우저의 국제화 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 prop은 날짜가 서버나 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 문자열은 정확한 시점을 보존하므로, 클라이언트 측 포맷팅이 이를 모든 타임존으로 정확하게 변환할 수 있습니다.