hacca8

getStaticPropsでFirestoreのTimestampを渡したらエラー

Admin SDKを利用してFirestoreのデータをgetStaticPropsのpropsに渡すとエラーになりました。

Reason: `object` ("[object Object]") cannot be serialized as JSON.

エラー時のコードはこのようになっていました。

export const getStaticProps = async () => {
  const ref = db.collection('post')
  const snapshot = await ref.doc('slug').get()
  const data = snapshot.data()
  return {
    props: {
      createdAt: data.createdAt
    }
  }
}

dbはよくあるfirebase-adminを呼び出す関数とします。

var admin = require("firebase-admin");

try {
  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL
    })
  });
} catch {}

export const db = admin.firestore();

原因

フィールドのcreatedAtをTimestamp型で設定していたため、JSONにシリアライズできない状態でした。
propsに渡す前にキャストなどしてあげる必要があります。

対処

エラー内容を見るとcreatedAtはオブジェクトなので、Date型にしてみます。
propsを以下のように変更します。

props: {
  createdAt: data.createdAt.toDate()
}

するとエラーが返ってきました。

Reason: `object` ("[object Date]") cannot be serialized as JSON.

Date型も渡せないようなので、JSON文字列にしてみます。

props: {
  createdAt: JSON.stringify(data.createdAt)
}

これでエラーが解消されました。
このままですと{"_seconds":1658588400,"_nanoseconds":532000000}のような文字列がpropsに渡ります。
例えば、このブログページではクライアント側で機能的にDateの値は利用していないので2022.7.24のような文字列を渡すだけでもOKです。
例としてそのような形式で返してみます。

export const getStaticProps = async () => {
  const ref = db.collection('post')
  const snapshot = await ref.doc('slug').get()
  const data = snapshot.data()

  // timestampを日付の文字列に変換
  const createdAt = data.createdAt.toDate()
  const str = `${createdAt.getFullYear()}.${createdAt.getMonth() + 1}.${createdAt.getDate()}`
  return {
    props: {
      createdAt: str
    }
  }
}

これでpropsに2022.7.24が渡りました。

PS

Dateを決まった文字列で返すのは、バックエンド側でフロントの仕様を把握しないといけないので、本来は微妙な実装かと思います。
ただ、今回のようなSSGの場合、わざわざ毎回クライアント側で日付処理させる必要がありません。propsに渡す前に変換してあげる方がよさそうです。
ケースによってgetStaticPropsはバックエンドとフロントエンドの間のようなものとして捉えるとよいかも知れません。