logo

日付を扱うときはSSRとCSRの違いに注意

2026-05-09
15 days ago

開発環境

  • Next.js 16.2.1(App Router)
  • date-fns 4.1.0

前提

Next.js の App Router では、コンポーネントはデフォルトで SSR(サーバーサイドレンダリング)で動作します。

"use client" を付けることで CSR(クライアントサイドレンダリング)に切り替えられます。

本題

事象

日時を表示するコンポーネントで、時刻が UTC で表示されていました。

日本時間(JST)で表示してほしいのに、9時間ずれた時刻が出てきます。

// (修正前)
import { format } from 'date-fns'

const SomeDetail = ({ item }) => {
  return (
    <div>
      <p>{format(new Date(item.createdAt), 'yyyy/MM/dd HH:mm')}</p>
    </div>
  )
}

export default SomeDetail

なぜ UTC で表示されるのか

date-fns の format() は実行環境のタイムゾーンを参照します。

  • SSR(サーバー):サーバーのタイムゾーン(多くの場合 UTC)
  • CSR(ブラウザ):ユーザーのブラウザのタイムゾーン(日本なら JST)

App Router のデフォルトはサーバーコンポーネントです。 "use client" を書き忘れると、format() がサーバーで実行されてしまいます。

⚠️ 「日付処理なのに時刻がずれる」と感じたら、まず実行環境を疑う。

修正

"use client" を追加して CSR に変更します。

// (修正後)
'use client'

import { format } from 'date-fns'

const SomeDetail = ({ item }) => {
  return (
    <div>
      <p>{format(new Date(item.createdAt), 'yyyy/MM/dd HH:mm')}</p>
    </div>
  )
}

export default SomeDetail

👉 これだけで format() がブラウザ上で実行されるようになり、ユーザーのローカルタイムゾーン(JST)が適用されます。

別の解決アプローチ

CSR に切り替える以外にも対処法はあります。

  • @date-fns/tz を使ってタイムゾーンを明示指定する
  • Intl.DateTimeFormattimeZone オプションを渡す

今回のケースは「ユーザーのローカル時刻で表示したい」という要件だったため、"use client" による CSR 化がシンプルな解決策でした。

タイムゾーンを固定したい場面(例:日本時間で統一したいが、SSR のまま使いたい)では @date-fns/tzformatInTimeZone() が有効です。

さいごに

SSR と CSR でタイムゾーンの扱いが変わるという落とし穴は、意識していないと踏みやすいです。

date-fns に限らず、実行環境のタイムゾーンに依存する処理は「どこで実行されるか」を意識することが大切です。

Next.js App Router を使うなら「デフォルトはサーバー」という前提を常に頭に置いておきたい。

参照