Перенаправления (Redirects)
В 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
:
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+) рассмотрите создание кастомного решения с использованием Промежуточного ПО. Подробнее см. в разделе Управление большим количеством перенаправлений.
redirects
выполняется до Промежуточного ПО.
NextResponse.redirect
в Промежуточном ПО
Промежуточное ПО (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*',
}
Полезно знать:
- Промежуточное ПО выполняется после
redirects
вnext.config.js
и до рендеринга.
Подробнее см. в документации по Промежуточному ПО.
Управление большим количеством перенаправлений (продвинутый уровень)
Для управления большим количеством перенаправлений (1000+) можно создать кастомное решение с использованием Промежуточного ПО. Это позволяет обрабатывать перенаправления программно без необходимости переразвертывания приложения.
Для этого необходимо:
- Создать и хранить карту перенаправлений.
- Оптимизировать производительность поиска данных.
Пример Next.js: См. наш пример Промежуточное ПО с фильтром Блума для реализации рекомендаций ниже.
1. Создание и хранение карты перенаправлений
Карта перенаправлений — это список перенаправлений, который можно хранить в базе данных (обычно key-value хранилище) или JSON-файле.
Пример структуры данных:
{
"/old": {
"destination": "/new",
"permanent": true
},
"/blog/post-old": {
"destination": "/blog/post-new",
"permanent": true
}
}
В Промежуточном ПО можно читать из базы данных, например Edge Config или Redis от Vercel, и перенаправлять пользователя на основе входящего запроса:
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. Оптимизация производительности поиска данных
Чтение большого набора данных для каждого входящего запроса может быть медленным и ресурсоемким. Существует два способа оптимизировать производительность поиска данных:
- Использовать базу данных, оптимизированную для быстрого чтения, такую как Vercel Edge Config или Redis.
- Использовать стратегию поиска данных, такую как Фильтр Блума (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 { 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
.- Следует проверять запросы к вашему Обработчику маршрута, чтобы предотвратить вредоносные запросы.