🔥

tRPCでPrismaのDate型がstringで帰ってくる問題とsuperjsonによる解決策

2025/02/03に公開

背景

再現に必要な各種packageのバージョンは以下の通りです。

  • Next.js 15.0系
  • Prisma 5.系統
  • trpc 10.系統

trpcで取得したdataをfrontendで受け取ってuseStateのstateとして取り込もうとした際に下記のエラーが生じました。

import { useState, useEffect } from 'react';
import { User } from '@prisma/client';
import { trpc } from '@/server/trpc/trpcClient';
// (中略)

const Page = () => {
  const [users, setUsers] = useState<User[]>();
  const { data } = trpc.getUsers.useQuery();
  useEffect(() => {
    if (data) {
      setUsers(data);
    }
  }, [setUsers, data]);

  return <div>{users && users[0].name}</div>;
};

export default Page;


        Types of property 'created_at' are incompatible.
          Type 'string | null' is not assignable to type 'Date | null'.
            Type 'string' is not assignable to type 'Date'.ts(2345)

解決策

基本的にこのコミュニティで議論されている通りでした。
https://discord-questions.trpc.io/m/1070455735533703269
結論としてはServer側とClient側との両方でtransformerにsuperjsonを設定することで解決しました。

trpcの公式ドキュメントでも、
transformerにsuperjsonを設定するプロセスが記載されています。
以下は公式ドキュメントの転載です。

Server側

import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
export const t = initTRPC.create({
  transformer: superjson,
});

Client側

(Next.jsの場合)

import { createTRPCNext } from '@trpc/next';
import type { AppRouter } from '~/server/routers/_app';
import superjson from 'superjson';
// [...]
export const trpc = createTRPCNext<AppRouter>({
  config({ ctx }) {
    return {
      transformer: superjson, // <-- ここに追加
    };
  },
  // [...]
});

私の場合、trpcのproviderのpageを参考にして、以下のように設定しました。

export function App() {
  const [queryClient] = useState(() => new QueryClient());
  const [trpcClient] = useState(() =>
    trpc.createClient({
      transformer: superjson, // <-- ここに追加
      links: [
        httpBatchLink({
          url: 'http://localhost:3000/trpc',
          async headers() {
            return {
              authorization: getAuthCookie(),
            };
          },
        }),
      ],
    }),
  );
  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        {/* Your app here */}
      </QueryClientProvider>
    </trpc.Provider>
  );
}

何が問題だったの?

tRPCは基本的に型安全なAPI通信を実現するために、デフォルトでは標準のJSONシリアライズを使用します。
しかし、困ったことにJSONシリアライズでは日付やマップ、セットなどの特殊な型を扱うのが難しく、Date型などで正確なデータの受け渡しができない場合があるようです。

そこで、superjsonを導入することで、Date型も正しくシリアライズ/デシリアライズできるようになるということでした。
試しておりませんが、transformerをカスタマイズしても同様の効果が得られるようです。

GitHubで編集を提案

Discussion