Server Actions信者に告ぐ:そのコード、保守できますか?

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の即時性」を保証するためのもの。
3時間溶かして気づいたんだ。「`revalidatePath`だけ呼んで、なぜUIが更新されないんだ!」とブラウザの前で叫んでいたあの夜。Server Actionsはサーバー側の状態を更新するが、クライアントのReact Queryが持っているキャッシュまでは感知しない。

この二つを併用するのは、「サーバーの真実」と「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は、ユーザーの体験を最大化するための「キャッシュと同期のエンジン」として使う。
「全部Server Actionsでいいじゃん」というのは、ただの思考停止した筋トレだ。賢いエンジニアは、ツールに依存するのではなく、ツールの強みをパズルのように組み合わせて最強のUXを構築する。

もし君がまだ全部のローディングを`useState`で管理しているなら、今すぐそのコードを捨ててTanStack Queryを導入しろ。世界が変わるぞ。