1. A/B тестирование через middleware
В Next.js 15 middleware работает на уровне edge — максимально быстро, прямо на границе CDN, что делает его отличным инструментом для задач, где важна скорость принятия решения: например, определять, к какому варианту страницы отправить пользователя (A/B тест), или на каком языке ему показать сайт (i18n).
Что такое A/B тестирование?
A/B тестирование — это способ сравнить две (или больше) версии сайта или фичи, чтобы понять, какая из них эффективнее. Обычно часть пользователей видит вариант A, другая — вариант B. Дальше вы собираете статистику: кто больше кликает, покупает, остаётся дольше и т.д.
В Next.js A/B тестирование можно реализовать на уровне middleware, потому что оно позволяет:
- Делить пользователей на группы до того, как они увидят страницу.
- Сохранять выбранный вариант в cookie, чтобы пользователь всегда видел одну и ту же версию.
- Делать выборку случайной или по какому-то признаку (например, по географии, браузеру и т.д.).
Пример: Реализация простого A/B теста
Представим, что у нас есть две версии главной страницы: / (вариант A) и /variant-b (вариант B). Мы хотим, чтобы 50% пользователей видели вариант B, остальные — вариант A.
Структура файлов
/app
/page.tsx // Вариант A
/variant-b/page.tsx // Вариант B
/middleware.ts // Наш middleware
Пример кода middleware для A/B теста
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Проверяем, что это запрос к корню сайта ("/")
if (request.nextUrl.pathname === '/') {
// Проверяем, есть ли уже cookie с вариантом
const abCookie = request.cookies.get('ab-variant');
let variant = abCookie?.value;
if (!variant) {
// Если cookie нет — определяем вариант случайно
variant = Math.random() < 0.5 ? 'A' : 'B';
}
// Сохраняем выбор в cookie (чтобы пользователь всегда видел одну версию)
const response =
variant === 'B'
? NextResponse.redirect(new URL('/variant-b', request.url))
: NextResponse.next();
response.cookies.set('ab-variant', variant, { path: '/', maxAge: 60 * 60 * 24 * 30 });
return response;
}
// Для остальных маршрутов ничего не делаем
return NextResponse.next();
}
Как это работает?
- Пользователь заходит на /.
- Middleware проверяет, есть ли cookie ab-variant.
- Если нет — случайно выбирает вариант (A или B).
- Если выбран вариант B — делает редирект на /variant-b, иначе пропускает дальше.
- Устанавливает cookie, чтобы при следующем заходе пользователь попал в ту же группу.
- Всё это происходит мгновенно, ещё до рендера страницы.
Пример страницы B
// app/variant-b/page.tsx
export default function VariantBPage() {
return <h1>Это страница B (экспериментальная версия)</h1>;
}
А если вариантов больше?
Можно хранить в cookie не только "A" или "B", а, например, номер варианта (1, 2, 3), или даже делать выборку по сложным правилам.
Как собирать аналитику?
Обычно вы отправляете событие в свою систему аналитики (например, Google Analytics, Яндекс.Метрика или что-то кастомное) — например, вместе с событием "Открыта страница" передаёте значение cookie ab-variant. Это позволяет анализировать поведение каждой группы отдельно.
2. Интернационализация (i18n) через middleware
i18n (от английского Internationalization) — это "международализация", т.е. поддержка нескольких языков/регионов в вашем приложении. В Next.js есть встроенная поддержка i18n, но middleware позволяет реализовать более гибкие сценарии — например, автоматически перенаправлять пользователя на нужную языковую версию сайта в зависимости от его браузера, IP или предпочтений.
Пример: Автоматическое определение языка
Предположим, у нас есть две версии сайта: /ru и /en. Мы хотим, чтобы:
- Если пользователь заходит на /, его автоматически редиректило на /ru или /en в зависимости от его языка.
- Если пользователь уже зашёл на /ru или /en, ничего не делать.
- Язык сохранялся в cookie, чтобы при следующем заходе пользователь попадал на свой язык.
Структура файлов
/app
/ru/page.tsx
/en/page.tsx
/middleware.ts
Пример кода middleware для i18n
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const SUPPORTED_LOCALES = ['en', 'ru'];
const DEFAULT_LOCALE = 'en';
function getPreferredLocale(request: NextRequest) {
// 1. Читаем из cookie
const localeCookie = request.cookies.get('locale')?.value;
if (localeCookie && SUPPORTED_LOCALES.includes(localeCookie)) {
return localeCookie;
}
// 2. Пробуем из заголовка Accept-Language
const acceptLang = request.headers.get('accept-language');
if (acceptLang) {
const found = SUPPORTED_LOCALES.find((loc) =>
acceptLang.toLowerCase().startsWith(loc)
);
if (found) return found;
}
// 3. По умолчанию
return DEFAULT_LOCALE;
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Если уже на /ru или /en — ничего не делаем
if (SUPPORTED_LOCALES.some((loc) => pathname.startsWith(`/${loc}`))) {
return NextResponse.next();
}
// Определяем нужный язык
const locale = getPreferredLocale(request);
// Сохраняем язык в cookie и делаем редирект
const response = NextResponse.redirect(new URL(`/${locale}${pathname}`, request.url));
response.cookies.set('locale', locale, { path: '/', maxAge: 60 * 60 * 24 * 30 });
return response;
}
Как это работает?
- Пользователь заходит на / или на любой маршрут без языка.
- Middleware определяет язык: сначала по cookie, потом по заголовку браузера, потом дефолт.
- Делаем редирект на нужный язык (/ru или /en).
- Устанавливаем cookie, чтобы не определять язык заново при каждом заходе.
Пример страницы
// app/ru/page.tsx
export default function RussianPage() {
return <h1>Добро пожаловать!</h1>;
}
// app/en/page.tsx
export default function EnglishPage() {
return <h1>Welcome!</h1>;
}
Как добавить выбор языка вручную?
На сайте можно сделать переключатель языка, который меняет cookie locale и делает редирект на нужную версию. Middleware автоматически подхватит этот cookie при следующих заходах.
3. Комбинированные сценарии: A/B + i18n
А что если хочется и A/B тест, и интернационализацию? Например, чтобы в каждой языковой версии были свои варианты A/B теста. В middleware можно реализовать оба сценария: сначала определять язык, потом — вариант теста.
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const SUPPORTED_LOCALES = ['en', 'ru'];
const DEFAULT_LOCALE = 'en';
// 1. Проверяем язык
let locale = request.cookies.get('locale')?.value;
if (!locale || !SUPPORTED_LOCALES.includes(locale)) {
const acceptLang = request.headers.get('accept-language');
locale = SUPPORTED_LOCALES.find((loc) =>
acceptLang?.toLowerCase().startsWith(loc)
) || DEFAULT_LOCALE;
// Редиректим на нужный язык, если не на /ru или /en
if (!SUPPORTED_LOCALES.some((loc) => pathname.startsWith(`/${loc}`))) {
const response = NextResponse.redirect(new URL(`/${locale}${pathname}`, request.url));
response.cookies.set('locale', locale, { path: '/', maxAge: 60 * 60 * 24 * 30 });
return response;
}
}
// 2. A/B тестирование только на главной странице каждого языка
for (const loc of SUPPORTED_LOCALES) {
if (pathname === `/${loc}`) {
let variant = request.cookies.get('ab-variant')?.value;
if (!variant) {
variant = Math.random() < 0.5 ? 'A' : 'B';
}
if (variant === 'B') {
const response = NextResponse.redirect(new URL(`/${loc}/variant-b`, request.url));
response.cookies.set('ab-variant', variant, { path: '/', maxAge: 60 * 60 * 24 * 30 });
return response;
}
// Для варианта A ничего не делаем, просто сохраняем cookie
const response = NextResponse.next();
response.cookies.set('ab-variant', variant, { path: '/', maxAge: 60 * 60 * 24 * 30 });
return response;
}
}
// Остальные маршруты не трогаем
return NextResponse.next();
}
4. Практические нюансы и особенности
- Middleware не может работать с динамическими данными из базы: Всё, что нужно для принятия решения, должно быть доступно быстро (cookie, заголовки, путь, query-параметры).
- Не злоупотребляйте сложной логикой: Middleware должен быть быстрым! Не делайте в нём тяжёлых вычислений или долгих запросов.
- Edge middleware работает очень быстро, но есть ограничения: Например, нельзя использовать некоторые Node.js API (например, fs, net и т.п.)
- Редиректы в middleware — это обычная практика: Пользователь даже не заметит, что его перенаправили — всё происходит на edge-уровне и очень быстро.
5. Типичные ошибки при реализации A/B тестов и i18n в middleware
Ошибка №1: Не сохраняется вариант в cookie.
Если вы не сохраняете выбранный вариант A/B теста в cookie, пользователь может каждый раз попадать в новую группу при каждом заходе. Это испортит статистику и опыт пользователя. Всегда сохраняйте выбор!
Ошибка №2: Редирект зацикливается.
Если вы не проверяете, что пользователь уже находится на нужной странице (/variant-b или /ru), можно попасть в бесконечный цикл редиректов. Перед редиректом всегда проверяйте текущий путь!
Ошибка №3: Неправильная обработка языков.
Если вы не проверяете, что путь уже содержит язык (/ru, /en), middleware будет постоянно редиректить пользователя, даже если он уже на нужной странице.
Ошибка №4: Использование тяжёлых операций в middleware.
Middleware должен быть лёгким и быстрым. Не делайте в нём запросы в базу или сторонние API — это замедлит ответы всем пользователям.
Ошибка №5: Неочевидная логика выбора варианта.
Если вы используете слишком сложные условия для выбора A/B варианта или языка, это затруднит отладку и может привести к неожиданным результатам. Старайтесь держать логику максимально прозрачной и тестируемой.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