Server Actions信者に告ぐ:そのコード、保守できますか?
「Next.js 14/15最高!データ取得も更新も全部Server Actionsで完結するじゃん!」
そう息巻いている君、一度冷静になれ。その「楽」は、数ヶ月後の自分を地獄に突き落とすための布石だ。小規模なプロトタイプならそれでいい。だが、少しでもインタラクティブなUI、例えば「楽観的更新(Optimistic Update)」が求められる中規模アプリでそれをやると、コードはあっという間にスパゲッティ化し、UXはガタガタになる。
いいか、「全部Server Actions」はただの思考停止だ。
現代のNext.js開発における最適解は、「更新はServer Actions、取得はTanStack Query」。この境界線を引くことこそが、フロントエンドの戦場を生き抜くための唯一の生存戦略だ。
1. 悲劇:Server Actionsだけで状態を管理するということ
俺もかつては同じ過ちを犯した。タスク管理アプリを作り、「全部Server Actionsでいいや」と実装した結果がこれだ。
// 悪夢の始まり:Stateの管理地獄
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<Task[]>([]);
const handleSubmit = async (formData: FormData) => {
setIsLoading(true);
setError(null);
try {
const result = await addTaskAction(formData);
setData(prev => [...prev, result]); // 手動での状態同期
} catch (e) {
setError('失敗しました');
} finally {
setIsLoading(false);
}
};
ボタンが10個あれば、このボイラープレートを10回書くのか? 正気じゃない。ローディング、エラーハンドリング、キャッシュの再検証……これらをコンポーネント内に散りばめるのは、もはやプログラミングではなく「苦行」だ。
TanStack Queryを使えば、これら全てが一行で片付く。
// TanStack Queryならこう書く
const { mutate, isPending } = useMutation({
mutationFn: addTaskAction,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['tasks'] })
});
`isPending`をチェックするだけ。状態管理のロジックはコンポーネントから消え去り、UIは驚くほどクリーンになる。
2. なぜ「二重管理」が許容されるのか?
ここで鋭い奴ならこう突っ込むはずだ。「`revalidatePath`と`invalidateQueries`、両方呼ぶのは冗長じゃないか?」と。
結論から言うと、この冗長性は「意図的な分離」だ。
- `revalidatePath`: Next.jsのキャッシュ(Server Component)を無効化する。これは「データそのものの鮮度」を保証するためのもの。
- `invalidateQueries`: クライアントサイドのキャッシュを無効化する。これは「UIの即時性」を保証するためのもの。
この二つを併用するのは、「サーバーの真実」と「UIの心地よさ」の双方が必要だからだ。 泥臭いようだが、これが現時点での最も堅牢な設計だ。
3. 破綻しないディレクトリ構成の鉄則
迷いを消すために、俺は常に以下の構成を強制している。
src/
├── app/
│ └── dashboard/
│ └── page.tsx // Server Component: 初回ロードのみ担当
├── features/
│ └── tasks/
│ ├── actions.ts // Server Actions: 副作用(CUD)専用
│ ├── api.ts // TanStack Query: 取得(R)とキャッシュ管理
│ └── types.ts
取得系(`api.ts`)で`fetch`をラップし、更新系(`actions.ts`)でDBを叩く。この分離を徹底すれば、コンポーネントは「UIの描画」という本来の責務に集中できる。
4. なぜこの設計がNext.js 15でも揺るがないのか
Next.js 15でServer Actionsがどう進化しようと、この「役割分担」が覆ることはない。なぜなら、「サーバーの再検証」と「クライアントのステート管理」は、抽象化のレイヤーが全く異なるからだ。
Reactの哲学は「宣言的UI」だ。データ取得の状態を`useState`で泥臭く管理する時代はもう終わった。TanStack Queryは、サーバーの不確実性からUIを保護するための防波堤だ。
結論:ツールを使い倒せ
- Server Actionsは、サーバー側の副作用を安全に実行するための「トリガー」として使う。
- TanStack Queryは、ユーザーの体験を最大化するための「キャッシュと同期のエンジン」として使う。
もし君がまだ全部のローディングを`useState`で管理しているなら、今すぐそのコードを捨ててTanStack Queryを導入しろ。世界が変わるぞ。