Создание middleware.ts и его назначение в Next.js

Модуль 4: Node.js, Next.js и Angular
11 уровень , 5 лекция
Открыта

1. Что такое middleware в Next.js и зачем он нужен

В мире веб-разработки middleware — это такой себе "швейцар на входе": он стоит перед дверью (вашим маршрутом) и решает, кого пускать, кого не пускать, а кому выдать тапочки и показать, где раздевалка. Если говорить по-серьёзному, middleware — это специальная функция, которая выполняется до того, как пользователь получит доступ к странице или API-эндпоинту, и может изменить или прервать этот доступ.

В Next.js с появлением App Router (начиная с версии 13 и выше) появилась поддержка глобального middleware через файл middleware.ts (или middleware.js). Этот файл располагается в корне вашего проекта (рядом с app/ или pages/) и автоматически применяется ко всем запросам, которые проходят через ваше приложение.

Для чего нужен middleware?

  • Защита маршрутов: проверка авторизации, редирект неавторизованных пользователей на страницу входа.
  • Локализация: определение языка пользователя и установка нужной локали.
  • A/B тестирование: выбор варианта страницы для пользователя.
  • Аналитика и логирование: сбор статистики о посещениях.
  • Модификация запросов и ответов: например, установка заголовков безопасности.

middleware — это ваш первый рубеж обороны и универсальный инструмент для "глобальных" сценариев.

2. Как работает middleware в Next.js

В Next.js middleware — это обычная функция (или асинхронная функция), которая экспортируется из файла middleware.ts и принимает на вход объект request (инстанс NextRequest). Она должна вернуть объект типа NextResponse (или ничего, если вы хотите пропустить запрос дальше).

  sequenceDiagram
  participant User
  participant Next.js
  participant Middleware
  participant Page/API

  User->>Next.js: Отправить запрос
  Next.js->>Middleware: Вызов (если путь совпадает)
  Middleware->>Middleware: Обработка запроса
(анализ, изменение, редирект, ошибка) alt Запрос обработан успешно Middleware-->>Next.js: Запрос обработан Next.js->>Page/API: Запрос к компоненту Page/API-->>Next.js: Результат else Произошел редирект Middleware-->>User: Редирект else Возникла ошибка Middleware-->>User: Ошибка end Next.js-->>User: Отправить результат

Схема работы middleware в Next.js

Порядок обработки запроса:

  1. Пользователь отправляет запрос к вашему приложению.
  2. Next.js вызывает middleware.ts, если путь запроса подпадает под область его действия.
  3. Middleware анализирует запрос, может модифицировать его, выполнить проверку, сделать редирект или даже вернуть ошибку.
  4. Если всё хорошо, запрос идёт дальше — к вашему компоненту страницы или API.

Создание файла middleware.ts: базовый пример

Давайте создадим свой первый middleware. Для этого в корне проекта (рядом с папкой app/ или pages/) создайте файл:

/middleware.ts

Минимальный рабочий пример:


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

export function middleware(request: NextRequest) {
  // Просто пропускаем все запросы дальше
  return NextResponse.next()
}

Этот middleware ничего не делает — он просто пропускает все запросы. Но это отличная точка старта!

3. Простейшая защита маршрута

Теперь представим, что у нас есть защищённая страница /dashboard, и мы хотим, чтобы неавторизованные пользователи попадали на /login.

Для этого нам надо:

  1. Проверить, залогинен ли пользователь (например, по наличию cookie или заголовка).
  2. Если не залогинен — сделать редирект на /login.

Пример middleware с редиректом


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

export function middleware(request: NextRequest) {
  // Проверяем, запрашивает ли пользователь защищённый маршрут
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    // Пример: проверяем наличие cookie "token"
    const hasToken = request.cookies.has('token')
    if (!hasToken) {
      // Не авторизован — редирект на /login
      return NextResponse.redirect(new URL('/login', request.url))
    }
  }

  // Всё ок — пропускаем дальше
  return NextResponse.next()
}

Пояснения:
Мы смотрим путь запроса через request.nextUrl.pathname.
Проверяем наличие cookie через request.cookies.has('token').
Если cookie нет — делаем редирект через NextResponse.redirect.
Если всё хорошо — пропускаем дальше через NextResponse.next().

