[Nextjs] trpc環境でのSSRデータ再検証(Revalidation)について
今回は、Next.jsでServer-Side Rendering(SSR)したデータを更新する方法について、具体的な実装例を交えながら解説していきます。
はじめに
Next.jsでWebアプリケーションを開発していると、こんな課題に直面したことはありませんか?
- ユーザー一覧ページでSSRを使ってデータを表示しているけど、新規ユーザーを追加した後にリストが更新されない
- データを更新したのに、画面に反映されるまでにページの再読み込みが必要
- クライアントでデータを更新した後、SSRで取得したデータを自動的に更新したい
これらの課題は、Next.jsのrevalidatePath
やrevalidateTag
を使うことで効率的に解決できます。
実装例:ユーザー管理システム
具体的な例として、あるプロジェクトでのユーザー管理システムの実装を見ていきましょう。
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.js
の Server Action
は、従来のクライアントサイドでのデータ更新とは異なるアプローチを提供します。サーバー側で直接データを処理できるこの機能には、以下のような重要なメリットがあります:
1. セキュリティの強化 🔒
- 機密情報の保護: データベースの認証情報や API キーをクライアントに公開することなく、安全に管理できます
- データ操作の制御: 更新ロジックをサーバー側に隠蔽することで、不正なデータ操作を防ぎます
- 認証・認可の一元管理: セキュリティチェックをサーバーサイドで完結できます
2. パフォーマンスの最適化 ⚡
-
バンドルサイズの削減:
- データ処理ロジックをサーバーに置くことで、クライアントサイドのJavaScriptを軽量化
- 初期ロード時間の短縮につながります
-
通信の効率化:
- 従来の API リクエスト/レスポンスのオーバーヘッドを削減
- サーバーとの往復回数を最小限に抑えられます
3. 開発効率の向上 💡
-
コードの簡素化:
- API エンドポイントの作成が不要
- データ処理ロジックを一箇所にまとめられる
- 型安全性の向上(TypeScriptとの相性が良い)
-
保守性の向上:
- サーバー/クライアント間のインターフェース管理が不要
- ビジネスロジックの見通しが良くなる
- デバッグが容易になる
revalidatePathの仕組み
revalidatePath
が呼ばれると:
- 指定されたパスのキャッシュが無効化
- 次回のアクセス時に新しいデータを取得
- React Suspenseと組み合わせることで、更新中も UI の応答性を維持
実装のポイント
-
Server Actionsの活用
'use server'
このディレクティブをファイルの先頭に追加することで、そのファイル内の全ての関数がServer Actionsとして扱われます。
-
適切なパスの指定
revalidatePath('./account')
相対パスを使用して、更新が必要なページを正確に指定します。
-
エラーハンドリング
try { await createUser(data) await revalidateUserList() } catch (error) { // エラー処理 }
データ更新と再検証の両方で適切なエラーハンドリングを実装。
データ更新から再検証までのフロー
上記の実装が実際にどのように動作するのか、シーケンス図で確認してみましょう。これによりServer ComponentsとServer Actionsがどのように連携してデータの更新と再検証を行うかが明確になります。
このフローから分かるように、Server Actionを使った場合:
- ユーザーがデータを更新すると、そのリクエストは直接サーバーに送られる
- サーバー側でデータベース更新と再検証(revalidatePath)が連続して実行される
- ユーザーが該当ページにアクセスした際、キャッシュではなく最新のデータが取得される
この流れにより、従来のクライアントサイドでの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
を再検証すると:
-
/account
セグメント配下のすべてのルートが影響を受ける - 動的ルート(
/account/edit/[id]
)も含めて再検証される - 共有されているデータ(ユーザー情報など)が一貫して更新される
これは、Next.jsが効率的なキャッシュ無効化のために、ルートセグメントベースでキャッシュを管理しているためです。
まとめ
Next.jsのrevalidationの仕組みを使う際は:
- Server Actionsを使用することで、より確実なキャッシュ制御が可能
- ルートセグメントの構造を理解することで、効率的な再検証が実現できる
- データの一貫性を保ちながら、パフォーマンスを最適化できる
Server ComponentsとServer Actionsを適切に組み合わせることで、より堅牢で保守性の高いアプリケーションを構築できます。
Discussion