App Routerへの移行で「データが.更新されない」と絶望した話:Server Actionsのキャッシュ制御にハマった記録
深夜2時。目の前のブラウザには、さっきまで確かに存在していたはずの「完了済みタスク」が、なぜかまだ「未完了」として表示され続けている。
新しいプロジェクトの基盤をNext.js 14のApp Routerで作り直す際、僕は確信していた。「Server Actionsを使えば、API Routeをわざわざ書く手間も省けるし、もっとシンプルに書けるはずだ」と。確かに、フォームから直接サーバーサイドの関数を呼び出す体験は魔法のようにスムーズだった。しかし、その「魔法」のせいで、画面上のデータが更新されないという、一見すると初歩的ながら、App Router特有のキャッシュ機構に阻まれた壁にぶつかることになった。
事の発端は、単純なタスクのステータス更新機能だった。DB(Prisma)の値を書き換えるServer Actionを作成し、それを`use client`で作成したボタンから呼び出す。コード自体は非常にシンプルで、エラーも出ない。DBの中身を確認すると、確かに値は書き換わっている。なのに、ブラウザをリロードしない限り、画面上の表示は古いままなのだ。
最初は「Client ComponentのState管理が間違っているのか?」と疑い、`useState`や`useEffect`を弄り回した。しかし、根本的な原因はクライアント側ではなく、Next.jsの強力すぎるキャッシュ(Data Cache)側にあった。App RouterにおけるServer Componentsは、一度レンダリングされると、その結果がサーバー側にキャッシュされることがある。Server ActionsでDBを更新しても、Next.ックのサーバー側にある「古いレンダリング結果」がそのまま使い回されてしまうのだ。
結局、解決策は「再検証(Revalidation)」を明示的に行うことだった。`revalidatePath`を使うことで、特定のパスのキャッシュを破棄し、最新のデータを取得し直すよう指示する必要があったのだ。
以下に、僕が失敗したコードと、修正後のコードをまとめる。
// ❌ 失敗したパターン:DBは更新されるが、画面が変わらない
'use server'
import { db } from '@/lib/db'
export async function updateTaskStatus(formData: FormData) {
const taskId = formData.get('id') as string;
const newStatus = formData.get('status') as string;
// DBの更新は成功しているが、これだけでは画面の表示は古いまま
await db.task.update({
where: { id: taskId },
data: { status: newStatus },
});
}
// ✅ 修正後のパターン:revalidatePathでキャッシュを明示的に更新する
'use server'
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'
export async function updateTaskStatus(formData: FormData) {
const taskId = formData.get('id') as string;
const newStatus = formData.get('status') as string;
await db.task.update({
where: { id: taskId },
data: { status: newStatus },
});
// タスク一覧のページ(/tasks)のキャッシュを無効化し、再レンダリングを促す
revalidatePath('/tasks');
}
この`revalidatePath`を追加した瞬間、ボタンを押すと同時に画面がパッと切り替わった時の感動は、今でも忘れられない。
また、開発中にTurbopackを有効にしていた際にも、少しばかりの混乱があった。Next.jsの起動速度は劇的に向上して非常に快適なのだが、時折、古いモジュールの変更が反映されないような挙動(キャッシュの影響と思われるもの)に遭遇した。App Routerの学習コストは、単なる「書き方の違い」だけでなく、この背後にある強力なキャッシュ戦略やレンダリング・メカニズムを理解することにあるのだと痛感させられた。
Pages Router時代のような「リクエストが来たら常に動的に処理する」という感覚で挑むと、今回のような「データは変わっているのに画面が変わらない」という迷宮に迷い込むことになる。Server Actionsを使う際は、常に「この更新によって、どのパスのキャッシュを破棄すべきか?」をセットで考える癖をつけなければならない。
App Routerへの移行は、単なる技術のアップデートではなく、Webアプリケーションにおけるデータの鮮度とパフォーマンスのトレードオフを再設計する作業なのだと思う。