Как обрабатывать редиректы в Next.js
Существует несколько способов обработки редиректов в Next.js. На этой странице рассматриваются все доступные варианты, случаи их использования и управление большим количеством редиректов.
API | Назначение | Где используется | Код состояния |
---|---|---|---|
useRouter | Клиентская навигация | Компоненты | N/A |
redirects в next.config.js | Перенаправление входящего запроса на основе пути | Файл next.config.js | 307 (Временный) или 308 (Постоянный) |
NextResponse.redirect | Перенаправление входящего запроса на основе условия | Middleware | Любой |
Хук useRouter()
Если нужно перенаправить пользователя внутри компонента, можно использовать метод push
из хука useRouter
. Например:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
Важно знать:
- Если не требуется программная навигация, лучше использовать компонент
<Link>
.
Подробнее см. в справочнике API useRouter
.
redirects
в next.config.js
Опция redirects
в файле next.config.js
позволяет перенаправлять входящие запросы с одного пути на другой. Это полезно при изменении структуры URL страниц или наличии заранее известного списка редиректов.
redirects
поддерживает сопоставление путей, заголовков, куки и запросов, что даёт гибкость в перенаправлении пользователей на основе входящего запроса.
Чтобы использовать redirects
, добавьте опцию в файл next.config.js
:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async redirects() {
return [
// Простой редирект
{
source: '/about',
destination: '/',
permanent: true,
},
// Сопоставление с подстановочным знаком
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
]
},
}
export default nextConfig
module.exports = {
async redirects() {
return [
// Простой редирект
{
source: '/about',
destination: '/',
permanent: true,
},
// Сопоставление с подстановочным знаком
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
]
},
}
Подробнее см. в справочнике API redirects
.
Важно знать:
redirects
может возвращать код состояния 307 (Временное перенаправление) или 308 (Постоянное перенаправление) с опциейpermanent
.- На некоторых платформах есть ограничения на количество редиректов. Например, на Vercel лимит составляет 1024 редиректа. Для управления большим количеством редиректов (1000+) рассмотрите создание кастомного решения с использованием Middleware. Подробнее см. в разделе управление большим количеством редиректов.
redirects
выполняется до Middleware.
NextResponse.redirect
в Middleware
Middleware позволяет выполнять код до завершения запроса. Затем, на основе входящего запроса, можно перенаправить пользователя на другой URL с помощью NextResponse.redirect
. Это полезно для перенаправления пользователей на основе условий (например, аутентификации, управления сессиями) или при большом количестве редиректов.
Например, для перенаправления пользователя на страницу /login
, если он не аутентифицирован:
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*',
}
Важно знать:
- Middleware выполняется после
redirects
вnext.config.js
и до рендеринга.
Подробнее см. в документации по Middleware.
Управление большим количеством редиректов (продвинутый уровень)
Для управления большим количеством редиректов (1000+) можно создать кастомное решение с использованием Middleware. Это позволяет обрабатывать редиректы программно без необходимости переразвёртывания приложения.
Для этого нужно учитывать:
- Создание и хранение карты редиректов.
- Оптимизацию производительности поиска данных.
Пример Next.js: Смотрите наш пример Middleware с фильтром Блума для реализации рекомендаций ниже.
1. Создание и хранение карты редиректов
Карта редиректов — это список редиректов, который можно хранить в базе данных (обычно key-value хранилище) или JSON-файле.
Рассмотрим следующую структуру данных:
{
"/old": {
"destination": "/new",
"permanent": true
},
"/blog/post-old": {
"destination": "/blog/post-new",
"permanent": true
}
}
В Middleware можно читать из базы данных, например Edge Config от Vercel или Redis, и перенаправлять пользователя на основе входящего запроса:
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. Оптимизация производительности поиска данных
Чтение большого набора данных для каждого входящего запроса может быть медленным и ресурсоемким. Есть два способа оптимизировать производительность поиска данных:
- Использовать базу данных, оптимизированную для быстрого чтения
- Использовать стратегию поиска данных, такую как фильтр Блума (Bloom filter), чтобы эффективно проверить наличие редиректа перед чтением большого файла или базы данных с редиректами.
Рассмотрим предыдущий пример: можно импортировать сгенерированный файл фильтра Блума в Middleware, а затем проверить, существует ли путь входящего запроса в фильтре.
Если путь существует, запрос перенаправляется в API-маршруты (API Routes), который проверит фактический файл и перенаправит пользователя на нужный URL. Это позволяет избежать импорта большого файла редиректов в Middleware, что может замедлить обработку каждого входящего запроса.
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)) {
// Перенаправляем путь в Обработчик маршрута
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin
)
try {
// Получаем данные редиректа из Обработчика маршрута
const redirectData = await fetch(api)
if (redirectData.ok) {
const redirectEntry: RedirectEntry | undefined =
await redirectData.json()
if (redirectEntry) {
// Определяем код статуса
const statusCode = redirectEntry.permanent ? 308 : 307
// Перенаправляем на целевой URL
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)) {
// Перенаправляем путь в Обработчик маршрута
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin
)
try {
// Получаем данные редиректа из Обработчика маршрута
const redirectData = await fetch(api)
if (redirectData.ok) {
const redirectEntry = await redirectData.json()
if (redirectEntry) {
// Определяем код статуса
const statusCode = redirectEntry.permanent ? 308 : 307
// Перенаправляем на целевой URL
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
}
} catch (error) {
console.error(error)
}
}
// Редирект не найден, продолжаем обработку запроса без перенаправления
return NextResponse.next()
}
Затем в API-маршруте:
import type { NextApiRequest, NextApiResponse } from 'next'
import redirects from '@/app/redirects/redirects.json'
type RedirectEntry = {
destination: string
permanent: boolean
}
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const pathname = req.query.pathname
if (!pathname) {
return res.status(400).json({ message: 'Bad Request' })
}
// Получаем запись редиректа из файла redirects.json
const redirect = (redirects as Record<string, RedirectEntry>)[pathname]
// Учитываем ложные срабатывания фильтра Блума
if (!redirect) {
return res.status(400).json({ message: 'No redirect' })
}
// Возвращаем запись редиректа
return res.json(redirect)
}
import redirects from '@/app/redirects/redirects.json'
export default function handler(req, res) {
const pathname = req.query.pathname
if (!pathname) {
return res.status(400).json({ message: 'Bad Request' })
}
// Получаем запись редиректа из файла redirects.json
const redirect = redirects[pathname]
// Учитываем ложные срабатывания фильтра Блума
if (!redirect) {
return res.status(400).json({ message: 'No redirect' })
}
// Возвращаем запись редиректа
return res.json(redirect)
}
Полезно знать:
- Для генерации фильтра Блума можно использовать библиотеку
bloom-filters
.- Следует проверять запросы к вашему Обработчику маршрута, чтобы предотвратить вредоносные запросы.