Spec-Zone.ru › Next.js

ПОСРЕДНИК

Посредник позволяет запускать код до завершения запроса. Затем, исходя из входящего запроса, вы можете изменить ответ, переписав его, перенаправив его, изменив заголовки запроса или ответа или напрямую выдав ответ.

Посредник выполняется до кешированного контента и сопоставления маршрутов. Подробнее см. Сопоставление путей.

Сценарии использования

Интеграция посредника в ваше приложение может привести к существенному улучшению производительности, безопасности и пользовательского опыта. Некоторые распространенные сценарии, где посредник особенно эффективен, включают:

  • Аутентификация и авторизация: Убедитесь в идентичности пользователя и проверьте куки сессии перед предоставлением доступа к определенным страницам или маршрутам API.
  • Перенаправление на стороне сервера: Перенаправляйте пользователей на уровне сервера на основе определенных условий (например, языка, роли пользователя).
  • Переписывание пути: Поддерживайте A/B-тестирование, развертывание функций или устаревшие пути, динамически переписывая пути к маршрутам API или страницам на основе свойств запроса.
  • Обнаружение ботов: Защитите свои ресурсы, обнаруживая и блокируя трафик ботов.
  • Ведение журнала и аналитика: Собирайте и анализируйте данные запроса для получения информации перед обработкой страницей или API.
  • Флаговая система функций: Динамически включайте или отключайте функции для беспрепятственного развертывания или тестирования функций.

Важен также анализ ситуаций, где посредник может не быть оптимальным решением. Вот некоторые сценарии, на которые следует обратить внимание:

  • Сложная обработка и извлечение данных: Посредник не предназначен для непосредственного извлечения или обработки данных; это следует выполнять в обработчиках маршрутов или утилитах серверной стороны.
  • Затратные вычислительные задачи: Посредник должен быть лёгким и быстро реагировать, иначе он может привести к задержкам при загрузке страницы. Затратные вычислительные задачи или длительные процессы должны выполняться в выделенных обработчиках маршрутов.
  • Расширенное управление сессиями: Хотя посредник может обрабатывать базовые задачи управления сессиями, расширенное управление сессиями должно осуществляться с помощью специализированных служб аутентификации или внутри обработчиков маршрутов.
  • Прямые операции с базой данных: Не рекомендуется выполнять прямые операции с базой данных внутри посредника. Взаимодействие с базой данных должно выполняться в обработчиках маршрутов или утилитах серверной стороны.

Конвенция

Используйте файл middleware.ts (или .js) в корне вашего проекта для определения посредника. Например, на том же уровне, что и pages или app, или внутри src при необходимости.

Примечание: Хотя в проекте поддерживается только один файл middleware.ts, вы всё равно можете организовывать логику посредника модульно. Разбейте функциональность посредника на отдельные файлы .ts или .js и импортируйте их в ваш основной файл middleware.ts. Это позволяет более организованно управлять посредниками, относящимися к конкретному маршруту, которые объединяются в middleware.ts для централизованного управления. Использование одного файла посредника упрощает конфигурацию, предотвращает потенциальные конфликты и оптимизирует производительность, избегая нескольких слоёв посредников.

Пример

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}
 
// See "Matching Paths" below to learn more
export const config = {
  matcher: '/about/:path*',
}

Сопоставление путей

Посредник будет вызываться для каждого маршрута в вашем проекте. В связи с этим крайне важно использовать сопоставители для точной настройки или исключения определённых маршрутов. Порядок выполнения:

  1. headers из next.config.js
  2. redirects из next.config.js
  3. Посредник (rewrites, redirects, и т.д.)
  4. beforeFiles (rewrites) из next.config.js
  5. Маршруты файловой системы (public/, _next/static/, pages/, app/, и т.д.)
  6. afterFiles (rewrites) из next.config.js
  7. Динамические маршруты (/blog/[slug])
  8. fallback (rewrites) из next.config.js

Существует два способа определения путей, на которых будет работать посредник:

  1. Настройка пользовательского сопоставителя
  2. Условные операторы

Сопоставитель

matcher позволяет фильтровать посредника для запуска по определённым путям.

