ミドルウェア
middleware.ts:config.matcherを使い、各リクエストの前にリダイレクト、リライト、ヘッダー、Cookieを処理します。
ミドルウェアはリクエストが完了する前にコードを実行します。リダイレクト、リライト、ヘッダーやCookieの設定、またはレスポンスの直接返却ができます。
規約
プロジェクトのルート(または src/)で、app/ の隣に middleware.ts を作成します。middleware 関数(またはデフォルトエクスポート)と、必要に応じて config をエクスポートします。
import { NextResponse, type NextRequest } from "@textjs/app/server";export function middleware(request: NextRequest) { if (!request.cookies.has("session")) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next();}export const config = { matcher: ["/dashboard/:path*", "/api/:path*"],};ミドルウェアは、text dev、text start、すべてのサーバー用デプロイアダプターで、ルートハンドラー、静的ファイル、コンソールページより前に実行されます。Webの Request と Response を使うため、同じファイルがNode、Bun、Vercel Edge、Cloudflare Workersで動作します。
パスのマッチング
config.matcher がない場合、ミドルウェアはすべてのリクエストで実行されます。マッチャーはNext.jsと同じ構文を使います。
| マッチャー | 一致するパス |
|---|---|
/about | /about と /about/ |
/blog/:slug | /blog/hello |
/docs/:path* | /docs、/docs/a/b |
/a/:rest+ | /a/b(/a は対象外) |
/user/:id(\\d+) | /user/42(/user/ada は対象外) |
/((?!api/).*) | /api/* 以外のすべて |
has と missing で条件(ヘッダー、Cookie、クエリー、ホスト)を追加します。
export const config = { matcher: [{ source: "/beta/:path*", has: [{ type: "cookie", key: "beta", value: "1" }] }],};マッチャーは basePath を除いたパス名に適用されます。text dev では、Vite自体のモジュールリクエスト(/@vite/*、ソースファイル)でミドルウェアが実行されることはありません。
すべてのホストは、ミドルウェアの実行前に正規化されたパス名を1つ決定し、マッチング、request.nextUrl、ルート、ハンドラー、ファイルの検索に使います。パーセントエスケープをデコードし、new URL() の出力形式でパスを再エンコードして、. と .. のセグメントを解決し、重複するスラッシュと末尾のスラッシュを削除します。/ad%6din//users/ は /admin/users としてミドルウェアに渡され、/café は /caf%C3%A9 のままです。/admin/ のように末尾にスラッシュを付けたマッチャーは、/admin に一致します。パスにエンコードされたスラッシュやバックスラッシュ(%2F、%5C)、制御文字、無効なパーセントエンコーディングが含まれるリクエストには 400 を返します。ルートは大文字と小文字を区別します。/Admin は /admin に一致せず、静的ファイルは名前が完全に一致する場合にのみ配信されます。
ファイル名とあわせて、ファイルが配信されるURLにも一致させてください。Cloudflare Pagesは report.html を /report でも配信するため、/report.html 用のマッチャーでは /report を保護できません。matcher: ["/report", "/report.html"] のように両方を列挙するか、保護するファイルをディレクトリーにまとめ、public/reports/ 内のファイルに対する /reports/:path* のように、そのディレクトリーに一致させます。
NextResponse
| API | 効果 |
|---|---|
NextResponse.next() | ルートへ処理を継続 |
NextResponse.redirect(url, status?) | デフォルトは 307。url は絶対URLが必須 |
NextResponse.rewrite(url) | 別のパスを配信。外部オリジンはプロキシ経由 |
NextResponse.json(body, init?) | JSONで応答 |
new Response(...) | 任意の内容で応答 |
何も返さなければ、変更せずに処理を継続します。next() と rewrite() は { status } を受け取り、最終レスポンスのステータスを変更できます。ルーティングを制御するのは NextResponse.next() / NextResponse.rewrite() で作成されたレスポンスのみです。fetch() が返すものを含め、それ以外の Response は x-middleware-* ヘッダーを削除したうえで、そのままクライアントに送信されます。
request.nextUrl の pathname は basePath を除外しますが、href と toString() はこれを含みます。アプリ内にとどまるには、クローンを作成します。const url = request.nextUrl.clone(); url.pathname = "/login"; return NextResponse.redirect(url);。
export function middleware(request: NextRequest) { if (request.nextUrl.pathname === "/status") { return NextResponse.rewrite(new URL("/api/health", request.url)); }}ヘッダー
後続のハンドラーが受け取るヘッダーを変更するには request.headers を、レスポンスヘッダーを追加するには headers を渡します。
export function middleware(request: NextRequest) { const headers = new Headers(request.headers); headers.set("x-user", "ada"); const response = NextResponse.next({ request: { headers } }); response.headers.set("x-frame", "▼"); return response;}ルートハンドラーが同じヘッダーを設定しない限り、ミドルウェアのレスポンスヘッダーは最終レスポンスに追加されます。
Cookie
request.cookies は Cookie ヘッダーを読み取り、response.cookies は Set-Cookie を書き込みます。
export function middleware(request: NextRequest) { const theme = request.cookies.get("theme")?.value ?? "geistDark"; const response = NextResponse.next(); response.cookies.set("theme", theme, { httpOnly: true, sameSite: "lax", maxAge: 60 * 60 * 24 }); return response;}next() または rewrite() のレスポンスに設定されたCookieは、同じリクエストのルートハンドラーからも参照できます。
バックグラウンド処理
第2引数には waitUntil() があり、レスポンス送信後もサーバーレスランタイムの実行を維持します。
import type { NextFetchEvent, NextRequest } from "@textjs/app/server";export function middleware(request: NextRequest, event: NextFetchEvent) { event.waitUntil(fetch("https://analytics.example.com", { method: "POST", body: request.url }));}注意事項
- クライアントレンダリングのページはブラウザーで描画されるため、別のページへのリライトは配信するファイルを変更しますが、コンソールアプリが描画するルートは変更しません。ルートハンドラー、静的ファイル、外部オリジンへのリライトは期待どおりに動作します。
- ミドルウェアはリクエストボディを読み取れますが、ルートハンドラーも引き続きそのボディを受け取れます。マッチャーが除外したリクエストのボディには一切アクセスしません。
request.urlのオリジンは常にサーバーのオリジンです。//other.host/xのようなリクエストターゲットは、先頭のスラッシュが1つになるよう正規化されます。- 静的アダプターではミドルウェアを実行できません。