RSC에서 날짜 처리: 쿠키 기반 타임존

문제: Date 객체 직렬화, 서버/클라이언트 타임존 불일치, 하이드레이션 FOUT

해결: 서버에서 타임존 적용해서 렌더링

// proxy.js (Next.js 16 — 예전 middleware.js) - 타임존 감지
export function proxy(request) {
  const timezone =
    request.cookies.get('user-timezone')?.value ||
    request.headers.get('x-vercel-ip-timezone') || // Vercel. request.geo 는 15 에서 제거
    'UTC'

  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-user-timezone', timezone)
  // 응답 헤더가 아니라 요청 헤더로 넘겨야 서버 컴포넌트의 headers() 가 읽는다
  return NextResponse.next({ request: { headers: requestHeaders } })
}

// 서버 컴포넌트
const getUserTimezone = cache(async () => {
  return (await headers()).get('x-user-timezone') || 'UTC' // 15 부터 async
})

// 클라이언트 - 타임존 자동 감지 후 쿠키 저장
useEffect(() => {
  const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
  document.cookie = `user-timezone=${tz}; path=/; max-age=31536000`
}, [])

첫 방문은 인프라 추정값 사용, 이후 정확한 타임존 적용. FOUT 없음.123


대안: useSyncExternalStore (client component 전용)

'use client'

const timezoneStore = {
  getSnapshot: () => Intl.DateTimeFormat().resolvedOptions().timeZone,
  getServerSnapshot: () => 'UTC',
  subscribe: () => () => {},
}

function useTimezone() {
  return useSyncExternalStore(
    timezoneStore.subscribe,
    timezoneStore.getSnapshot,
    timezoneStore.getServerSnapshot
  )
}

서버: UTC → 클라이언트: 실제 타임존. 하이드레이션 에러 없음, 대신 FOUT 발생 — getServerSnapshot 은 서버 렌더와 하이드레이션 때만 쓰이고, 그 뒤 getSnapshot 값으로 다시 그린다.4


Footnotes

  1. proxy.js – Next.js: “The middleware file convention is deprecated and has been renamed to proxy.” / “NextResponse.next({ request: { headers: requestHeaders } }) to make requestHeaders available upstream” ↩

  2. How to upgrade to version 15 – Next.js: “The geo and ip properties on NextRequest have been removed as these values are provided by your hosting provider.” / “Previously synchronous Request-time APIs that rely on request information are now asynchronous” ↩

  3. Request headers – Vercel: “The name of the time zone for the location of the requester’s public IP address in ICANN Time Zone Database name format such as America/Chicago.” ↩

  4. useSyncExternalStore – React: “optional getServerSnapshot: A function that returns the initial snapshot of the data in the store. It will be used only during server rendering and during hydration of server-rendered content on the client.” ↩

`#509`