export const config = {
  matcher: '/about/:path*',
}

Вы можете сопоставить один путь или несколько путей с помощью синтаксиса массива:

export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

Конфигурация matcher позволяет использовать полные регулярные выражения, поэтому поддерживаются сопоставления, например, с отрицательными предвосхищениями или сопоставлениями символов. Пример использования отрицательного предвосхищения для сопоставления всех путей, кроме определённых, можно увидеть здесь:

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
}

Вы также можете обойти посредника для определённых запросов, используя массивы missing или has, или их комбинацию:

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
 
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      has: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
 
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      has: [{ type: 'header', key: 'x-present' }],
      missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
    },
  ],
}

Важно знать: Значения matcher должны быть константами, чтобы их можно было статически проанализировать во время сборки. Динамические значения, такие как переменные, будут игнорироваться.

Настроенные сопоставители:

  1. ДОЛЖНЫ начинаться с /
  2. Могут содержать именованные параметры: /about/:path соответствует /about/a и /about/b, но не /about/a/c
  3. Могут иметь модификаторы для именованных параметров (начинающихся с :): /about/:path* соответствует /about/a/b/c, потому что * означает ноль или более. ? означает ноль или один, а + — один или более
  4. Могут использовать регулярные выражения в скобках: /about/(.*) эквивалентно /about/:path*

Подробнее см. документацию по path-to-regexp.

Важно знать: Для обеспечения обратной совместимости Next.js всегда рассматривает /public как /public/index. Следовательно, сопоставитель /public/:path будет соответствовать.

Условные операторы

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/about')) {
    return NextResponse.rewrite(new URL('/about-2', request.url))
  }
 
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.rewrite(new URL('/dashboard/user', request.url))
  }
}

NextResponse

API NextResponse позволяет:

  • redirect входящий запрос на другой URL
  • rewrite ответ, отобразив заданный URL
  • Устанавливать заголовки запроса для маршрутов API, getServerSideProps, и пунктов назначения rewrite
  • Устанавливать куки ответа
  • Устанавливать заголовки ответа

Для создания ответа из посредника вы можете:

  1. rewrite к маршруту (Страница или Маршрут API-шлюза), который генерирует ответ
  2. Возвратить NextResponse непосредственно. См. Генерация ответа

Использование куки

Куки — это обычные заголовки. В Request, они хранятся в заголовке Cookie. В Response, они находятся в заголовке Set-Cookie. Next.js предоставляет удобный способ доступа и управления этими куки через расширение cookies в NextRequest и NextResponse.

  1. Для входящих запросов, cookies поставляется со следующими методами: get, getAll, set, и delete куки. Вы можете проверить наличие куки с помощью has или удалить все куки с помощью clear.
  2. Для исходящих ответов, cookies имеют следующие методы get, getAll, set, и delete.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  // Assume a "Cookie:nextjs=fast" header to be present on the incoming request
  // Getting cookies from the request using the `RequestCookies` API
  let cookie = request.cookies.get('nextjs')
  console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
  const allCookies = request.cookies.getAll()
  console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
 
  request.cookies.has('nextjs') // => true
  request.cookies.delete('nextjs')
  request.cookies.has('nextjs') // => false
 
  // Setting cookies on the response using the `ResponseCookies` API
  const response = NextResponse.next()
  response.cookies.set('vercel', 'fast')
  response.cookies.set({
    name: 'vercel',
    value: 'fast',
    path: '/',
  })
  cookie = response.cookies.get('vercel')
  console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
  // The outgoing response will have a `Set-Cookie:vercel=fast;path=/` header.
 
  return response
}

Установка заголовков

Вы можете установить заголовки запроса и ответа, используя API NextResponse (установка заголовков запроса доступна с Next.js v13.0.0).

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  // Clone the request headers and set a new header `x-hello-from-middleware1`
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-hello-from-middleware1', 'hello')
 
  // You can also set request headers in NextResponse.rewrite
  const response = NextResponse.next({
    request: {
      // New request headers
      headers: requestHeaders,
    },
  })
 
  // Set a new response header `x-hello-from-middleware2`
  response.headers.set('x-hello-from-middleware2', 'hello')
  return response
}

