Middleware в Next.js

Middleware в Next.js это функция, которая выполняется перед обработкой каждого запроса. Она позволяет перехватывать запросы и модифицировать ответ до того, как он попадет к пользователю.

Как создать Middleware

Middleware определяется в файле middleware.ts в корне проекта (рядом с app/ или pages/):

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  if (pathname.startsWith('/admin')) {
    const token = request.cookies.get('session')
    if (!token) {
      return NextResponse.redirect(new URL('/auth/login', request.url))
    }
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/admin/:path*', '/settings/:path*']
}

Matcher

config.matcher определяет, для каких путей будет запускаться middleware:

export const config = {
  matcher: [
    '/admin/:path*',
    '/settings/:path*',
    '/((?!api|_next/static|_next/image|favicon.ico).*)'
  ]
}

Без matcher'а middleware запустится для каждого запроса, включая статические файлы. Это лишняя нагрузка.

Типичные сценарии

Авторизация

export function middleware(request: NextRequest) {
  const token = request.cookies.get('session')
  const isAuthPage = request.nextUrl.pathname.startsWith('/auth')

  if (!token && !isAuthPage) {
    return NextResponse.redirect(new URL('/auth/login', request.url))
  }

  if (token && isAuthPage) {
    return NextResponse.redirect(new URL('/dashboard', request.url))
  }

  return NextResponse.next()
}

Интернационализация (i18n)

Так реализована локализация на Hack Frontend: middleware определяет язык пользователя и перенаправляет на соответствующий путь.

const locales = ['ru', 'en']
const defaultLocale = 'ru'

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl
  const hasLocale = locales.some(
    locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  )

  if (hasLocale) return NextResponse.next()

  const locale = request.headers.get('accept-language')?.startsWith('en')
    ? 'en'
    : defaultLocale

  return NextResponse.redirect(
    new URL(`/${locale}${pathname}`, request.url)
  )
}

Добавление заголовков

export function middleware(request: NextRequest) {
  const response = NextResponse.next()

  response.headers.set('x-request-id', crypto.randomUUID())
  response.headers.set(
    'Content-Security-Policy',
    "default-src 'self'"
  )

  return response
}

Edge Runtime

Middleware работает на Edge Runtime, а не на Node.js. Это означает:

  • Быстрый запуск (нет холодного старта)
  • Ограниченный API (нет файловой системы, нет нативных модулей Node.js)
  • Ограничение по размеру (1 MB)

Ограничения:

В Middleware нельзя использовать Prisma, тяжелые npm-пакеты или обращаться к базе данных напрямую. Для сложной логики авторизации лучше проверять токен в middleware, а детальную проверку прав делать в серверных компонентах или Server Actions.

Цепочка действий

Middleware может выполнять несколько действий последовательно:

export function middleware(request: NextRequest) {
  const response = NextResponse.next()

  // 1. Логирование
  console.log(`${request.method} ${request.nextUrl.pathname}`)

  // 2. Добавление заголовка
  response.headers.set('x-pathname', request.nextUrl.pathname)

  // 3. A/B тестирование
  if (!request.cookies.get('ab-variant')) {
    response.cookies.set('ab-variant', Math.random() > 0.5 ? 'A' : 'B')
  }

  return response
}

Полезные ресурсы

Связанные темы