😄

[Nextjs] trpc環境でのSSRデータ再検証(Revalidation)について

2025/02/23に公開

今回は、Next.jsでServer-Side Rendering(SSR)したデータを更新する方法について、具体的な実装例を交えながら解説していきます。

はじめに

Next.jsでWebアプリケーションを開発していると、こんな課題に直面したことはありませんか?

  • ユーザー一覧ページでSSRを使ってデータを表示しているけど、新規ユーザーを追加した後にリストが更新されない
  • データを更新したのに、画面に反映されるまでにページの再読み込みが必要
  • クライアントでデータを更新した後、SSRで取得したデータを自動的に更新したい

これらの課題は、Next.jsのrevalidatePathrevalidateTagを使うことで効率的に解決できます。

実装例:ユーザー管理システム

具体的な例として、あるプロジェクトでのユーザー管理システムの実装を見ていきましょう。

1. SSRでのデータ取得(Server Component)

// src/app/(authorized)/all-sites/settings/account/page.tsx
const AccountPage = async () => {
  const api = await serverApi()
  const users = await api.user.listByLevel()  // SSRでユーザーリストを取得

  return (
    <div className="container mx-auto">
      <UserManagement users={users} />
    </div>
  )
}

このページコンポーネントはServer Componentとして実装されています。Server Componentを使用する利点は:

  • データベースに直接アクセスできる
  • APIキーなどの機密情報を安全に扱える
  • 初期ロード時のパフォーマンスが向上
  • SEO対策に有効

2. データ更新時の再検証(Revalidation)

// src/app/actions.ts
'use server'

import { revalidatePath } from 'next/cache'

export async function revalidateUserList() {
  revalidatePath('./account')
}
// src/components/settings/account/UserRegistrationForm.tsx
const onSubmit = async (data: UserFormData) => {
  try {
    await createUser(data)
    await revalidateUserList()  // ユーザー作成後にリストを再検証
    
    toast({
      title: 'ユーザーを登録しました',
      status: 'success',
      duration: 3000,
      isClosable: true
    })
    
    router.push('../account')
  } catch (error) {
    // エラーハンドリング
  }
}

なぜServer Actionを使うのか?

Next.jsServer Action は、従来のクライアントサイドでのデータ更新とは異なるアプローチを提供します。サーバー側で直接データを処理できるこの機能には、以下のような重要なメリットがあります:

1. セキュリティの強化 🔒

  • 機密情報の保護: データベースの認証情報や API キーをクライアントに公開することなく、安全に管理できます
  • データ操作の制御: 更新ロジックをサーバー側に隠蔽することで、不正なデータ操作を防ぎます
  • 認証・認可の一元管理: セキュリティチェックをサーバーサイドで完結できます

2. パフォーマンスの最適化 ⚡

  • バンドルサイズの削減:

    • データ処理ロジックをサーバーに置くことで、クライアントサイドのJavaScriptを軽量化
    • 初期ロード時間の短縮につながります
  • 通信の効率化:

    • 従来の API リクエスト/レスポンスのオーバーヘッドを削減
    • サーバーとの往復回数を最小限に抑えられます

3. 開発効率の向上 💡

  • コードの簡素化:

    • API エンドポイントの作成が不要
    • データ処理ロジックを一箇所にまとめられる
    • 型安全性の向上(TypeScriptとの相性が良い)
  • 保守性の向上:

    • サーバー/クライアント間のインターフェース管理が不要
    • ビジネスロジックの見通しが良くなる
    • デバッグが容易になる

revalidatePathの仕組み

revalidatePathが呼ばれると:

  1. 指定されたパスのキャッシュが無効化
  2. 次回のアクセス時に新しいデータを取得
  3. React Suspenseと組み合わせることで、更新中も UI の応答性を維持

実装のポイント

  1. Server Actionsの活用

    'use server'
    

    このディレクティブをファイルの先頭に追加することで、そのファイル内の全ての関数がServer Actionsとして扱われます。

  2. 適切なパスの指定

    revalidatePath('./account')
    

    相対パスを使用して、更新が必要なページを正確に指定します。

  3. エラーハンドリング

    try {
      await createUser(data)
      await revalidateUserList()
    } catch (error) {
      // エラー処理
    }
    

    データ更新と再検証の両方で適切なエラーハンドリングを実装。

データ更新から再検証までのフロー

上記の実装が実際にどのように動作するのか、シーケンス図で確認してみましょう。これによりServer ComponentsとServer Actionsがどのように連携してデータの更新と再検証を行うかが明確になります。

このフローから分かるように、Server Actionを使った場合:

  1. ユーザーがデータを更新すると、そのリクエストは直接サーバーに送られる
  2. サーバー側でデータベース更新と再検証(revalidatePath)が連続して実行される
  3. ユーザーが該当ページにアクセスした際、キャッシュではなく最新のデータが取得される

この流れにより、従来のクライアントサイドでのAPI呼び出しよりもスムーズで確実なデータ更新が実現できます。また、サーバーサイドで処理が完結するため、セキュリティも向上します。

実装時の注意点:tRPCルーターとServer Actionsの違い

実際の開発で詰まった部分を紹介します。

tRPCルーターでの再検証の限界

当初、tRPCルーター内でrevalidatePathを実行していましたが、以下のような問題が発生しました:

// src/server/router/user.ts内でrevalidateを実行
const userRouter = router({
  update: protectedProcedure
    .input(/* ... */)
    .mutation(async ({ input }) => {
      // データ更新処理
      revalidatePath('./account') // ここでの実行は完全な再検証につながらない
    })
})

この実装では、ユーザー一覧ページは更新されても、編集ページ(/account/edit/[id])のデータが更新されないという問題がありました。

Server Actionsを使用した確実な再検証

// src/app/actions.ts
'use server'

export async function revalidateUserList() {
  revalidatePath('./account') // Server Actionsからの実行で確実に再検証
}

Server ActionsからrevalidatePathを実行することで、Next.jsのキャッシュを確実に更新できます。これは、Server ActionsがNext.jsのランタイムと直接統合されているためです。

なぜ './account' の再検証で編集ページも更新されるのか?

Next.jsのルートセグメントキャッシュの仕組みにより、./accountを再検証すると:

  1. /accountセグメント配下のすべてのルートが影響を受ける
  2. 動的ルート(/account/edit/[id])も含めて再検証される
  3. 共有されているデータ(ユーザー情報など)が一貫して更新される

これは、Next.jsが効率的なキャッシュ無効化のために、ルートセグメントベースでキャッシュを管理しているためです。

まとめ

Next.jsのrevalidationの仕組みを使う際は:

  • Server Actionsを使用することで、より確実なキャッシュ制御が可能
  • ルートセグメントの構造を理解することで、効率的な再検証が実現できる
  • データの一貫性を保ちながら、パフォーマンスを最適化できる

Server ComponentsとServer Actionsを適切に組み合わせることで、より堅牢で保守性の高いアプリケーションを構築できます。

参考リンク

codeciaoテックブログ

Discussion