Важно знать: Избегайте установки больших заголовков, так как это может привести к ошибке 431 Request Header Fields Too Large, в зависимости от конфигурации вашего сервера веб-приложения.

CORS

Вы можете установить заголовки CORS в посреднике, чтобы разрешить запросы с разных сайтов, включая простые и предварительно запрошенные запросы.

import { NextRequest, NextResponse } from 'next/server'
 
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
 
const corsOptions = {
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
 
export function middleware(request: NextRequest) {
  // Check the origin from the request
  const origin = request.headers.get('origin') ?? ''
  const isAllowedOrigin = allowedOrigins.includes(origin)
 
  // Handle preflighted requests
  const isPreflight = request.method === 'OPTIONS'
 
  if (isPreflight) {
    const preflightHeaders = {
      ...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
      ...corsOptions,
    }
    return NextResponse.json({}, { headers: preflightHeaders })
  }
 
  // Handle simple requests
  const response = NextResponse.next()
 
  if (isAllowedOrigin) {
    response.headers.set('Access-Control-Allow-Origin', origin)
  }
 
  Object.entries(corsOptions).forEach(([key, value]) => {
    response.headers.set(key, value)
  })
 
  return response
}
 
export const config = {
  matcher: '/api/:path*',
}

Генерация ответа

Вы можете сгенерировать ответ из посредника напрямую, вернув экземпляр Response или NextResponse. (Это доступно с Next.js v13.1.0)

import { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
 
// Limit the middleware to paths starting with `/api/`
export const config = {
  matcher: '/api/:function*',
}
 
export function middleware(request: NextRequest) {
  // Call our authentication function to check the request
  if (!isAuthenticated(request)) {
    // Respond with JSON indicating an error message
    return Response.json(
      { success: false, message: 'authentication failed' },
      { status: 401 }
    )
  }
}

waitUntil и NextFetchEvent

Объект NextFetchEvent расширяет стандартный объект FetchEvent и включает метод waitUntil().

Метод waitUntil() принимает обещание в качестве аргумента и продлевает время существования посредника до выполнения обещания. Это полезно для выполнения работы в фоновом режиме.

import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
 
export function middleware(req: NextRequest, event: NextFetchEvent) {
  event.waitUntil(
    fetch('https://my-analytics-platform.com', {
      method: 'POST',
      body: JSON.stringify({ pathname: req.nextUrl.pathname }),
    })
  )
 
  return NextResponse.next()
}

Расширенные флаги посредника

В v13.1 Next.js были добавлены два дополнительных флага для middleware, skipMiddlewareUrlNormalize и skipTrailingSlashRedirect, для обработки сложных случаев использования.

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

module.exports = {
  skipTrailingSlashRedirect: true,
}
const legacyPrefixes = ['/docs', '/blog']
 
export default async function middleware(req) {
  const { pathname } = req.nextUrl
 
  if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
    return NextResponse.next()
  }
 
  // apply trailing slash handling
  if (
    !pathname.endsWith('/') &&
    !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
  ) {
    req.nextUrl.pathname += '/'
    return NextResponse.redirect(req.nextUrl)
  }
}

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

module.exports = {
  skipMiddlewareUrlNormalize: true,
}
export default async function middleware(req) {
  const { pathname } = req.nextUrl
 
  // GET /_next/data/build-id/hello.json
 
  console.log(pathname)
  // with the flag this now /_next/data/build-id/hello.json
  // without the flag this would be normalized to /hello
}

Runtime

Middleware в настоящее время поддерживает только Edge runtime. Node.js runtime использовать нельзя.

История версий

Версия Изменения
v13.1.0 Добавлены флаги расширенного middleware
v13.0.0 Middleware может изменять заголовки запроса, заголовки ответа и отправлять ответы
v12.2.0 Middleware стабилен, см. руководство по обновлению
v12.0.9 Принудительное использование абсолютных URL в Edge Runtime (PR)
v12.0.0 Добавлен middleware (Бета-версия)

© 2024 Vercel, Inc.
Licensed under the MIT License.
https://nextjs.org/docs/pages/building-your-application/routing/middleware

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API