safe-action-tanstack-query について
safe-action-tanstack-query は、next-safe-action の Server Actions を TanStack Query のミューテーションに接続するアダプターです。TanStack Query を導入済みの Next.js プロジェクト向けで、結果内のサーバーエラーや検証エラーを型付きエラーに変換します。
主な機能
- 01TanStack Query のミューテーションオプション生成
- 02サーバー・検証エラーを ActionMutationError に変換
- 03エラー型判定ヘルパー
- 04リトライ、キャッシュ無効化、オフラインミューテーション対応
活用シーン
- 01TanStack Query プロジェクトで Server Actions を実行
- 02ミューテーション後にクライアントクエリキャッシュを無効化
- 03失敗したミューテーションのリトライ設定
- 04オフライン中に保留したミューテーションの永続化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ next-safe-action/ skills --skill safe-action-tanstack-queryインストール後、Claude Code で/safe-action-tanstack-queryを入力して読み込まれたか確認します。
同梱ファイルmutation-patterns.md
詳細
next-safe-action TanStack Query アダプター
インストール
npm install @next-safe-action/adapter-tanstack-query @tanstack/react-query
インポート
import {
mutationOptions,
ActionMutationError,
isActionMutationError,
hasServerError,
hasValidationErrors,
} from "@next-safe-action/adapter-tanstack-query";
クイックスタート
"use client";
import { useMutation } from "@tanstack/react-query";
import { mutationOptions } from "@next-safe-action/adapter-tanstack-query";
import { createUser } from "@/app/actions";
export function CreateUserForm() {
const { mutate, isPending, isError, error, data } = useMutation(
mutationOptions(createUser, {
onSuccess: (data) => {
toast.success(`Created ${data.name}`);
},
})
);
return (
<form onSubmit={(e) => {
e.preventDefault();
const fd = new FormData(e.currentTarget);
mutate({ name: fd.get("name") as string, email: fd.get("email") as string });
}}>
<input name="name" required />
<input name="email" type="email" required />
<button type="submit" disabled={isPending}>
{isPending ? "Creating..." : "Create User"}
</button>
{isError && <p className="text-red-500">{error.message}</p>}
{data && <p>Created: {data.name}</p>}
</form>
);
}
仕組み
mutationOptions() は、next-safe-action の結果エンベロープを TanStack Query のエラーモデルに橋渡しする、完全な UseMutationOptions オブジェクトを作成します。
mutate()/mutateAsync()に渡された入力で安全なアクションを呼び出します- 結果エンベロープを確認し、
serverErrorまたはvalidationErrorsが含まれているか調べます - どちらかが存在する場合は
ActionMutationErrorをスローします(クライアント側のエラークラスであり、instanceofで判定できます) - 成功時は、
dataを TanStack Query のTDataとして直接返します - React のレンダーフェーズ中に必ず再スローされるよう
throwOnErrorを組み合わせて、ナビゲーションエラー(redirect()、notFound()など)を処理します
returnServerError() が生成する想定内のエラーは、他のサーバーエラーと同様に serverError に入るため、型付きペイロードを持つ ActionMutationError として通知されます。throwServerError とは異なり、mutationOptions() と完全に互換性があります。
どちらを使うべきか
| シナリオ | 推奨 |
|---|---|
| TanStack Query を使わない新しい Next.js プロジェクト | 組み込みフック |
| シンプルなフォーム送信やボタンアクション | 組み込みフック |
React の useOptimistic による即時の楽観的 UI | 組み込みフック(useOptimisticAction) |
| キューに入れて蓄積する必要がある、重複する書き込み(並べ替え、カンバン) | 組み込みフック(useOptimisticStateAction) |
| ユーザーとは独立して変化するデータ(ソケット、ポーリング、バックグラウンド更新) | 読み取りには TanStack Query / SWR、ミューテーションにはアダプター |
| 依存関係を一切追加したくない場合 | 組み込みフック |
| データ取得にすでに TanStack Query を使用している場合 | アダプター |
| tRPC と TanStack Query をすでに使用している場合 | アダプター |
| バックオフ付きの自動リトライが必要な場合 | アダプター |
| ミューテーション後にクライアントのクエリキャッシュを無効化する必要がある場合 | アダプター |
| ミューテーションに TanStack Query DevTools を使いたい場合 | アダプター |
| オフラインでのミューテーション永続化が必要な場合 | アダプター |
機能の比較
| 機能 | 組み込みフック | アダプター |
|---|---|---|
| React トランジション | はい。アクションは startTransition 内で実行されます | いいえ |
| 楽観的更新 | React の useOptimistic を介した useOptimisticAction | onMutate とクエリキャッシュを使って手動で行います |
| 自動リトライ | いいえ | はい。バックオフ付きの retry オプション |
| サーバーキャッシュの無効化 | はい。revalidatePath() / revalidateTag() | はい。同じ Next.js API |
| クライアントクエリキャッシュの無効化 | いいえ(該当しません) | はい。queryClient.invalidateQueries() |
| DevTools | いいえ | はい。TanStack Query DevTools |
| エラーモデル | 結果エンベロープ(result.serverError、result.validationErrors) | 型ガード付きの ActionMutationError をスロー |
| オフラインでのミューテーション永続化 | いいえ | はい。dehydrate/hydrate による一時停止中のミューテーション |
| 非同期実行 | executeAsync() は Promise<Result> を返します | mutateAsync() は Promise<Data> を返します |
| ステータスの追跡 | status 文字列と簡略形のブール値 | ブール値フラグ(isPending、isError、isSuccess) |
| 追加の依存関係 | なし(React のみ) | @tanstack/react-query |
一般的な指針: ほとんどの Next.js アプリでは、組み込みフックを優先してください。追加の依存関係が不要で、React の並行レンダリングと統合されています。TanStack Query がすでに技術スタックに含まれている場合、特にキャッシュの無効化、リトライ、DevTools、オフラインサポートが必要な場合は、アダプターを優先してください。
ミューテーションのみの理由
このアダプターが提供するのは mutationOptions() のみです。設計上、queryOptions() はありません。
- Server Actions は
POSTのみを使うため、GETベースのクエリには適していません - Server Actions はクライアントごとにキューに入るため、リクエストのウォーターフォールが発生します
POSTリクエストはブラウザーキャッシュ、ETag、条件付きリクエストを回避します- TanStack Query の重複排除に使える、安定したリソース識別子がありません
データ取得には、サーバー側では Server Components、クライアント側では Route Handlers + useQuery、またはフルスタックの型安全性が必要な場合は tRPC を使用してください。
エントリーポイント
| エントリーポイント | エクスポート | 環境 |
|---|---|---|
@next-safe-action/adapter-tanstack-query | mutationOptions、ActionMutationError、isActionMutationError、hasServerError、hasValidationErrors、型 | クライアント |
重要な制約
ステートレスな .action() 関数でのみ動作します。 mutationOptions() は SingleInputActionFn を受け取るため、.stateAction() をラップすることはできません。ステートフルなアクションには、next-safe-action/hooks の useStateAction / useOptimisticStateAction を使用してください。
例外をスローしないアクションでのみ動作します。 mutationOptions() に渡すアクションでは、throwValidationErrors: true や throwServerError: true を使用しないでください。このアダプターは、エラーを検出するために結果エンベロープを調べます。エラーが返されずにスローされた場合、アダプターは構造化されたエラーデータを抽出できず、型安全なエラー処理ができなくなります。TypeScript は NonThrowingActionConstraint によってこれを強制します。
関連ドキュメント
アンチパターン
// BAD: Using throwValidationErrors with adapter — errors bypass the result envelope
const client = createSafeActionClient({ throwValidationErrors: true });
const action = client.inputSchema(schema).action(async ({ parsedInput }) => { ... });
mutationOptions(action); // TypeScript error! NonThrowingActionConstraint not met
// BAD: Using mutationOptions for data fetching — server actions are POST-only
const { data } = useQuery(mutationOptions(fetchUsers)); // Wrong! Use Route Handler + useQuery
// BAD: Manually calling the action inside mutationFn
useMutation({
mutationFn: async (input) => {
const result = await myAction(input); // Loses error bridging, navigation handling
return result.data;
},
});
// GOOD: Let mutationOptions handle the bridging
useMutation(mutationOptions(myAction));