middlewareの再入門(matcher、条件分岐、rewrite、redirect)
1. はじめに
- Next.js の middleware で処理対象となるパスの指定方法(
matcher
と条件分岐)について解説します。 - NextResponse の
rewrite
とredirect
の違いについて解説します。
2. 結論
- middleware とは、リクエストが完了する前にコードを実行できる仕組みです。
-
middleware.tsは、
app
あるいはpages
ディレクトリと同じ階層に配置します。 - matcher とは、middleware を適用するための URL のパターンを正規表現で指定します。
- 条件分岐 は、middleware を適用するための URL のパターンを if 構文などで指定します。
- rewrite は、リクエストを別の URL に書き換えます。ユーザーには書き換えた URL が見えません。
- redirect は、リクエストを別の URL にリダイレクトします。ユーザーにはリダイレクト先の URL が見えます。
3. middlewareとは❓
middleware を利用すると、リクエストが完了する前にコードを実行できます。
4. middlewareの配置位置
app
あるいは pages
ディレクトリと同じ階層に、middleware.ts
(あるいは middleware.js
)を配置します。
.
└── src
└── app
└── middleware.ts
.
└── src
└── pages
└── middleware.ts
5. middlewareが実行されるパス
middleware が実行される対象パスは以下の2つで指定します。
-
matcher
の利用 - 条件分岐
matcher
を利用
5.1. matcher
を利用することで、実行対象となる特定 URL パスを正規表現で指定できます。
正規表現は文字列のパターンマッチングに用いられる強力なツールです。正規表現を用いることで、特定の文字列のパターンを検索したり、置換したり、抽出したりできます。
5.1.1. 特定のURLパターンをマッチさせる
export const config = {
matcher: '/about/:path*',
}
この例では、特定の URL パスをマッチさせるためのパターンを示しています。ここでのパターンは、/about/
に続く任意のパスを表しています。
要素 | 説明 |
---|---|
/about/ |
文字列がこのパターンで始まる必要があります。 |
:path |
コロン(:)はプレースホルダーを示しており、ここでは任意の文字列を受け取ることができます。 |
* |
アスタリスク(*)は、プレースホルダーに対応する部分が0回以上繰り返されることを示します。 |
5.1.2. 複数のURLパターンをマッチさせる
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
このコードは、配列を用いて 2 つのパスのパターンを定義しています。それぞれのパターンは、特定のルートにマッチするためのテンプレートを表しています。正規表現は先程と内容が同じのため説明を省略します。
5.2. 条件分岐
matcher
を利用しない場合は、URL のパスを利用し、条件に応じて処理を分岐させます。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
6. NextResponse
middleware での処理の結果を返すために、NextResponse
を利用します。
NextResponse
で以下を実施できます。
-
redirect
は、リクエストを別の URL にリダイレクトします。相手側にはリダイレクト先の URL が見えます。 -
rewrite
は、リクエストを別の URL に書き換えます。相手側にはリダイレクト先の URL は見えません。 - ヘッダーを設定します。
- リクエストとレスポンスのクッキーを設定します。
7. 実装して確認
実装して確認して行きます。
7.1. Next.jsプロジェクトの新規作成し環境構築
作業するプロジェクトを新規に作成していきます。
長いので、折り畳んでおきます。
新規プロジェクト作成と初期環境構築の手順詳細
$ pnpm create next-app@latest nextjs-middleware-sample --typescript --eslint --import-alias "@/*" --src-dir --use-pnpm --tailwind --app
$ cd nextjs-middleware-sample
以下の通り不要な設定を削除し、プロジェクトの初期環境を構築します。
$ mkdir src/styles
$ mv src/app/globals.css src/styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
export default function Home() {
return (
<main className="text-lg">
テストページ
</main>
)
}
import '@/styles/globals.css'
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ja">
<body className="">{children}</body>
</html>
);
}
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
plugins: [],
};
export default config;
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
+ "baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
コミットします。
$ pnpm build
$ git add .
$ git commit -m "新規にプロジェクトを作成し, 作業環境を構築"
7.2. middlewareでmatcherを利用
/about/:path*
にアクセスした場合、/home
へリダイレクトする middleware を作成します。
$ touch src/middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
console.log(request.nextUrl.href);
return NextResponse.redirect(new URL("/", request.url));
}
// See "Matching Paths" below to learn more
export const config = {
matcher: "/about/:path*",
};
ページを作成します。
$ mkdir -p src/app/about/section/subsection
$ touch src/app/about/page.tsx \
src/app/about/section/page.tsx \
src/app/about/section/subsection/page.tsx
$ mkdir -p src/app/dashboard/user/settings
$ touch src/app/dashboard/page.tsx \
src/app/dashboard/user/page.tsx \
src/app/dashboard/user/settings/page.tsx
実装部分は長いので折り畳んで起きます。
ページ作成
export default function Home() {
return (
<main className="text-lg">
/about
</main>
)
}
export default function Home() {
return (
<main className="text-lg">
/about/section
</main>
)
}
export default function Home() {
return (
<main className="text-lg">
/about/section/subsection
</main>
)
}
export default function Home() {
return (
<main className="text-lg">
/dashboard
</main>
)
}
export default function Home() {
return (
<main className="text-lg">
/dashboard/user
</main>
)
}
export default function Home() {
return (
<main className="text-lg">
/dashboard/user/settings
</main>
)
}
開発サーバーを起動します。
$ pnpm dev
以下の URL にアクセスすると、http://localhost:3000 にリダイレクトされます。ブラウザに表示される URL はリダイレクト先の http://localhost:3000 となっています。
- http://localhost:3000/about
- http://localhost:3000/about/section
- http://localhost:3000/about/section/subsection
以下の URL にアクセスすると、リダイレクトは発生しません。
- http://localhost:3000/dashboard
- http://localhost:3000/dashboard/user
- http://localhost:3000/dashboard/user/settings
サーバログは以下の通り書き出されています。
http://localhost:3000/about
http://localhost:3000/about/section
http://localhost:3000/about/section/subsection
コミットします。
$ pnpm build
$ git add .
$ git commit -m "matcherの利用を確認"
7.3. middlewareで条件分岐を利用
1 つ前の middleware はバックアップしておき、新規に middleware を作成します。
-
rewrite
を利用し、http://localhost:3000/about にアクセスした場合、URL は変更せず、http://localhost:3000/about/section を表示します。 -
rewrite
を利用し、http://localhost:3000/dashboard にアクセスした場合、URL は変更せず、http://localhost:3000/dashboard/user を表示します。
$ mv src/middleware.ts src/middleware-1.ts
$ touch src/middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about/section', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
開発サーバーを起動します。
$ pnpm dev
http://localhost:3000/about にアクセスすると、URL は変更されず、http://localhost:3000/about/section が表示されます。
http://localhost:3000/dashboard にアクセスすると、URL は変更されず、http://localhost:3000/dashboard/user が表示されます。
それ以外のパスは、リダイレクトされません。
コミットします。
$ pnpm build
$ git add .
$ git commit -m "条件分岐とrewriteの利用を確認"
8. まとめ
-
Next.js の middleware で処理対象となるパスの指定方法(
matcher
と条件分岐)について解説しました。 -
NextResponse の
rewrite
とredirect
の違いについて解説しました。 -
作業コードは以下です。
https://github.com/hayato94087/nextjs-middleware-sample
Discussion