Next.js プログラミング

【tRPC、Next.js App Router】Hydration failedの解決

T3Appを使用してToDoアプリを作成していたところ、エラーが発生しましたので解決方法を紹介します。

技術スタック

T3Appを実行してそのままの環境で開発を行いました。使用している技術スタックは下記のとおりです。

技術スタックバージョン
prisma5.18.0
typescript5.5.4
trpc/server11.0.0-rc.482
zod3.23.8
react18.3.1
tanstack/react-query5.52.0

発生したエラー

プロジェクト作成直後のコンポーネントではエラーは発生していませんでした。

DB内のデータを取得、表示するためのコンポーネントを作成したところ、下記のエラーが発生しました。

エラーメッセージ

Error: Hydration failed because the initial UI does not match what was rendered on the server.

エラーメッセージを翻訳すると、

「エラー: 初期 UI がサーバー上でレンダリングされたものと一致しないため、ハイドレーションは失敗しました。

」となります。

新たに作成したコンポーネントと、エラーが発生しているコンポーネントのコードは次のようになっていました。

実際のコード

app/api/trpc/[trpc]/page.tsx
export default async function Home() {
  void api.todo.getAllTodo.prefetch();

  return (
    <HydrateClient>
      <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
        <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16">
          <AddTodo />
          <TodoList />
        </div>
      </main>
    </HydrateClient>
  );
}
api/app/_components/ToDoList.tsx
const TodoList = () => {
  const { data: todoList } = api.todo.getAllTodo.useQuery();

  return (
    <div className="w-full max-w-xs">
      <ul className="flex flex-col gap-2">
        {todoList?.map((todo) => (
          <li
            key={todo.id}
            className="flex items-center justify-between rounded-full bg-white/10 px-4 py-2"
          >
            <span>{todo.title}</span>
            <button className="rounded-full bg-white/10 px-4 py-2 font-semibold transition hover:bg-white/20">
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

DBのデータを表示するコンポーネントの準備が終わる前にレンダリングされるため、サーバー側との差異が発生しています。

解決方法

コンポーネントの準備ができたら画面に表示するように修正すれば解決するのですが、2種類の方法があります。

解決方法その2(サーバーコンポーネント側で対処)

プリフェッチをawaitで実行する

app/api/trpc/[trpc]/page.tsx
//  void await api.todo.getAllTodo.prefetch();
  try {
    await api.todo.getAllTodo.prefetch();
  } catch (error) {
    console.error(error);
  }

解決方法その2(クライアントコンポーネント側で対処)

useSuspenseQueryを使用する

api/app/_components/ToDoList.tsx
//  const { data: todo } = api.todo.getAllTodo.useQuery();
  const [todoList] = api.todo.getAllTodo.useSuspenseQuery();

クライアントコンポーネント側で対処したほうがエラーハンドリングがしやすく感じたので、私は後者を採用しました。

まとめ

Hydration failedのエラーメッセージが表示された場合は、非同期処理がうまくできているのか確認しましょう。

https://www.amazon.co.jp/%E5%AE%9F%E8%B7%B5Next-js-%E2%80%94%E2%80%94-App-Router%E3%81%A7%E9%80%B2%E5%8C%96%E3%81%99%E3%82%8BWeb%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA-%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E9%81%B8%E6%9B%B8/dp/4297140616?_encoding=UTF8&dib_tag=se&dib=eyJ2IjoiMSJ9.rcRJpNX9Bj_z78WeXONRLGPTAlV3HSQzoofkp8skATzgsId18fI2PZXPYbedvU6RjM3fWj2EIuuDvqCLMNmgtW-ricCb7c917K6mAVDuLtotPJ5RMvsFCk1b8q3qOU6hLHvZ-73QrFeTwjq9KZL3L2GBXT7DdU-PwmBUAL2Uot-hqY1s2tKVl_3GrZKKgLJ4wM7MrEETSmgSK9IxOjPUVQLd9mHYXV2pLpTEoHLcV_BQ-YgJHC6BzDp4bNiw91XoWXW6FAtuSaq1hgs_J-FOxmb9FB3ruNgZoVQpnJTuklU.bsAvDdoKFbmRiX9cS7ZeBqVpQKmgGYjF2bDppWV75zI&qid=1724554464&sr=8-7&linkCode=ll1&tag=yuu0e77-22&linkId=c67db98bac081185c8496b92960a5591&language=ja_JP&ref_=as_li_ss_tl

-Next.js, プログラミング
-, ,