🙌

Next.js で Props に Date を渡す(superjson)

2022/03/03に公開

Next.js の getServerSideProps で、 Prisma を使って取得したデータを return すると以下のようなエラーがでます。

Reason: `object` ("[object Date]") cannot be serialized as JSON. 
Please only return JSON serializable data types.

Next.js (に限らずSSR)ではサーバーサイドで React を描画したあと、クライアントでそれを hydrate する必要があるため、Propsは文字列にシリアライズ/デシリアライズ可能でなければならないためです。Dateを JSON.stringify することはできますが、ISO形式の文字列になってしまうため、デシリアライズする際に「元が文字列だったのか、Dateオブジェクトだったのか」という情報がなくなってしまうため、ちゃんとデシリアライズすることができません。

Prisma を使っていると、RDBの DateTime カラムは JavaScript の Date 型に変換してくれるため、Prisma で取得したデータをそのまま Props として return するとこのケースに当たります。
対応としては getServerSideProps 側で Date を文字列に変換してしまうというのが素朴ですが

return {
  props: {
    users: users.map(user => ({
      ...user,
      createdAt: user.createdAt.toString()
    }))
  }
}

このような記述になりがちで、面倒ですね。Prisma の relation を使うなどしてオブジェクトが入れ子になっている場合はより面倒な記述になってしまいます。また、受け取る React Component 側で createdAt を Date として使いたい場合に都度 new Date(props.createdAt) とする必要もあります。

superjson でシリアライズ/デシリアライズする

babel を使う場合

Next.js が Props の中の Dateオブジェクトをうまくシリアライズ/デシリアライズできれば良いわけです。
そこで superjson というモジュールを使う方法があったので紹介します。ちなみに Blitz.js 製なので、まさにこのようなケースのために作られていそうです。
superjson は以下のように、シリアライズの際に meta 情報を付加します

const jsonString = superjson.stringify({ date: new Date(0) });

// jsonString === '{"json":{"date":"1970-01-01T00:00:00.000Z"},"meta":{"values":{date:"Date"}}}'

これでうまく元の Props にデシリアライズ可能になるわけです。
これをうまいこと組み込む babel-plugin-superjson-next も同様に作られていて、使い方もリンク先に書いてありますが、 .babelrc に追加するだけですみます

{
  presets: ['next/babel'],
  plugins: [
    ...
    'superjson-next' // 👈
  ]
}

簡単すぎる!これで getServerSideProps から Date を return して、受け取った Page Component でそのまま Date として受け取ることができるようになりました 🎉

[追記] SWC を使う場合

SWC を使う場合は、 https://www.npmjs.com/package/next-superjson-plugin を使うことができます。こちらも blitz 製なので安心して使うことができますね。使い方はリンク先を見るのが一番ですが、やはり next.config.js で plugin を追加するだけです。

module.exports = {
  experimental: {
    swcPlugins: [["next-superjson-plugin", {}]],
  },
};

Discussion