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

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

APIНазначениеГде используетсяКод состояния
useRouterКлиентская навигацияКомпонентыN/A
redirects в next.config.jsПеренаправление входящего запроса на основе путиФайл next.config.js307 (Временное) или 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:

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

Если путь существует, перенаправьте запрос в 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.
  • Следует проверять запросы к вашему Обработчику маршрута, чтобы предотвратить вредоносные запросы.