Next.jsのルートハンドラーとサーバーアクションの使い分けを徹底比較
はじめに
Next.jsにはRoute Handlers(ルートハンドラー)とServer Actions(サーバーアクション) という仕組みがあります。
この記事では、それぞれの特徴を比較して解説します。以下ではカタカナ表記を使います。
↓ルートハンドラーとサーバーアクションに関しては、過去に記事を書いています。
・ルートハンドラーに関する記事
・サーバーアクションに関する記事
この記事は他の記事や動画を調べてまとめ、最後に少し僕の意見を交えただけの記事です。
なので使い時に関しては絶対ではありません。
参考程度にお読みください。ルートハンドラーとサーバーアクションを選定する際の一助になれば幸いです。
この記事の対象者
・ルートハンドラーとサーバーアクションの使い分けを知りたい人
・ルートハンドラーとサーバーアクションの違いをざっくり知りたい人
ルートハンドラーとサーバーアクションの違い
ルートハンドラーとサーバーアクションの違いを、構文例で確認します。
ルートハンドラーのコード例
// src\app\api\post\route.ts
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const { message } = await req.json();
console.log("受信メッセージ:", message);
return NextResponse.json({ success: true, message });
}
"use client";
// src\app\routeHandler\page.tsx
import React, { useState } from "react";
const Page = () => {
const [message, setMessage] = useState<string>("");
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
await fetch("/api/post/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message }),
});
}
return (
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="message">メッセージ</label>
<input
type="text"
name="message"
onChange={(e) => setMessage(e.target.value)}
/>
<button type="submit">送信</button>
</form>
</div>
);
};
export default Page;
サーバーアクションのコード例
"use server";
// src\app\actions\action.ts
export async function sendMessage(formData: FormData) {
const message = formData.get("message");
console.log("受信メッセージ:", message);
}
// src\app\sendMessage\page.tsx
import React from "react";
import { sendMessage } from "../actions/action";
const page = () => {
return (
<div>
<form action={sendMessage}>
<label htmlFor="message">メッセージ</label>
<input type="text" name="message" />
<button type="submit">送信</button>
</form>
</div>
);
};
export default page;
ルートハンドラーでは、fetch()を使ってHTTPベースで通信を行います。
そのため、外部サービスから直接エンドポイントを叩くことも可能です。
一方、サーバーアクションは関数を呼び出すだけ処理が完結します。
余分な HTTP 通信やエンドポイント定義が不要なため、コードをすっきりと書けるのが特徴です。
使い分け
調べてみた自分の結論は以下の表のようになりました。
他の方の動画や記事、AIを使って調べてまとめたので、こちらが必ず正解というわけではありません。参考程度に見ていただければと思います。
| 利用ケース | ルートハンドラー | サーバーアクション |
|---|---|---|
| ストリーミング | ○ | × |
| Webhook | ○ | × |
| C/U/D 操作 | ○ | ○ |
| フォームの送信 | △ | ○ |
| カスタムヘッダー | ○ | △ |
| CORS | ○ | × |
ストリーミング
ストリーミングに関してですが、調べてみると海外のYoutuberさんは、よくAI Streamingとして紹介していました。
AI Streamingをまとめてみると以下のような感じです。
・LLM(例: OpenAI, Anthropic など)のAPIは トークン単位で部分的なレスポンスを返せる
・これをストリーミングで受け取ることで、ユーザーは 回答が完成する前に読み始められる
・ChatGPTのように文字が一文字ずつ出てくる挙動はこれ
ルートハンドラーでは下記のようにオプションを設定してストリーミングに対応することができますが、サーバーアクションではまだ対応していないそうです。
したがって、ストリーミングの機能を使う際は、ルートハンドラーに軍配が上がります。
return new Response(stream, { headers: { "Content-Type": "text/event-stream" } });
Webhook
Webhookとは、アプリケーションやサービスで特定のイベントが発生した際に、その情報をリアルタイムで別のアプリケーションに通知する仕組みです。
例えば、GitHubのpushイベントを感知して、slackに通知を送る機能などがあたります。
Webhookの仕組みとしては、発信元(GitHubなど)がイベントを発生させると、自動で指定されたURLにHTTPリクエストを送ります。
つまり、受け取り側には 公開されたエンドポイント(API URL) が必要になりますので、外部からアクセスすることのできるルートハンドラーに軍配が上がります。
C/U/D操作
CUD とはCRUDのうちCreate/Update/Deleteを指します。
サーバーアクションは、Next.jsが「関数呼び出し」を裏でPOSTリクエストにシリアライズして送る仕組みのため、基本的にPOST 相当で動作します。
そのため、新規作成・更新・削除・フォーム送信などのデータ変更系処理に適しています。
サーバーアクションはミューテーションを得意としており、ルートハンドラーより簡潔に書けるため、CUDに関しては、サーバーアクションを推奨しています。
一方でサーバーアクションは、GET(データ取得)には向いていません。
なぜサーバーアクションでGETが非推奨なのか
・通信モデルに合わない
サーバーアクションは必ずPOSTで送られるため、データ取得をわざわざPOSTで行うのはHTTPセマンティクスに反します。
・キャッシュが効かない
Next.jsのfetch()なら自動的にキャッシュ・再検証(revalidate)が効きますが、サーバーアクションは毎回POSTになるためキャッシュが使えず、パフォーマンス低下につながります。
フォームの送信
フォームの送信は大概がミューテーションの操作で、先ほど説明したCUDに該当するケースが多いと思います。
また、先ほど記載したルートハンドラーとサーバーアクションの構文の比較をみるとわかると思いますが、サーバーアクションの方がaction属性に関数をセットするだけで、かなりスッキリと楽に書けると思います。
カスタムヘッダーやCORS
ヘッダーを細かく指定したいなら、ルートハンドラーが適していると思います。なぜなら、ルートハンドラーはHTTPレイヤーにアクセスすることができ、以下のように細かくヘッダーを設定することができるからです。
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json(
{ message: "Hello" },
{ headers: { "X-Custom-Header": "my-value" } }
);
}
また、CORSに関しても、以下のようにヘッダー設定することで許可することができます。
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json(
{ message: "ok" },
{
headers: {
"Access-Control-Allow-Origin": "*", // 必要に応じてドメインを制限
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
},
}
);
}
サーバーアクションはfetchをせずそのままDBを操作したりするので、基本的に上記のようなヘッダーの設定などはできないと思われます。
結論の整理
結論を一文でまとめると
「UIからのデータ操作はサーバーアクション、HTTPエンドポイントが必要ならルートハンドラー」
最後に
本記事に目を通していただきありがとうございました。
今回はルートハンドラーとサーバーアクションの使い分けをメインに扱いました。
自分で実装した経験から書いた記事ではないので、間違っている箇所あればご指摘いただきたいです。
参考
・https://www.youtube.com/watch?v=NWx8oVLEdwE&t=17s
・https://www.youtube.com/watch?v=4pmPaAFB6ik&t=81s
・https://zenn.dev/electnoob/articles/f02e12e4066fd4
・https://form.run/media/contents/business-efficiency/about-webhook/
Discussion