11.

Next.js の Route Handlers|route.ts で API 実装

編集

この記事の要点

  • route.ts を置くと、そのパスが API エンドポイントになる。中身は Web 標準の RequestResponse
  • 扱えるメソッドは GET POST PUT PATCH DELETE HEAD OPTIONS。それ以外は 405 が返る。
  • 同じ階層に page.tsxroute.ts置けない。API は別パスに切る。
  • Route Handlers は既定でキャッシュされない。GET だけは設定でキャッシュに乗せられる。
  • リクエスト全体に共通処理を挟みたい場合は proxy.ts。バージョン 16 で middleware から改名された。

画面の中でデータを更新するだけなら Server Functions で足ります。しかし、外部サービスからの Webhook を受ける、モバイルアプリに JSON を返す、CSV をダウンロードさせる、といった用途では独立したエンドポイントが必要です。それを担うのが Route Handlers です。

1もっとも小さな API

app の中に route.ts を置き、HTTP メソッド名の関数をエクスポートします。

// app/api/hello/route.ts
export async function GET(request: Request) {
  return Response.json({ message: 'hello' })
}

これで /api/hello に GET すると JSON が返ります。特別なフレームワーク独自のオブジェクトはなく、引数も戻り値もブラウザでおなじみの Web 標準 API です。

ファイルの置き場所のルールは page.tsx と同じで、フォルダ階層がそのまま URL になります。ただし同じ階層に page.tsxroute.ts を同居させることはできません。1 つのパスに対する扱いは、どちらか一方が全 HTTP メソッドを引き受ける形になるためです。

ページルート結果
app/page.tsxapp/route.ts競合してビルドできない
app/page.tsxapp/api/route.ts問題なし
app/[user]/page.tsxapp/api/route.ts問題なし

2メソッドごとに書き分ける

1 つのファイルに複数のメソッドを並べられます。定義していないメソッドで呼ばれた場合は 405 Method Not Allowed が自動的に返ります。

// app/api/posts/route.ts
export async function GET() {
  const posts = await db.posts.findMany()
  return Response.json(posts)
}

export async function POST(request: Request) {
  const body = await request.json()
  const created = await db.posts.create(body)
  return Response.json(created, { status: 201 })
}

Next.js は標準の RequestResponse を拡張した NextRequest / NextResponse も提供しています。Cookie の読み書きなど、少し込み入った処理を書くときに便利です。

3URL に含まれる値を受け取る

動的セグメントを使う場合、値は第 2 引数のコンテキストから取り出します。ここも Promise なので await が必要です。

// app/users/[id]/route.ts
import type { NextRequest } from 'next/server'

export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
  const { id } = await ctx.params
  return Response.json({ id })
}

RouteContext はグローバルに使える型ヘルパーで、next devnext build の際に自動生成されます。インポートは不要です。

4キャッシュの扱い

Route Handlers は既定ではキャッシュされませんGET だけは、設定を書けば静的な応答として扱えます。

// app/items/route.ts
export const dynamic = 'force-static'

export async function GET() {
  const res = await fetch('https://api.example.com/items')
  const data = await res.json()
  return Response.json({ data })
}

同じファイルに書いた他のメソッドはキャッシュされません。副作用を伴う POST などがキャッシュされないのは当然の設計です。

Cache Components を有効にしている場合、GET の扱いは通常のページと同じモデルになります。リクエスト依存の値に触れなければ事前生成され、触れた時点でリクエスト時実行に切り替わります。

Cache Components 有効時に GET が事前生成されるか 事前生成される 固定値を返すだけ/'use cache' を付けたヘルパー経由でデータを取る リクエスト時に実行される headers() / cookies() / connection() を呼ぶ、request の中身を読む 同上 DB 問い合わせ・ネットワーク通信・Math.random() などの非決定的な処理
「リクエストに依存するか」「毎回同じ結果か」で振り分けられる。ページの判定基準とまったく同じ。

なお、'use cache' をハンドラ本体に直接書くことはできません。ヘルパー関数に切り出して、そちらに付けます。

// app/api/products/route.ts
import { cacheLife } from 'next/cache'

export async function GET() {
  const products = await getProducts()
  return Response.json(products)
}

async function getProducts() {
  'use cache'
  cacheLife('hours')
  return await db.query('SELECT * FROM products')
}

5Server Functions との使い分け

Server Functions を使う
自分のアプリの画面から呼ぶ更新処理。フォーム送信、ボタン操作。URL を公開する必要がない。
Route Handlers を使う
外部から呼ばれるもの。Webhook 受信、他システム連携、モバイルアプリ向け API、ファイルの生成と配信。

判断の軸は「呼び出し元が自分の画面かどうか」です。自分の画面からしか呼ばないのに API を作ると、URL とスキーマを維持する手間だけが増えます。

6特別な役割を持つファイル

sitemap.tsrobots.tsopengraph-image.tsxicon.tsx なども内部的には Route Handlers ですが、これらはリクエスト時の API や動的な設定を使わない限り静的に生成されます。詳しくは メタデータ API で SEO を整える で扱います。

7全リクエストに共通処理を挟む — proxy.ts

認証チェックやリダイレクトのように、ルートに入る前の段階で処理したいことがあります。従来 middleware.ts と呼ばれていたこの仕組みは、バージョン 16 で proxy.ts に改名されました。

// proxy.ts(プロジェクトのルートに置く)
export function proxy(request: Request) {
  // 認証チェック、リダイレクト、書き換えなど
}

関数名も proxy にします。設定フラグの名前も変わっており、たとえば skipMiddlewareUrlNormalizeskipProxyUrlNormalize になりました。改名は移行用のコードモッドで自動処理できます。

注意点として、proxy の実行環境は Node.js に固定されており、変更できません。Edge ランタイムを使い続ける必要がある場合は、当面 middleware のまま運用することになります。

8つまずきやすいところ

  • page.tsx と同じ場所に置いてビルドが落ちる/api 以下など、別のパスに切り出します。
  • 404 になる:ファイル名が route.ts かどうか、エクスポートした関数名が大文字のメソッド名かどうかを確認します。
  • 認証を忘れる:Route Handlers は公開エンドポイントです。ハンドラの冒頭で必ず権限を確認します。
  • キャッシュされていると思い込む:既定ではされません。負荷を減らしたいなら明示的に設定します。
  • 古い記事の pages/api:Pages Router 時代の API Routes です。App Router では Route Handlers を使い、併用する必要はありません。

バックエンド側の口が用意できたら、次は公開品質を整える工程に入ります。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 開発環境の構築とプロジェクトの初期構成
  2. App Router のルーティングとファイル規約
  3. レイアウトと特殊ファイルの使い分け
  4. サーバーコンポーネントとクライアントコンポーネント
  5. サーバー側でのデータ取得と並列化
  6. ストリーミングで体感速度を上げる
  7. キャッシュの仕組みと use cache
  8. 再検証の使い分け
  9. フォーム送信とデータ更新の実装
  10. エラーハンドリングとエラー境界
  11. Route Handlers で API を作る
  12. メタデータ API で SEO を整える
  13. 画像とフォントの最適化
  14. 本番ビルドとセルフホスト
  15. バージョン16 への移行と注意点