ミドルウェア

middleware.ts:config.matcherを使い、各リクエストの前にリダイレクト、リライト、ヘッダー、Cookieを処理します。

ミドルウェアはリクエストが完了する前にコードを実行します。リダイレクト、リライト、ヘッダーやCookieの設定、またはレスポンスの直接返却ができます。

規約

プロジェクトのルート(または src/)で、app/ の隣に middleware.ts を作成します。middleware 関数(またはデフォルトエクスポート)と、必要に応じて config をエクスポートします。

middleware.tsts
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、クエリー、ホスト)を追加します。

middleware.tsts
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);。

middleware.tsts
export function middleware(request: NextRequest) {  if (request.nextUrl.pathname === "/status") {    return NextResponse.rewrite(new URL("/api/health", request.url));  }}

ヘッダー

後続のハンドラーが受け取るヘッダーを変更するには request.headers を、レスポンスヘッダーを追加するには headers を渡します。

middleware.tsts
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 を書き込みます。

middleware.tsts
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() があり、レスポンス送信後もサーバーレスランタイムの実行を維持します。

middleware.tsts
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つになるよう正規化されます。
  • 静的アダプターではミドルウェアを実行できません。
GitHubでこのページを編集