В реальных проектах авторизация обычно сложнее (next-auth, JWT, серверная проверка), но концепция та же.

4. Область действия middleware: matcher

По умолчанию middleware применяется ко всем маршрутам приложения. Но иногда хочется, чтобы он работал только для некоторых путей (например, только для /dashboard/*, а не для /api/* или /login).

Для этого в Next.js можно использовать экспортируемый объект config и свойство matcher:


// middleware.ts
export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*']
}

Теперь middleware будет применяться только к маршрутам, начинающимся с /dashboard или /profile.

Примеры шаблонов:

  • /about — только к /about
  • /dashboard/:path* — ко всем /dashboard и его подпутям
  • /((?!api|_next/static|_next/image|favicon.ico).*) — ко всему, кроме API и служебных путей

5. Особенности работы middleware

  • Только Edge Runtime
    Middleware в Next.js работает на Edge Runtime — это облегчённая среда, похожая на серверless-функции, но с ограничениями. Например, нельзя использовать Node.js-модули, работать с файловой системой или запускать тяжелые вычисления. Всё должно быть максимально быстро и просто.
  • Только чтение cookies и заголовков
    В middleware нельзя изменять cookies или заголовки "на лету", только читать их. Если нужно изменить — используйте методы NextResponse, чтобы добавить/удалить cookie в ответе.
  • Нет доступа к React-компонентам
    Middleware работает до рендеринга React-компонентов. Здесь нельзя импортировать компоненты, обращаться к состоянию приложения или делать асинхронные запросы к базе данных (если только через fetch к внешнему API).

6. Примеры использования middleware

Пример 1. Локализация по языку браузера


import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const acceptLanguage = request.headers.get('accept-language')
  const userLang = acceptLanguage?.split(',')[0] || 'en'
  if (request.nextUrl.pathname === '/') {
    // Редиректим на /en или /ru в зависимости от языка браузера
    return NextResponse.redirect(new URL(`/${userLang}`, request.url))
  }
  return NextResponse.next()
}

Пример 2. Установка заголовков безопасности


import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  return response
}

Пример 3. A/B тестирование


import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // 50% пользователей попадают на /variant-a, 50% — на /variant-b
  if (request.nextUrl.pathname === '/experiment') {
    const variant = Math.random() < 0.5 ? 'a' : 'b'
    return NextResponse.rewrite(new URL(`/experiment/variant-${variant}`, request.url))
  }
  return NextResponse.next()
}

7. Интеграция с next-auth и проверка сессии

Если вы используете next-auth, то для проверки сессии в middleware потребуется использовать специальную функцию, потому что обычные серверные функции тут недоступны.

См. официальную документацию next-auth для middleware.

Пример (очень упрощённый):


import { withAuth } from 'next-auth/middleware'

export default withAuth({
  pages: {
    signIn: '/login'
  }
})

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

Пояснение:
withAuth автоматически проверяет сессию и редиректит на /login, если пользователь не авторизован.
Можно указать, к каким маршрутам применять middleware через matcher.

8. Типичные ошибки при работе с middleware

Ошибка №1: попытка использовать Node.js API или тяжелые библиотеки.
Middleware работает в Edge Runtime, поэтому нельзя использовать, например, fs, crypto (некоторые методы), или обращаться к базе данных напрямую. Если нужно что-то сложное — делайте это на сервере, а не в middleware.

Ошибка №2: забыли про область действия.
Если не указать matcher, middleware будет применяться ко всем маршрутам, включая API-эндпоинты и статику. Это может привести к неожиданным редиректам или ошибкам. Всегда явно указывайте, для каких путей нужен middleware.

Ошибка №3: неправильная работа с cookies.
В middleware можно только читать cookies из запроса, а чтобы добавить или удалить cookie, используйте методы NextResponse.

Ошибка №4: попытка использовать React-компоненты или серверные функции.
Middleware не может импортировать React-компоненты, обращаться к базе данных напрямую или использовать серверные функции Next.js. Всё должно быть максимально простым и быстрым.

Ошибка №5: забыли вернуть результат.
Если забыть вернуть NextResponse.next() или другой ответ, запрос "повиснет" и пользователь ничего не увидит.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