kk-web

【Next.js】SSRとTimezoneについて調べてみた【Day.js】

2022-04-27

仕事で Vercel の SSR 環境下において Timezone がおかしいと指摘をされまして。

Timezone の仕様をイマイチ理解していなかったなーと思い、しっかり調べてみました。


前提

  • Next.js
  • Day.js
  • Vercel

ローカル環境は日本とします。

実装

Day.js 側

Day.js に Timezone を継承したモジュールを作成しました。

import dayjs from "dayjs";
import "dayjs/locale/ja";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";

dayjs.extend(utc);
dayjs.extend(timezone);

dayjs.locale("ja");
dayjs.tz.setDefault("Asia/Tokyo");

export default dayjs;

Day.js の Timezone の仕様については調べたら山程情報が出てきますが。

ざくっと説明するなら dayjs().tz() と書いてはじめて Timezone が反映される仕様になっているとのことです。

Timezone を考慮した Page Container

まずは Timezone を考慮したケースからです。

import type { GetServerSideProps, NextPage } from "next";
import dayjs from "libs/dayjs";
import { useEffect, useState } from "react";

export type HomeProps = {
  hoge: string;
  fuga: string;
};

const Home: NextPage<HomeProps> = ({ hoge, fuga }: HomeProps) => {
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  console.log(isMounted, dayjs().tz().format("YYYY-MM-DD HH:mm:ss"));
  console.log(
    isMounted,
    dayjs("2022-01-01").tz().format("YYYY-MM-DD HH:mm:ss"),
  );

  return (
    <div>
      <div>{hoge}</div>
      <div>{fuga}</div>
    </div>
  );
};

export const getServerSideProps: GetServerSideProps<HomeProps> = async () => ({
  props: {
    hoge: dayjs().tz().format("YYYY-MM-DD HH:mm:ss"),
    fuga: dayjs("2022-01-01").tz().format("YYYY-MM-DD HH:mm:ss"),
  },
});

export default Home;

Timezone を考慮しない Page Container

続いて Timezone を考慮しない実装です。

import type { GetServerSideProps, NextPage } from "next";
import dayjs from "libs/dayjs";
import { useEffect, useState } from "react";

export type HomeProps = {
  hoge: string;
  fuga: string;
};

const Home: NextPage<HomeProps> = ({ hoge, fuga }: HomeProps) => {
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  console.log(isMounted, dayjs().format("YYYY-MM-DD HH:mm:ss"));
  console.log(isMounted, dayjs("2022-01-01").format("YYYY-MM-DD HH:mm:ss"));

  return (
    <div>
      <div>{hoge}</div>
      <div>{fuga}</div>
    </div>
  );
};

export const getServerSideProps: GetServerSideProps<HomeProps> = async () => ({
  props: {
    hoge: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    fuga: dayjs("2022-01-01").format("YYYY-MM-DD HH:mm:ss"),
  },
});

export default Home;

tz() を呼び出すか呼び出さないかの違いだけですね。

結果

現在時刻

まずは日付を指定しない、つまり現在の日時を取得するケースからです。

SSRCSR(Mount 前)CSR(Mount 後)
Timezone を考慮2022-04-27 00:27:452022-04-27 00:27:452022-04-27 00:27:45
Timezone を考慮しない2022-04-26 15:28:182022-04-27 00:28:182022-04-27 00:28:18

日時を指定

続いて日時を指定したケースです。

SSRCSR(Mount 前)CSR(Mount 後)
Timezone を考慮2022-01-01 09:00:002022-01-01 00:00:002022-01-01 00:00:00
Timezone を考慮しない2022-01-01 00:00:002022-01-01 00:00:002022-01-01 00:00:00

わかったこと

ややこしすぎてゲロ吐きそうです。

とはいえまじめに解説していこうと思います。

CSR 時はローカル環境で実行されるので、Mount の前後で値がほぼ同じなのは当たり前ですね。

なので以降は SSR と CSR の 2 つの環境を比較していこうと思います。

CSR の挙動

ローカル環境が日本であれば、Timezone に Asia/Tokyo を設定する/しないにかかわらず同じ値を返します。

当たり前ですね。

ただ逆に SSR との差分が発生し得るとも言えます。

SSR の挙動

ここがめちゃくちゃややこしいですね。

1 つずつ説明を書いていきます。

dayjs().tz().format("YYYY-MM-DD HH:mm:ss")

グリニッジ標準時における現在時刻を取得し、日本時間に計算し直しています。

したがって CSR と同じ値を返します。

dayjs("2022-01-01").tz().format("YYYY-MM-DD HH:mm:ss")

何らかの日時を日本時間に計算し直しています。

何らかの日時がグリニッジ標準時によって取得された値であれば、正しい日本時間が取得されます。

何らかの日時が日本時間によって取得された値であれば、よくわからない時間が取得されてしまいます。

dayjs().format("YYYY-MM-DD HH:mm:ss")

グリニッジ標準時における現在時刻を取得しています。

したがって CSR と異なる値を返します。

dayjs("2022-01-01").format("YYYY-MM-DD HH:mm:ss")

何らかの日時を取得しています。

何らかの日時がグリニッジ標準時によって取得された値であれば CSR と異なる値を返します。

何らかの日時が日本時間によって取得された値であれば、CSR と同じ値を返します。

備考

業務中に、Day.js において Timezone を考慮した処理が多いと、処理が重くなってめっちゃ挙動が遅くなることを確認しました。

Timezone の使用は最低限にすることを意識しましょう。


ややこしいですが、まとめてみるとわかるもんですね。

DB に格納されている日時がどこを基準にした値かによって処理がいろいろと複雑になるので、気をつけて扱うようにしましょう。

© 2018 kk-web