Перенаправления (Redirects)

В Next.js существует несколько способов обработки перенаправлений. На этой странице рассматриваются все доступные варианты, случаи их использования и управление большим количеством перенаправлений.

APIНазначениеГде используетсяКод состояния
redirectПеренаправление пользователя после мутации или событияСерверные компоненты, Серверные действия, Обработчики маршрутов307 (Временное) или 303 (Серверное действие)
permanentRedirectПостоянное перенаправление пользователя после мутации или событияСерверные компоненты, Серверные действия, Обработчики маршрутов308 (Постоянное)
useRouterКлиентская навигацияОбработчики событий в клиентских компонентахN/A
redirects в next.config.jsПеренаправление входящего запроса на основе путиФайл next.config.js307 (Временное) или 308 (Постоянное)
NextResponse.redirectПеренаправление на основе условияПромежуточное ПО (Middleware)Любой

Функция redirect

Функция redirect позволяет перенаправить пользователя на другой URL. Её можно вызывать в Серверных компонентах, Обработчиках маршрутов и Серверных действиях.

redirect часто используется после мутации или события. Например, при создании поста:

'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 (Временное перенаправление). При использовании в Серверном действии возвращается код 303 (See Other), который обычно используется для перенаправления на страницу успеха после POST-запроса.
  • redirect внутренне выбрасывает ошибку, поэтому должен вызываться вне блоков try/catch.
  • redirect можно вызывать в Клиентских компонентах во время рендеринга, но не в обработчиках событий. Вместо этого используйте хук useRouter.
  • redirect также принимает абсолютные URL и может использоваться для перенаправления на внешние ссылки.
  • Если нужно перенаправить до процесса рендеринга, используйте next.config.js или Промежуточное ПО.

Подробнее см. в справочнике API redirect.

Функция permanentRedirect

Функция permanentRedirect позволяет постоянно перенаправить пользователя на другой URL. Её можно вызывать в Серверных компонентах, Обработчиках маршрутов и Серверных действиях.

permanentRedirect часто используется после мутации или события, изменяющего канонический URL сущности, например, при обновлении URL профиля пользователя после смены имени:

'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 или Промежуточное ПО.

Подробнее см. в справочнике API permanentRedirect.

Хук useRouter()

Для перенаправления в обработчике событий Клиентского компонента можно использовать метод push из хука useRouter. Например:

'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>.

Подробнее см. в справочнике API useRouter.

redirects в next.config.js

Опция redirects в файле next.config.js позволяет перенаправлять входящие запросы с одного пути на другой. Это полезно при изменении структуры URL страниц или наличии заранее известных перенаправлений.

redirects поддерживает сопоставление по пути, заголовкам, кукам и параметрам запроса, что позволяет гибко перенаправлять пользователей на основе входящего запроса.

Для использования redirects добавьте опцию в файл next.config.js:

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+) можно создать кастомное решение с использованием Промежуточного ПО. Это позволяет обрабатывать перенаправления программно без необходимости переразвертывания приложения.

Для этого необходимо:

  1. Создать и хранить карту перенаправлений.
  2. Оптимизировать производительность поиска данных.

Пример 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, а затем проверить, существует ли путь входящего запроса в фильтре.

Если путь существует, перенаправьте запрос в Обработчик маршрута (Route Handler) , который проверит фактический файл и перенаправит пользователя на соответствующий 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()
}

Затем в Обработчике маршрута:

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('Bad Request', { status: 400 })
  }

  // Получаем запись редиректа из файла redirects.json
  const redirect = (redirects as Record<string, RedirectEntry>)[pathname]

  // Учитываем ложные срабатывания фильтра Блума
  if (!redirect) {
    return new Response('No redirect', { 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('Bad Request', { status: 400 })
  }

  // Получаем запись редиректа из файла redirects.json
  const redirect = redirects[pathname]

  // Учитываем ложные срабатывания фильтра Блума
  if (!redirect) {
    return new Response('No redirect', { status: 400 })
  }

  // Возвращаем запись редиректа
  return NextResponse.json(redirect)
}

Полезно знать:

  • Для генерации фильтра Блума можно использовать библиотеку bloom-filters.
  • Следует проверять запросы к вашему Обработчику маршрута, чтобы предотвратить вредоносные запросы.