デプロイ後の「Failed to find Server Action」の直し方|Next.js(2026)
Next.jsで「Failed to find Server Action ... older or newer deployment」が出る原因を、古いタブ、ローリングデプロイ、ビルド・暗号化キーの不一致に切り分けます。診断表、deploymentIdとNEXT_SERVER_ACTIONS_ENCRYPTION_KEYの設定、確認手順付き。
目次
デプロイ後にNext.jsで「Failed to find Server Action」が出るのはなぜか?どう直すか?
結論
ブラウザが送ったServer ActionのIDを、リクエストを受けたサーバーインスタンスが知らない、というのが原因です。Next.jsはIDをモジュールに対応づけられないときにこのエラーを投げ、メッセージ自体にも「older or newer deployment(古い、または新しいデプロイ由来のリクエストかもしれない)」と書かれています。原因は大きく3つで、直し方が違います。
- 古いクライアント。 デプロイ前に開いたタブが、旧ビルドのアクションIDを呼んでいる。対処:バージョンずれの検知と、利用者向けの再読み込み導線。
- 新旧が混在した構成。 ローリングデプロイやロードバランサーの背後で、あるリクエストは旧インスタンスへ、次のリクエストは新インスタンスへ届く。対処:
deploymentIdの設定と、デプロイ単位のルーティング。 - インスタンスごとに別ビルド。 各サーバーが個別に
next buildしており、アクションIDも暗号化キーも違う。対処:ビルドは1回だけ行い同じ成果物を配布し、NEXT_SERVER_ACTIONS_ENCRYPTION_KEYを固定する。
Next.js 15.1.9では、メッセージは次の形です(node_modules/next/dist/server/app-render/action-handler.jsで確認)。
Failed to find Server Action "<actionId>". This request might be from an older or newer deployment. Original error: <reason>
末尾のOriginal errorが手がかりです。Couldn't find action module ID from module mapなら、そのIDがこのビルドに存在しないだけなので、原因1か2です。
ビルドごとにアクションIDが変わる理由
Server Actionは、暗号化された非決定的なIDで参照されます。公式のデータセキュリティガイドによると、IDはコンパイル時に作られ、最大14日間キャッシュされ、新しいビルドの開始やビルドキャッシュの無効化で再生成されます。また、アクションごとの秘密鍵もビルドのたびに新しく生成されるため、「アクションは特定のビルドに対してしか呼べない」とされています。
つまりアクションIDは1つのビルドに固有の値です。そのIDを運ぶリクエストは、同じビルドを動かしているインスタンスに届く必要があります。
どの原因かを切り分ける
設定を変える前に、次の観察を確認します。
| 観察 | 有力な原因 | 最初の対処 |
| --------------------------------------------------------- | ------------------------------------ | -------------------------------------------------------- |
| デプロイ直後の数分だけエラーが増え、その後おさまる | 古いタブとローリングデプロイの重なり | deploymentId、再読み込み導線、旧インスタンスの退避 |
| エラーが止まらず、一部のリクエストだけ失敗する | インスタンスごとにビルドが違う | 1回ビルドして同じ成果物を全台に配布 |
| オートスケールで増えたインスタンスでだけ起きる | 起動時にイメージを再ビルドしている | .nextをイメージに焼き込み、起動時にビルドしない |
| クロージャで値を捕捉するアクションだけ失敗する | 暗号化キーの不一致 | NEXT_SERVER_ACTIONS_ENCRYPTION_KEYを固定 |
| CDNやリバースプロキシ経由でのみ失敗し、オリジン直通は成功 | キャッシュされたHTMLが旧ビルド参照 | デプロイをまたいでHTMLをキャッシュしない、公開時にパージ |
| ホスティング変更の直後から全アクションが失敗する | 環境ごとにビルドが違う | .next/BUILD_IDとマニフェストを比較 |
原因を分けるには、2つの計測が有効です。まず、全インスタンスのビルドを比較します。
# 各インスタンスで実行(またはオーケストレーターのexec経由)
cat .next/BUILD_ID
sha256sum .next/server/server-reference-manifest.json
BUILD_IDやマニフェストのハッシュがインスタンス間で違えば原因3です。一致しているのにデプロイ時だけエラーが出るなら、原因1と2です。
次に、deploymentIdを設定したあとは、レスポンスHTMLのデプロイ識別子を見ます。<html>要素にdata-dpl-idが付くので、ロードバランサー経由でどのデプロイが応答したか分かります。
curl -s https://example.com/ | grep -o 'data-dpl-id="[^"]*"'
ロールアウト中に何度か繰り返します。値が複数出るなら、トラフィックが複数のデプロイに分かれています。
対処1:デプロイIDを設定する
deploymentIdはnext.config.jsのトップレベルオプションです(v14.1.4で安定化)。ビルド時に環境変数NEXT_DEPLOYMENT_IDでも指定でき、両方あるときは設定ファイルの値が優先されます。
// next.config.js
module.exports = {
deploymentId: process.env.GIT_SHA,
};
設定すると、静的アセットのURLに?dpl=<id>が付き、クライアント遷移でx-deployment-idヘッダーが送られ、レスポンスにはx-nextjs-deployment-idが返ります。クライアントが不一致を検知すると、クライアント側遷移ではなく通常のページ読み込みに切り替わり、新しいアセットとアクションを取得できます。
注意点が2つあります。Next.jsは受信リクエストの?dpl=を読まないため、これだけではルーティングされません。そして、デプロイごとに一意のIDを付けても、ホスティング側やCDNが同じデプロイのインスタンスへ振り分けなければ、ずれは防げません。その場合は不一致が隠れるのではなく、再読み込みになるだけです。
対処2:全インスタンスで同じビルドと同じキーを使う
複数インスタンスをセルフホストするなら、CIで1回ビルドし、その出力をそのまま配布します。各コンテナの起動時にnext buildを走らせてはいけません。
単一の成果物を保証できない場合や、ビルドをまたいでアクションを生かしたい場合は、暗号化キーを固定します。データセキュリティガイドでは、NEXT_SERVER_ACTIONS_ENCRYPTION_KEYでキーをビルドをまたいで永続化し、全サーバーで共通にできると説明されています。値はbase64で、デコード後が16、24、32バイトのいずれかである必要があります(Next.jsの既定は32バイト)。
openssl rand -base64 32
値はシークレット管理に保存し、ビルド時と実行時の両方に同じ値を渡します。15.1.9のencryption-utils-server.jsでは、この環境変数があれば新規生成せずそれをキーとして返し、実行時の読み込みもこの値を優先してマニフェストのキーにフォールバックします。
キーを固定しても、アクションIDが安定するわけではありません。固定されるのは、クロージャで捕捉した値の復号がインスタンス間で通ることです。未知のアクションIDは未知のままなので、対処1と同じビルドの配布も必要です。
対処3:利用者が復帰できるようにする
ロールアウトが完璧でも、古いタブを開いたままの利用者は残ります。このエラーは起こりうる状態として扱います。
- フォームやクライアントコンポーネントで失敗を捕捉し、「新しいバージョンがあります。再読み込みしてください」と再読み込みボタンを表示する。
- 新バージョンが正常になった瞬間に旧インスタンスを止めず、トラフィックが引くまで残す。
- アクションを冪等にして、再読み込み後の再送で二重実行しないようにする。
- ビルド固有のアクションIDを含むHTMLは、リリース期間を超えてキャッシュしない。
確認手順
設定を変えたら、思い込みではなく結果を確かめます。
- Server Actionを持つプロジェクトのスクラッチのチェックアウトで
npm run buildを2回実行し、.next/BUILD_IDと.next/server/server-reference-manifest.json内のアクションIDを比べる。違いが出るはずで、古いタブが実際にリスクだという裏づけになる(Server Actionが無いプロジェクトではマニフェストは同一のままなので、ハッシュは指標にならない)。 deploymentIdを設定してステージングへデプロイする。タブを開き、もう一度デプロイしてから、古いタブでフォームを送信する。未処理の500ではなく、再読み込みまたは用意したメッセージが出ればよい。- 本番の全インスタンスで
.next/BUILD_IDを比較する。 - 次のリリース中に
Failed to find Server Actionのログを見て、短いデプロイ期間内かゼロに収まることを確認する。
この問題ではないケース
デプロイが絡まない単一のローカル環境で出るなら、あるターミナルでnext dev、別のターミナルでnext startを異なる.next出力に対して動かしていないか、ホットリロード後の古いページが開いたままでないかを確認してください。オリジンやホストの拒否も同時に出る場合は別の仕組みです。Server ActionsはOriginとHost(またはX-Forwarded-Host)を比較しており、serverActions.allowedOriginsで制御します。下のセキュリティチェックリストを参照してください。
参考資料
- How to think about data security in Next.js(Next.js Docs)
- deploymentId(Next.js Docs)
- Self-hosting:バージョンずれと暗号化キー(Next.js Docs)
- エラー文言とキー処理は、インストール済み
next@15.1.9のソース(app-render/action-handler.js、app-render/encryption-utils-server.js)で確認
内部リンク
- 親ハブ:Next.jsセキュリティ
- 関連記事:
免責
一般的な技術情報です。挙動、既定値、オプション名はNext.jsのバージョンやホスティング環境で変わる場合があります。利用中のバージョンで確認し、本番で頼る前にステージングでロールアウトを検証してください。