Українська

Досліджуйте передові техніки модифікації запитів за допомогою middleware в Next.js. Навчіться обробляти складну маршрутизацію, автентифікацію, A/B-тестування та стратегії локалізації для надійних веб-додатків.

Граничні випадки Middleware в Next.js: Опанування шаблонів модифікації запитів

Middleware в Next.js надає потужний механізм для перехоплення та модифікації запитів, перш ніж вони досягнуть маршрутів вашого застосунку. Ця можливість відкриває широкий спектр варіантів, від простих перевірок автентифікації до складних сценаріїв A/B-тестування та стратегій інтернаціоналізації. Однак ефективне використання middleware вимагає глибокого розуміння його граничних випадків та потенційних підводних каменів. Цей вичерпний посібник розглядає передові шаблони модифікації запитів, надаючи практичні приклади та дієві поради, які допоможуть вам створювати надійні та продуктивні застосунки на Next.js.

Розуміння основ Middleware в Next.js

Перш ніж занурюватися в розширені шаблони, давайте повторимо основи middleware в Next.js. Функції middleware виконуються до завершення обробки запиту, дозволяючи вам:

Функції middleware розміщуються у файлі middleware.js або middleware.ts у вашому каталозі /pages або /app (залежно від версії та налаштувань Next.js). Вони отримують об'єкт NextRequest, що представляє вхідний запит, і можуть повертати об'єкт NextResponse для керування подальшою поведінкою.

Приклад: Базове Middleware для автентифікації

Цей приклад демонструє просту перевірку автентифікації. Якщо користувач не автентифікований (наприклад, немає дійсного токена в cookie), його перенаправляють на сторінку входу.


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

export function middleware(request: NextRequest) {
  const authToken = request.cookies.get('authToken')

  if (!authToken) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}

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

Це middleware буде виконуватися тільки для маршрутів, що відповідають /protected/:path*. Воно перевіряє наявність cookie authToken. Якщо cookie відсутній, користувача перенаправляють на сторінку /login. В іншому випадку, запит продовжує оброблятися у звичайному режимі за допомогою NextResponse.next().

Розширені шаблони модифікації запитів

Тепер давайте розглянемо деякі розширені шаблони модифікації запитів, які демонструють справжню потужність middleware в Next.js.

1. A/B-тестування за допомогою Cookies

A/B-тестування є ключовою технікою для оптимізації користувацького досвіду. Middleware можна використовувати для випадкового призначення користувачів до різних варіацій вашого застосунку та відстеження їхньої поведінки. Цей шаблон покладається на файли cookie для збереження призначеного користувачеві варіанту.

Приклад: A/B-тестування цільової сторінки


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

const VARIANT_A = 'variantA'
const VARIANT_B = 'variantB'

