Next.jsMiddleTechnical

Что такое Middleware в Next.js и каковы его варианты использования?

Middleware в Next.js — Edge-функция, запускаемая до рендеринга. Используется для аутентификации, редиректов, rewrite, геолокации, A/B-тестов и rate limiting.

Middleware в Next.js

Middleware — это Edge Runtime функция, которая выполняется перед обработкой запроса, до того как сервер отдаст страницу или API-ответ. Она запускается на Edge (Vercel) или на Node.js сервере, работает с объектами NextRequest и NextResponse и может модифицировать запрос, ответ, заголовки или сделать редирект.

Структура middleware.ts

// middleware.ts (в корне проекта, рядом с app/)
import { NextRequest, NextResponse } from 'next/server';

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

  // Логика здесь
  return NextResponse.next();
}

// Ограничиваем, к каким маршрутам применяется Middleware
export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|api/public).*)',
  ],
};

Вариант 1: Аутентификация и авторизация

import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';

const SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);

export async function middleware(req: NextRequest) {
  const token = req.cookies.get('auth_token')?.value;

  if (!token) {
    return NextResponse.redirect(new URL('/login', req.url));
  }

  try {
    await jwtVerify(token, SECRET);
    return NextResponse.next();
  } catch {
    const res = NextResponse.redirect(new URL('/login', req.url));
    res.cookies.delete('auth_token');
    return res;
  }
}

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

Вариант 2: Геолокация и A/B тестирование

import { NextRequest, NextResponse } from 'next/server';

export function middleware(req: NextRequest) {
  // Заголовки геолокации (Vercel автоматически добавляет)
  const country = req.geo?.country ?? req.headers.get('x-vercel-ip-country');

  // A/B тест: 50/50 сплит
  let bucket = req.cookies.get('ab_bucket')?.value;
  if (!bucket) {
    bucket = Math.random() < 0.5 ? 'a' : 'b';
  }

  const res = NextResponse.next();
  res.cookies.set('ab_bucket', bucket, { maxAge: 60 * 60 * 24 * 30 });
  res.headers.set('x-ab-bucket', bucket);
  res.headers.set('x-country', country ?? 'unknown');

  return res;
}

Вариант 3: Редиректы и rewrite

import { NextRequest, NextResponse } from 'next/server';

export function middleware(req: NextRequest) {
  const { pathname, search } = req.nextUrl;

  // Redirect старых URL
  if (pathname.startsWith('/old-blog')) {
    return NextResponse.redirect(
      new URL(pathname.replace('/old-blog', '/blog') + search, req.url),
      { status: 301 }
    );
  }

  // Rewrite: показываем /maintenance без смены URL
  if (process.env.MAINTENANCE_MODE === 'true') {
    return NextResponse.rewrite(new URL('/maintenance', req.url));
  }

  return NextResponse.next();
}

Вариант 4: Rate limiting (с Redis)

import { NextRequest, NextResponse } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '10 s'),
});

export async function middleware(req: NextRequest) {
  const ip = req.headers.get('x-forwarded-for') ?? '127.0.0.1';
  const { success, remaining } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json(
      { error: 'Too many requests' },
      { status: 429, headers: { 'X-RateLimit-Remaining': String(remaining) } }
    );
  }
  return NextResponse.next();
}

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

Ключевые ограничения Edge Runtime

  • Нет доступа к файловой системе
  • Нет Node.js-специфичных API (fs, path, нативные модули)
  • Нет прямого подключения к PostgreSQL — только HTTP-запросы или Upstash Redis
  • Лимит размера Middleware: 1 MB на Vercel

Подводные камни

  • Middleware выполняется при каждом запросе: без правильного matcher он будет запускаться для статики, шрифтов и изображений — это замедляет ответ.
  • Edge Runtime ограничен: нельзя использовать Prisma, pg, bcrypt и другие Node.js-зависимые пакеты напрямую.
  • Чтение тела запроса недоступно: req.body недоступен в Middleware — для проверки тела используйте Route Handler.
  • Cookies после редиректа: при NextResponse.redirect cookie из res.cookies.set() не всегда применяются — нужно тестировать поведение в конкретной среде деплоя.
  • Бесконечный редирект: если matcher перехватывает страницу /login и редирект идёт туда же — infinite loop. Всегда исключайте целевые страницы редиректа из matcher.
  • Нет Promise.all для параллельных fetch: при длинной цепочке fetch в Middleware время ответа растёт линейно — держите Middleware максимально лёгким.
  • Несовместимость с некоторыми npm-пакетами: пакеты, использующие process.env во время импорта или динамические require, могут падать в Edge Runtime.

Common mistakes

  • Использовать Node-only SDK в Edge middleware
  • Делать тяжёлую логику и блокировать каждый запрос
  • Полагаться только на middleware-проверку для авторизации
  • Забывать про matcher и обрабатывать ассеты

What the interviewer is testing

  • Знает Edge vs Node runtime
  • Умеет писать matcher
  • Понимает ограничения API и bundle-size
  • Различает middleware-проверку и RSC-проверку

Sources

Related topics