リダイレクト
Next.jsでリダイレクトを処理する方法はいくつかあります。このページでは、利用可能な各オプション、ユースケース、および大量のリダイレクトを管理する方法について説明します。
API | 目的 | 使用場所 | ステータスコード |
---|---|---|---|
redirect | ミューテーションまたはイベント後にユーザーをリダイレクトする | Server Components、Server Actions、Route Handlers | 307(仮)または303(Server Action) |
permanentRedirect | ミューテーションまたはイベント後にユーザーをリダイレクトする | Server Components、Server Actions、Route Handlers | 308(永続) |
useRouter | クライアントサイドのナビゲーションを実行する | Client Componentsのイベントハンドラーで | 該当なし |
next.config.js 内のredirects | パスに基づいて受信リクエストをリダイレクトする | next.config.js ファイル | 307(仮)または308(永続) |
NextResponse.redirect | 条件に基づいて受信リクエストをリダイレクトする | ミドルウェア | 任意 |
redirect
関数
redirect
関数を使用すると、ユーザーを別のURLにリダイレクトすることができます。redirect
をServer Components、Route Handlers、およびServer Actionsで呼び出すことができます。
redirect
は、ミューテーションまたはイベントの後によく使用されます。たとえば、投稿を作成する場合です:
- TypeScript
- JavaScript
'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function createPost(id: string) {
try {
// データベースを呼び出す
} catch (error) {
// エラーを処理する
}
revalidatePath('/posts') // キャッシュされた投稿を更新する
redirect(`/post/${id}`) // 新しい投稿ページに移動する
}
'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function createPost(id) {
try {
// データベースを呼び出す
} catch (error) {
// エラーを処理する
}
revalidatePath('/posts') // キャッシュされた投稿を更新する
redirect(`/post/${id}`) // 新しい投稿ページに移動する
}
参考情報:
redirect
はデフォルトで307(Temporary Redirect)ステータスコードを返します。Server Actionで使用されるとき、通常はPOSTリクエストの結果として成功ページにリダイレクトするために使用される303(See Other)を返します。redirect
は内部でエラーをスローするため、try/catch
ブロックの外で呼び出す必要があります。redirect
はクライアントコンポーネントのレンダリングプロセス中に呼び出すことができますが、イベントハンドラーでは呼び出せません。useRouter
フックを代わりに使用できます。redirect
は絶対URLも受け入れ、外部リンクにリダイレクトするために使用できます。- レンダープロセスの前にリダイレクトしたい場合は、
next.config.js
またはMiddlewareを使用してください。
redirect
APIリファレンスを参照して、さらに情報をご覧ください。
permanentRedirect
関数
permanentRedirect
関数は、ユーザーを別のURLに永久にリダイレクトすることができます。permanentRedirect
をServer Components、Route Handlers、およびServer Actionsで呼び出すことができます。
permanentRedirect
は、エンティティの正規URLが変更されるミューテーションまたはイベントの後によく使用されます。たとえば、ユーザーがユーザー名を変更した後にプロフィールURLを更新する場合です:
- TypeScript
- JavaScript
'use server'
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function updateUsername(username: string, formData: FormData) {
try {
// データベースを呼び出す
} catch (error) {
// エラーを処理する
}
revalidateTag('username') // ユーザー名のすべての参照を更新する
permanentRedirect(`/profile/${username}`) // 新しいユーザープロフィールに移動する
}
'use server'
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function updateUsername(username, formData) {
try {
// データベースを呼び出す
} catch (error) {
// エラーを処理する
}
revalidateTag('username') // ユーザー名のすべての参照を更新する
permanentRedirect(`/profile/${username}`) // 新しいユーザープロフィールに移動する
}
参考情報:
permanentRedirect
はデフォルトで308(恒久的リダイレクト)ステータスコードを返します;permanentRedirect
は絶対URLも受け入れ、外部リンクにリダイレクトするために使用できます;- レンダープロセスの前にリダイレクトしたい場合は、
next.config.js
またはMiddlewareを使用してください;
permanentRedirect
APIリファレンスを参照して、さらに情報をご覧ください。
useRouter()
フック
クライアントコンポーネントのイベントハンドラー内でリダイレクトが必要な場合、useRouter
フックのpush
メソッドを使用できます。例えば:
- TypeScript
- JavaScript
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
参考情報:
- プログラムでユーザーをナビゲートする必要がない場合は、
<Link>
コンポーネントを使用するべきです。
useRouter
APIリファレンスを参照して、さらに情報をご覧ください。
next.config.js
内のredirects
next.config.js
ファイル内のredirects
オプションを使用すると、受信リクエストのパスを別の宛先パスにリダイレクトできます。これは、ページのURL構造を変更した場合や、事前に既知のリダイレクトのリストがある場合に便利です。
redirects
は、パス、ヘッダー、cookie、クエリのマッチングをサポートし、受信リクエストに基づいてユーザーをリダイレクトする柔軟性を提供します。
redirects
を使用するには、next.config.js
ファイルにオプションを追加します:
module.exports = {
async redirects() {
return [
// 基本的なリダイレクト
{
source: '/about',
destination: '/',
permanent: true,
},
// ワイルドカードパスマッチング
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
]
},
}
redirects
APIリファレンスを参照して、さらに情報をご覧ください。
参考情報:
redirects
は、permanent
オプションを使用して307(Temporary Redirect)または308(Permanent Redirect)ステータスコードを返すことができます;- プラットフォームによっては、
redirects
に制限がある場合があります。たとえば、Vercelでは1,024件のリダイレクトの制限があります。大量のリダイレクト(1000件以上)を管理するには、Middlewareを使用したカスタムソリューションを作成することを検討してください。規模に応じたリダイレクト管理については、managing redirects at scaleを参照してください;redirects
はミドルウェアの前に実行されます;
NextResponse.redirect
in Middleware
ミドルウェアを使用すると、リクエストが完了する前にコードを実行できます。そして、受信リクエストに基づいてNextResponse.redirect
を使用して別のURLにリダイレクトします。これは、条件(例:認証、セッション管理など)に基づいてユーザーをリダイレクトする場合や、大量のリダイレクトがあります:
たとえば、ユーザーが認証されていない場合に/login
ページにリダイレクトするには:
- TypeScript
- JavaScript
import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'
export function middleware(request: NextRequest) {
const isAuthenticated = authenticate(request)
// ユーザーが認証されている場合は、通常通り続行します
if (isAuthenticated) {
return NextResponse.next()
}
// 認証されていない場合は、ログインページにリダイレクトします
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
import { NextResponse } from 'next/server'
import { authenticate } from 'auth-provider'
export function middleware(request) {
const isAuthenticated = authenticate(request)
// ユーザーが認証されている場合は、通常通り続行します
if (isAuthenticated) {
return NextResponse.next()
}
// 認証されていない場合は、ログインページにリダイレクトします
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
参考情報:
- ミドルウェアは、
next.config.js
のredirects
後に実行され、レンダリングの前に実行されます;
Middlewareのドキュメントを参照して、さらに情報をご覧ください。
規模に応じたリダイレクト管理(高度)
大量のリダイレクト(1000件以上)を管理するために、ミドルウェアを使用してカスタムソリューションを作成することを検討してください。これにより、アプリケーションを再デプロイすることなく、プログラムでリダイレクトを処理できます。
これを行うには、以下を考慮する必要があります:
- リダイレクトマップの作成と保存
- データ検索パフォーマンスの最適化
Next.jsの例:以下の推奨事項の実装については、Middleware with Bloom filterの例を参照してください。
1. リダイレクトマップの作成と保存
リダイレクトマップは、データベース(通常はキー・バリュー・ストア)またはJSONファイルに保存できるリダイレクトのリストです。
次のデータ構造を考慮してください:
{
"/old": {
"destination": "/new",
"permanent": true
},
"/blog/post-old": {
"destination": "/blog/post-new",
"permanent": true
}
}
Middlewareで、VercelのEdge ConfigやRedisなどのデータベースから読み取り、受信リクエストに基づいてユーザーをリダイレクトすることができます:
- TypeScript
- JavaScript
import { NextResponse, NextRequest } from 'next/server'
import { get } from '@vercel/edge-config'
type RedirectEntry = {
destination: string
permanent: boolean
}
export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname
const redirectData = await get(pathname)
if (redirectData && typeof redirectData === 'string') {
const redirectEntry: RedirectEntry = JSON.parse(redirectData)
const statusCode = redirectEntry.permanent ? 308 : 307
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
// リダイレクトが見つからなかった場合、そのまま継続します
return NextResponse.next()
}
import { NextResponse } from 'next/server'
import { get } from '@vercel/edge-config'
export async function middleware(request) {
const pathname = request.nextUrl.pathname
const redirectData = await get(pathname)
if (redirectData) {
const redirectEntry = JSON.parse(redirectData)
const statusCode = redirectEntry.permanent ? 308 : 307
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
// リダイレクトが見つからなかった場合、そのまま継続します
return NextResponse.next()
}
2. データ検索パフォーマンスの最適化
大規模データセットをすべての受信リクエストのために読み取ることは遅く、費用がかかります。データ検索パフォーマンスを最適化する方法は2つあります:
- Vercel Edge ConfigやRedisなどの、高速読み取りに最適化されたデータベースを使用します;
- ブルームフィルターのようなデータ検索戦略を使用して、リダイレクトが存在するかどうかを効率的にチェックし、より大きなリダイレクトファイルやデータベースを読み取る前に行います;
前述の例では、生成されたブルームフィルターファイルをミドルウェアにインポートし、受信リクエストのパス名がブルームフィルターに存在するかどうかを確認します。
存在する場合は、要求を Route Handlerに転送し、要求が実際に存在するかをチェックし、ユーザーを適切なURLにリダイレクトします。これにより、ミドルウェアに大量のリダイレクトファイルをインポートすることを避けることができ、すべての受信リクエストが遅くなるのを防ぎます。
- TypeScript
- JavaScript
import { NextResponse, NextRequest } from 'next/server'
import { ScalableBloomFilter } from 'bloom-filters'
import GeneratedBloomFilter from './redirects/bloom-filter.json'
type RedirectEntry = {
destination: string
permanent: boolean
}
// 生成されたJSONファイルからブルームフィルターを初期化する
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter as any)
export async function middleware(request: NextRequest) {
// 受信リクエストのパスを取得する
const pathname = request.nextUrl.pathname
// パスがブルームフィルターにあるかチェックする
if (bloomFilter.has(pathname)) {
// パス名をRoute Handlerに転送する
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin
)
try {
// Route Handlerからリダイレクトデータを取得する
const redirectData = await fetch(api)
if (redirectData.ok) {
const redirectEntry: RedirectEntry | undefined =
await redirectData.json()
if (redirectEntry) {
// ステータスコードを決定する
const statusCode = redirectEntry.permanent ? 308 : 307
// 宛先にリダイレクトする
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
}
} catch (error) {
console.error(error)
}
}
// リダイレクトが見つからなかった場合、そのままリクエストを続行します
return NextResponse.next()
}
import { NextResponse } from 'next/server'
import { ScalableBloomFilter } from 'bloom-filters'
import GeneratedBloomFilter from './redirects/bloom-filter.json'
// 生成されたJSONファイルからブルームフィルターを初期化する
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter)
export async function middleware(request) {
// 受信リクエストのパスを取得する
const pathname = request.nextUrl.pathname
// パスがブルームフィルターにあるかチェックする
if (bloomFilter.has(pathname)) {
// パス名をRoute Handlerに転送する
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin
)
try {
// Route Handlerからリダイレクトデータを取得する
const redirectData = await fetch(api)
if (redirectData.ok) {
const redirectEntry = await redirectData.json()
if (redirectEntry) {
// ステータスコードを決定する
const statusCode = redirectEntry.permanent ? 308 : 307
// 宛先にリダイレクトする
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
}
} catch (error) {
console.error(error)
}
}
// リダイレクトが見つからなかった場合、そのままリクエストを続行します
return NextResponse.next()
}
その後、Route Handlerでは:
- TypeScript
- JavaScript
import { NextRequest, NextResponse } from 'next/server'
import redirects from '@/app/redirects/redirects.json'
type RedirectEntry = {
destination: string
permanent: boolean
}
export function GET(request: NextRequest) {
const pathname = request.nextUrl.searchParams.get('pathname')
if (!pathname) {
return new Response('不正なリクエスト', { status: 400 })
}
// redirects.jsonファイルからリダイレクトエントリを取得する
const redirect = (redirects as Record<string, RedirectEntry>)[pathname]
// ブルームフィルターの誤検知に対応する
if (!redirect) {
return new Response('リダイレクトなし', { status: 400 })
}
// リダイレクトエントリを返す
return NextResponse.json(redirect)
}
import { NextResponse } from 'next/server'
import redirects from '@/app/redirects/redirects.json'
export function GET(request) {
const pathname = request.nextUrl.searchParams.get('pathname')
if (!pathname) {
return new Response('不正なリクエスト', { status: 400 })
}
// redirects.jsonファイルからリダイレクトエントリを取得する
const redirect = redirects[pathname]
// ブルームフィルターの誤検知に対応する
if (!redirect) {
return new Response('リダイレクトなし', { status: 400 })
}
// リダイレクトエントリを返す
return NextResponse.json(redirect)
}
参考情報:
- ブルームフィルターを生成するために、
bloom-filters
のようなライブラリを使用できます;- 悪意のあるリクエストを防止するために、Route Handlerに対して行われるリクエストを検証する必要があります;