export function middleware(request: NextRequest) {
  let variant = request.cookies.get('variant')?.value

  if (!variant) {
    // Randomly assign a variant
    variant = Math.random() < 0.5 ? VARIANT_A : VARIANT_B
    const response = NextResponse.next()
    response.cookies.set('variant', variant)
    return response
  }

  if (variant === VARIANT_A) {
    return NextResponse.rewrite(new URL('/variant-a', request.url))
  } else if (variant === VARIANT_B) {
    return NextResponse.rewrite(new URL('/variant-b', request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/'],
}

У цьому прикладі, коли користувач відвідує кореневий шлях (/) вперше, middleware випадковим чином призначає його або до variantA, або до variantB. Цей варіант зберігається в cookie. Подальші запити від того ж користувача будуть переписані або на /variant-a, або на /variant-b, залежно від призначеного варіанту. Це дозволяє вам показувати різні цільові сторінки та відстежувати, яка з них працює краще. Переконайтеся, що у вашому застосунку Next.js визначені маршрути для /variant-a та /variant-b.

Глобальні міркування: Під час проведення A/B-тестування враховуйте регіональні відмінності. Дизайн, який знаходить відгук у Північній Америці, може бути не таким ефективним в Азії. Ви можете використовувати дані геолокації (отримані через IP-адресу або налаштування користувача) для адаптації A/B-тесту до конкретних регіонів.

2. Локалізація (i18n) за допомогою переписування URL

Інтернаціоналізація (i18n) є важливою для охоплення глобальної аудиторії. Middleware можна використовувати для автоматичного визначення бажаної мови користувача та перенаправлення його на відповідну локалізовану версію вашого сайту.

Приклад: Перенаправлення на основі заголовка `Accept-Language`


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

const SUPPORTED_LANGUAGES = ['en', 'fr', 'es', 'de']
const DEFAULT_LANGUAGE = 'en'

function getPreferredLanguage(request: NextRequest): string {
  const acceptLanguage = request.headers.get('accept-language')
  if (!acceptLanguage) {
    return DEFAULT_LANGUAGE
  }

  const languages = acceptLanguage.split(',').map((lang) => lang.split(';')[0].trim())

  for (const lang of languages) {
    if (SUPPORTED_LANGUAGES.includes(lang)) {
      return lang
    }
  }

  return DEFAULT_LANGUAGE
}

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

  // Check if there's an existing locale in the pathname
  if (
    SUPPORTED_LANGUAGES.some(
      (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
    )
  ) {
    return NextResponse.next()
  }

  const preferredLanguage = getPreferredLanguage(request)

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

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

Це middleware витягує заголовок Accept-Language із запиту та визначає бажану мову користувача. Якщо URL ще не містить мовного префікса (наприклад, /en/about), middleware перенаправляє користувача на відповідний локалізований URL (наприклад, /fr/about для французької). Переконайтеся, що у вашому каталозі `/pages` або `/app` є відповідна структура папок для різних локалей. Наприклад, вам знадобляться файли `/pages/en/about.js` та `/pages/fr/about.js`.

Глобальні міркування: Переконайтеся, що ваша реалізація i18n правильно обробляє мови з написанням справа наліво (наприклад, арабська, іврит). Також розгляньте можливість використання мережі доставки контенту (CDN) для подачі локалізованих ресурсів із серверів, розташованих ближче до ваших користувачів, що покращить продуктивність.

3. Функціональні прапорці (Feature Flags)

Функціональні прапорці дозволяють вмикати або вимикати функції у вашому застосунку без розгортання нового коду. Це особливо корисно для поступового впровадження нових функцій або для тестування функцій у продакшені. Middleware можна використовувати для перевірки статусу функціонального прапорця та відповідної модифікації запиту.

Приклад: Увімкнення бета-функції


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

const BETA_FEATURE_ENABLED = process.env.BETA_FEATURE_ENABLED === 'true'

export function middleware(request: NextRequest) {
  if (BETA_FEATURE_ENABLED && request.nextUrl.pathname.startsWith('/new-feature')) {
    return NextResponse.next()
  }

  // Optionally redirect to a "feature unavailable" page
  return NextResponse.rewrite(new URL('/feature-unavailable', request.url))
}

export const config = {
  matcher: ['/new-feature/:path*'],
}

Це middleware перевіряє значення змінної середовища BETA_FEATURE_ENABLED. Якщо вона встановлена в true і користувач намагається отримати доступ до маршруту під /new-feature, запит дозволяється. В іншому випадку, користувача перенаправляють на сторінку /feature-unavailable. Не забудьте правильно налаштувати змінні середовища для різних оточень (розробка, тестування, продакшн).

Глобальні міркування: Використовуючи функціональні прапорці, враховуйте юридичні наслідки ввімкнення функцій, які можуть не відповідати нормам у всіх регіонах. Наприклад, функції, пов'язані з конфіденційністю даних, можливо, доведеться вимкнути в певних країнах.

4. Визначення пристроїв та адаптивна маршрутизація

Сучасні веб-додатки повинні бути адаптивними та пристосовуватися до різних розмірів екранів та можливостей пристроїв. Middleware можна використовувати для визначення типу пристрою користувача та перенаправлення його на оптимізовані версії вашого сайту.

Приклад: Перенаправлення мобільних користувачів на оптимізований для мобільних пристроїв субдомен


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

export function middleware(request: NextRequest) {
  const userAgent = request.headers.get('user-agent')

  if (userAgent) {
    const deviceType = device(userAgent)

    if (deviceType.type === 'phone') {
      const mobileUrl = new URL(request.url)
      mobileUrl.hostname = 'm.example.com'
      return NextResponse.redirect(mobileUrl)
    }
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/'],
}

Цей приклад використовує бібліотеку `detection` для визначення типу пристрою користувача на основі заголовка User-Agent. Якщо користувач використовує мобільний телефон, його перенаправляють на субдомен m.example.com (за умови, що у вас там розміщена оптимізована для мобільних пристроїв версія сайту). Не забудьте встановити пакет `detection`: `npm install detection`.

Глобальні міркування: Переконайтеся, що ваша логіка визначення пристроїв враховує регіональні відмінності у використанні пристроїв. Наприклад, у деяких країнах, що розвиваються, все ще поширені кнопкові телефони. Розгляньте можливість використання комбінації визначення User-Agent та технік адаптивного дизайну для більш надійного рішення.

5. Збагачення заголовків запиту

Middleware може додавати інформацію до заголовків запиту перед тим, як він буде оброблений маршрутами вашого застосунку. Це корисно для додавання користувацьких метаданих, таких як ролі користувачів, статус автентифікації або ідентифікатори запитів, які можуть бути використані логікою вашого застосунку.

Приклад: Додавання ідентифікатора запиту


import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { v4 as uuidv4 } from 'uuid'

export function middleware(request: NextRequest) {
  const requestId = uuidv4()
  const response = NextResponse.next()
  response.headers.set('x-request-id', requestId)
  return response
}

export const config = {
  matcher: ['/api/:path*'], // Only apply to API routes
}

Це middleware генерує унікальний ідентифікатор запиту за допомогою бібліотеки uuid і додає його до заголовка x-request-id. Цей ідентифікатор потім можна використовувати для цілей логування, трасування та налагодження. Не забудьте встановити пакет `uuid`: `npm install uuid`.

Глобальні міркування: Додаючи користувацькі заголовки, пам'ятайте про обмеження на їх розмір. Перевищення цих лімітів може призвести до неочікуваних помилок. Також переконайтеся, що будь-яка конфіденційна інформація, додана до заголовків, належним чином захищена, особливо якщо ваш застосунок знаходиться за зворотним проксі або CDN.

6. Покращення безпеки: Обмеження частоти запитів (Rate Limiting)

Middleware може діяти як перша лінія захисту від зловмисних атак шляхом реалізації обмеження частоти запитів. Це запобігає зловживанням, обмежуючи кількість запитів, які клієнт може зробити протягом певного проміжку часу.

Приклад: Базове обмеження частоти запитів за допомогою простого сховища


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

const requestCounts: { [ip: string]: number } = {}
const WINDOW_SIZE_MS = 60000; // 1 minute
const MAX_REQUESTS_PER_WINDOW = 100;

export function middleware(request: NextRequest) {
  const clientIP = request.ip || '127.0.0.1' // Get client IP, default to localhost for local testing

  if (!requestCounts[clientIP]) {
    requestCounts[clientIP] = 0;
  }

  requestCounts[clientIP]++;

  if (requestCounts[clientIP] > MAX_REQUESTS_PER_WINDOW) {
    return new NextResponse(
      JSON.stringify({ message: 'Too many requests' }),
      { status: 429, headers: { 'Content-Type': 'application/json' } }
    );
  }

  // Reset count after window
  setTimeout(() => {
    requestCounts[clientIP]--;
    if (requestCounts[clientIP] <= 0) {
        delete requestCounts[clientIP];
    }
  }, WINDOW_SIZE_MS);

  return NextResponse.next();
}

export const config = {
  matcher: ['/api/:path*'], // Apply to all API routes
}

Цей приклад підтримує просте сховище в пам'яті (requestCounts) для відстеження кількості запитів з кожної IP-адреси. Якщо клієнт перевищує MAX_REQUESTS_PER_WINDOW протягом WINDOW_SIZE_MS, middleware повертає помилку 429 Too Many Requests. Важливо: Це спрощений приклад, який не підходить для продакшн-середовищ, оскільки він не масштабується та вразливий до атак типу "відмова в обслуговуванні". Для використання в продакшені розгляньте більш надійне рішення для обмеження частоти запитів, наприклад Redis або спеціалізований сервіс.

Глобальні міркування: Стратегії обмеження частоти запитів повинні бути адаптовані до конкретних характеристик вашого застосунку та географічного розподілу ваших користувачів. Розгляньте можливість використання різних лімітів для різних регіонів або сегментів користувачів.

Граничні випадки та потенційні підводні камені

Хоча middleware є потужним інструментом, важливо знати про його обмеження та потенційні підводні камені:

Найкращі практики використання Middleware в Next.js

Щоб максимізувати переваги middleware в Next.js та уникнути потенційних проблем, дотримуйтесь цих найкращих практик:

Висновок

Middleware в Next.js пропонує потужний спосіб модифікувати запити та налаштовувати поведінку вашого застосунку на межі мережі (edge). Розуміючи розширені шаблони модифікації запитів, обговорені в цьому посібнику, ви можете створювати надійні, продуктивні та глобально орієнтовані застосунки на Next.js. Не забувайте уважно розглядати граничні випадки та потенційні підводні камені, а також дотримуватися найкращих практик, викладених вище, щоб забезпечити надійність та легкість у підтримці ваших функцій middleware. Використовуйте потужність middleware для створення виняткових користувацьких досвідів та відкриття нових можливостей для ваших веб-додатків.