こんにちは、かつコーチです。
前回はServer Componentでのデータフェッチの基本を解説しました。
Server Component内で完結するデータ取得だけでなく、外部サービスから叩かれるAPIエンドポイント自体をNext.jsで作りたい場面もあります。
今回は、App Routerでそのようなエンドポイントを作る仕組みであるRoute Handlersを解説します。
Route Handlersとは?
App RouterにおけるAPIエンドポイントの仕組み
Route Handlersとは、App Router上でroute.tsというファイルを使ってAPIエンドポイントを定義する仕組みです。
appディレクトリ内の任意のフォルダにroute.tsを置くと、そのパスがAPIのエンドポイントになります。
Server Actionsがフォーム送信やデータ更新など「Reactコンポーネントから呼ぶ」用途に向くのに対し、Route Handlersは外部のWebhookやモバイルアプリなど「他のシステムから呼ばれる」用途に向いています。
GET/POSTなどHTTPメソッドごとに関数を定義する
Route Handlersでは、HTTPメソッド名(GET、POST、PUT、DELETEなど)と同じ名前の関数をexportすることで、そのメソッドのリクエストを処理します。
Web標準のRequest・Responseオブジェクトをベースにしているため、Expressなど他のフレームワークの経験があれば馴染みやすい形です。
基本の書き方
手順1:GETリクエストを処理するエンドポイントを作る
// app/api/posts/route.ts
import { NextResponse } from "next/server";
interface Post {
id: number;
title: string;
}
const posts: Post[] = [
{ id: 1, title: "はじめてのNext.js" },
{ id: 2, title: "Route Handlersの基本" },
];
export async function GET() {
return NextResponse.json(posts);
}
app/api/posts/route.tsに配置すると、/api/postsというGETエンドポイントが作られます。
NextResponse.json()は、JSONレスポンスを簡単に返すためのNext.js独自のヘルパーです。
手順2:POSTリクエストでリクエストボディを受け取る
// app/api/posts/route.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function POST(request: NextRequest) {
const body = await request.json();
if (typeof body.title !== "string" || body.title.length === 0) {
return NextResponse.json(
{ error: "titleは必須です" },
{ status: 400 }
);
}
const newPost = { id: Date.now(), title: body.title };
return NextResponse.json(newPost, { status: 201 });
}
request.json()でリクエストボディをパースし、バリデーションに失敗したらstatus: 400でエラーを返しています。
同じroute.tsファイルにGETとPOSTを両方定義することも可能です。
手順3:動的パラメータを受け取る
URLの一部を可変にしたい場合は、フォルダ名を[id]のように角括弧で囲みます。
// app/api/posts/[id]/route.ts
import { NextResponse } from "next/server";
interface Params {
params: Promise<{ id: string }>;
}
export async function GET(request: Request, { params }: Params) {
const { id } = await params;
const post = { id: Number(id), title: `投稿${id}` };
if (!post) {
return NextResponse.json({ error: "見つかりません" }, { status: 404 });
}
return NextResponse.json(post);
}
第2引数のparamsから、URLに含まれる動的な値を取得できます。
Next.jsの近年のバージョンではparamsがPromiseとして渡されるため、awaitで展開する点に注意が必要です。
手順4:クエリパラメータを扱う
// app/api/search/route.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const keyword = searchParams.get("q") ?? "";
return NextResponse.json({ keyword, results: [] });
}
request.nextUrl.searchParamsを使うと、?q=nextjsのようなクエリパラメータを取得できます。
NextRequestは標準のRequestを拡張したNext.js独自のクラスで、nextUrlのような便利なプロパティが追加されています。
つまずきポイント:Server ActionsとRoute Handlersの使い分けで迷った話
実際に起きたこと
私が最初にRoute Handlersを学んだとき、「フォームの送信もAPI経由でPOSTすればいいのでは」と考え、フォームの送信処理をすべてRoute Handlers経由で書いていました。
// app/api/contact/route.ts
export async function POST(request: Request) {
const body = await request.json();
// お問い合わせをDBに保存する処理
return NextResponse.json({ success: true });
}
// フォーム側(Client Component)
'use client';
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
await fetch("/api/contact", {
method: "POST",
body: JSON.stringify({ message: "..." }),
});
}
動くには動くのですが、Client Componentにfetchのロジックを書く必要があり、コード量が増えてしまいました。
原因と気づき
調べてみると、Reactコンポーネントの内側からデータを更新するだけであれば、以前の記事で扱ったServer Actionsのほうがシンプルに書けることに気づきました。
Route Handlersが向いているのは「LINEやStripeなどの外部サービスからWebhookを受け取る」「モバイルアプリなどNext.js以外のクライアントからアクセスされる」といった、React外部との接点が必要な場面です。
一方でReactコンポーネント内のフォーム送信のような用途は、Server Actionsを使ったほうがfetchを自前で書く必要がなく、型安全性も保ちやすいです。
私はこの線引きを知らずにすべてRoute Handlers経由にしてしまい、後から一部をServer Actionsに書き直すことになりました。
判断基準のまとめ
| 用途 | 推奨する仕組み |
|---|---|
| Reactコンポーネントからのフォーム送信・データ更新 | Server Actions |
| 外部サービスからのWebhook受信 | Route Handlers |
| モバイルアプリなど非React クライアント向けAPI | Route Handlers |
| RESTライクな汎用APIとして公開したい | Route Handlers |
「呼び出し元がReactコンポーネントの内側か外側か」を基準に考えると、迷いにくくなります。
まとめ
この記事のポイント
- Route Handlersは
appディレクトリ内にroute.tsを置くことで、App Router上にAPIエンドポイントを作る仕組み GET・POSTなどHTTPメソッド名の関数をexportして、メソッドごとの処理を定義する[id]のような動的フォルダ名やクエリパラメータの取得もServer Component同様の書き方でできる- ReactコンポーネントからのフォームやデータはServer Actions、外部システムとの連携はRoute Handlersと使い分けるのが基本
次に読むべき記事
Route Handlersでデータを更新した後、画面側のキャッシュが古いままになる問題に出会うことがあります。
次回は、revalidatePathを呼んだのに画面が更新されなかった話と、その対処法を解説します。
→ 次の記事:revalidatePathを呼んだのに画面が更新されなかった話と対処法