04 янв. 2025 г.·7 мин чтения

Правила Next.js middleware, которые остаются понятными по мере роста приложения

Правила Next.js middleware проще поддерживать, когда проверки выстроены по назначению, порядок всегда один и тот же, а редиректы, локали, ботов и auth не смешивают в одну кучу.

Правила Next.js middleware, которые остаются понятными по мере роста приложения

Почему middleware быстро превращается в хаос

Файл middleware часто начинается с одной безобидной проверки. Может, вы перенаправляете неавторизованных пользователей с "/dashboard". Через неделю добавляете определение локали. Потом блокировку ботов. Потом особый случай для режима предпросмотра, старых URL или callback после входа. Файл всё ещё выглядит маленьким, но его поведение уже не кажется очевидным.

Вот в чём настоящая проблема правил Next.js middleware. Код может помещаться на одном экране, но каждая новая проверка меняет смысл тех, что стоят выше и ниже. Один ранний return может отменить редирект по локали. Одно правило для ботов может заблокировать маршрут, который auth должен обработать раньше. Один маленький патч способен превратить чистый поток запросов в груду догадок.

Чаще всего беспорядок создают мелкие исправления. Кто-то видит баг, добавляет ещё один if и идёт дальше. Приложение работает в этом случае, но в файле появляется скрытая система приоритетов, которую никто не записал. Спустя месяцы пользователя отправляет не на тот язык, или вошедший в систему пользователь попадает в цикл редиректов, и команде приходится разбирать запрос шаг за шагом, чтобы понять, что произошло.

Ситуацию усугубляет пересечение правил. Редиректы, проверки локали, фильтрация ботов в Next.js и наведение порядка в потоке авторизации редко надолго остаются отдельными. Маршрут входа может нуждаться в префиксе локали. Правило для ботов может пропускать публичные маркетинговые страницы, но блокировать страницы аккаунта. Редирект со старого пути может сработать до auth и отправить пользователя туда, что уже не совпадает с состоянием его сессии.

Простой пример показывает, как быстро это происходит. Вы добавляете правило, которое отправляет / на /en. Позже добавляете правило, которое отправляет вошедших в систему пользователей с /login в /app. Потом support просит добавить фильтрацию ботов для /app. Если порядок будет неаккуратным, бот сможет попасть на /login, получить редирект в /app, а затем запустить auth-логику, до которой вообще не должен был дойти.

Читабельный middleware обычно сводится к одной вещи: каждый запрос проходит через один и тот же фиксированный порядок. Когда этот порядок ясен, баги остаются локальными. Когда его нет, файл растёт неожиданно.

Что должно быть в middleware, а что — нет

Middleware должен принимать быстрые решения на основе самого запроса. Это хорошее место, чтобы проверить, нужен ли редирект, не потерян ли префикс локали, не стоит ли блокировать бота или есть ли вообще у пользователя session cookie.

Именно это и важно. Проверка cookie уместна в middleware. Поиск пользователя, загрузка данных аккаунта, тарифных планов или прав команды — нет. Как только middleware начинает тянуть данные страницы, он перестаёт быть охранником трафика и превращается в скрытую логику приложения.

Простое правило помогает: если решение зависит только от запроса и небольшой, недорогой дополнительной информации, middleware подходит. Если нужны обращения к базе, сложная логика прав или состояние конкретной страницы, перенесите это в route handler, server action или саму страницу.

Хороший middleware обычно укладывается в короткий список:

  • нормализовать URL и применять редиректы
  • добавлять или проверять префиксы локали
  • блокировать очевидно плохих ботов или вредоносные шаблоны
  • проверять, есть ли session token
  • добавлять простые флаги запроса для дальнейшего использования

Бизнес-правила должны жить в другом месте. Если премиальная функция должна открываться только для платных команд с активными контрактами, это правило должно быть рядом с самой функцией, а не в middleware. Если checkout-маршрут должен проверять остатки, статус купона и налоговые правила, поместите эту логику в handler, который отвечает за checkout.

Такое разделение упрощает поиск багов. Когда запрос перенаправляется до загрузки страницы, вы сразу понимаете, что смотреть нужно в middleware. Когда на странице показывается не тот тариф или роль, смотреть нужно в коде функции. Команды теряют массу времени, когда оба типа логики лежат в одном файле.

Лучшие правила Next.js middleware остаются скучными. Они срабатывают рано, быстро принимают решение и не мешают дальше. Если для объяснения правила нужен абзац, скорее всего, ему там не место.

Небольшой пример хорошо показывает границу. Перенаправить /dashboard на /en/dashboard, если локаль отсутствует? Это middleware. Загрузить виджеты дашборда пользователя на основе тарифного уровня и сохранённых настроек? Это код страницы или код маршрута. Такая граница помогает поддерживать порядок в потоке авторизации, а не распылять его по редиректам, rewrites и скрытым проверкам.

Выстраивайте правила в одном фиксированном порядке

Middleware становится запутанным, когда каждое новое правило может сделать редирект, rewrite или остановить запрос. Фиксированный порядок снижает число сюрпризов. Когда человек читает файл, он должен понимать, почему запрос сменил путь и какое правило сработало первым.

Хороший базовый порядок для правил Next.js middleware намеренно прост. Оставляйте одну и ту же последовательность каждый раз, даже когда приложение растёт.

  1. Сначала жёстко пропускайте assets, статические файлы и внутренние механизмы фреймворка.
  2. Затем запускайте проверки ботов и злоупотреблений до всего, что увидит пользователь.
  3. После этого нормализуйте URL с помощью локали и правил очистки.
  4. В конце применяйте auth как отдельный этап.

Первый шаг должен завершаться сразу. Если запрос относится к изображению, шрифту, скрипту, favicon или внутреннему asset Next.js, пропустите его дальше и ничего больше не делайте. Это экономит ресурсы и делает логи чище. Ещё это защищает от странных случаев, когда CSS-файл попадает под правило редиректа, написанное для страниц.

Проверки ботов должны идти рано, потому что им не нужен контекст пользователя. Если в приложение приходит скрапер, плохой краулер или очевидный шаблон злоупотребления, заблокируйте его до работы с локалью и проверками сессии. Иначе вы тратите время на запросы, которые никогда не были реальными просмотрами страниц.

После этого приведите URL в порядок. Именно здесь должны жить проверки локали, правила со слэшем в конце и другие исправления пути. Делайте это до auth, чтобы каждое следующее правило видело одну стабильную форму пути. Если /dashboard должен стать /en/dashboard, внесите это изменение первым.

Такой порядок помогает избежать запутанных цепочек редиректов. Типичная ошибка выглядит так: пользователь открывает /dashboard, auth отправляет его на /login, а потом логика локали снова меняет адрес на /en/login. Браузер всё равно добирается до нужной страницы, но разбираться в таком потоке сложнее, а крайние случаи копятся.

Оставляйте auth как один понятный этап ближе к концу. К этому моменту уже ясно, что запрос идёт на реальную страницу, а не на файл, не от бота и не через странную вариацию пути. Auth может принять одно чистое решение: разрешить запрос или отправить пользователя на правильную страницу входа.

Если новое правило не вписывается в этот порядок, это обычно тревожный знак. Значит, файл начинает прятать поведение, вместо того чтобы показывать его.

Стройте поток правил шаг за шагом

Начинайте не с редактора. Middleware быстро превращается в кашу, когда люди каждую неделю добавляют ещё одно условие и никогда не останавливаются, чтобы назвать правила.

Сначала выпишите все правила на бумаге, даже самые маленькие. Обычно сюда входят редиректы, проверки локали в middleware, фильтрация ботов в Next.js, auth-ограничения и короткий список публичных путей. Когда весь набор виден сразу, пересечения быстро становятся заметны.

Затем группируйте правила по назначению, а не по имени маршрута. Файл дольше остаётся читаемым, если он задаёт простые вопросы вроде «нужно ли блокировать этот запрос?» или «нужна ли этому URL локаль?». Имена маршрутов могут быть внутри этих проверок, но они не должны определять структуру всего файла.

Чистый порядок часто выглядит так:

  1. Игнорируйте запросы, которых вы никогда не хотите касаться, например статические файлы или внутренние assets.
  2. Останавливайте плохих ботов или трафик, который не хотите обслуживать.
  3. Исправляйте форму URL, например префиксы локали или правила со слэшем в конце.
  4. Применяйте проверки auth и редиректы.
  5. Пропускайте запрос дальше.

Превращайте каждую группу в небольшую именованную функцию. Имена здесь делают большую часть работы. skipAsset, blockBot, addLocale и protectDashboard читаются лучше, чем длинная цепочка if с комментариями, которые быстро устаревают.

Каждая функция должна делать только одно и сразу завершаться, когда находит совпадение. Это важнее, чем кажется многим командам. Если правило для ботов сработало, остановитесь. Если запросу нужен редирект по локали, отправьте его и тоже остановитесь. Не давайте последующей auth-логике переписывать тот же запрос и скрывать, что произошло.

Верхнеуровневый поток должен читаться сверху вниз как чеклист. Человек, который впервые видит приложение, должен понять порядок редиректов в Next.js меньше чем за минуту.

Небольшой пример это хорошо показывает. Допустим, /dashboard требует входа, /pricing открыт для всех, каждой странице нужна локаль вроде /en, а некоторые скраперы нужно блокировать. Если запрос приходит на /dashboard без локали, middleware должен сначала добавить локаль, а потом уже дать следующему запросу решить вопрос с auth. Если на тот же путь приходит скрапер, правило для ботов должно остановить его до запуска auth-кода.

Такой порядок экономит время на отладке. Когда у каждого запроса есть только один возможный путь, странное поведение заметить проще.

Простой пример для приложения

Проведите аудит edge-логики Next.js
Проверьте middleware, rewrites и защищённые маршруты с человеком, который масштабировал реальные системы.

Небольшое приложение-магазин хорошо показывает порядок. В нём есть публичные страницы вроде /, /search и страницы товаров, страницы аккаунта вроде /account и /orders, а также административные страницы под /admin. Правила Next.js middleware остаются читаемыми, когда каждый запрос проходит одни и те же проверки в одном и том же порядке.

Начинайте с URL, а не с пользовательской сессии. Если человек открывает /, приложение сначала должно выбрать правильную локаль и перенаправить его на /en, /fr или другой поддерживаемый путь. Пользователь без входа не должен попадать на /login с / только потому, что приложение не нашло session cookie. Сначала нужно понять, какую страницу человек запросил, и только потом решать, требует ли она входа.

У страниц поиска есть отдельное правило. Маршрут поиска в магазине часто привлекает шумных скраперов и дешёвый бот-трафик. Если запрос идёт на /en/search?q=boots, middleware может проверить user agent и блокировать бота только на этом маршруте. Настоящие покупатели всё равно получат страницу. Публичные страницы товаров тоже будут загружаться. Хорошие краулеры, которых вы хотите разрешить, пройдут без изменений.

Auth должен срабатывать после этих проверок URL. Если пользователь без входа запрашивает /en/admin, запрос должен уйти на /en/login?next=/en/admin. Если тот же человек открывает /en/product/red-boots, middleware после проверки локали ничего не делает, потому что эта страница публичная.

Один запрос показывает весь поток:

  1. Пользователь вводит /admin в браузере.
  2. Middleware видит, что локаль отсутствует, и перенаправляет на /en/admin.
  3. Следующий запрос попадает на /en/admin.
  4. Middleware проверяет, не является ли маршрут страницей поиска. Это не так, поэтому правило для ботов ничего не делает.
  5. Middleware проверяет session. Валидного входа нет, поэтому он перенаправляет на /en/login?next=/en/admin.

Такой поток легко читать в коде и легко отлаживать в логах. Каждое правило делает только свою работу, и ни одно не пытается угадать, что сделает следующее.

Как тестировать поток

Начинайте с реальных URL, а не с абстрактных правил. Баги в middleware обычно проявляются, когда один запрос может попасть под две или три проверки, а никто не может договориться, какая из них должна сработать первой.

Сначала напишите короткую таблицу тестов, прежде чем менять код. Одной строки на запрос достаточно, если в каждой строке указаны путь, состояние пользователя и точный ожидаемый результат.

  • /pricing для гостя -> разрешить
  • /dashboard для гостя -> перенаправить на /login
  • /dashboard без локали -> перенаправить на /en/dashboard
  • /en/dashboard для вошедшего пользователя -> разрешить
  • /signup от известного бота -> заблокировать или вернуть ограниченный ответ

Этот список делает две полезные вещи. Он ловит скрытые конфликты правил и заставляет вас заранее определить порядок редиректов в Next.js, пока файл не превратился в догадки.

Обработке локали нужен отдельный фокус. Проверьте одну и ту же страницу дважды: один раз с локалью в URL и один раз без неё. Если ваше приложение отправляет гостя с /dashboard на /login, но ещё и добавляет /en/ первым, убедитесь, что результат каждый раз соответствует выбранному порядку.

То же самое сделайте для публичных и приватных страниц. Публичная страница должна оставаться публичной, даже когда сначала срабатывает логика локали. Приватная страница не должна прыгать через два или три редиректа, если только вы не спроектировали этот путь специально.

Проверки ботов заслуживают отдельного прохода. Используйте bot user agent или простой тестовый заголовок и убедитесь, что запрос останавливается там, где вы ожидаете. Если бот всё ещё может попасть на login, signup или дорогие маршруты, порядок правил, скорее всего, неверный.

Маленький лог помогает сильнее, чем длинный комментарий. Логируйте pathname и правило, которое сработало, чтобы видеть поток, а не гадать.

console.log(`[middleware] ${request.nextUrl.pathname} -> locale_redirect`)

Держите имя правила коротким и стабильным. Когда вы тестируете правила Next.js middleware таким способом, плохое поведение видно быстро: локаль срабатывает не первой, auth стоит не на своём месте или правило для ботов вообще не запускается. Если один запрос удивляет вас дважды, поток всё ещё нужно дорабатывать.

Ошибки, которые прячут плохое поведение

Проверьте поток middleware
Запишитесь на консультацию к Oleg Sotnikov, чтобы навести порядок в auth, локали и правилах для ботов до того, как они расползутся.

Большинство багов в middleware не появляются из-за одной плохой строки. Они возникают из маленьких упрощений, которые накапливаются, пока никто уже не может понять, почему запрос был перенаправлен, переписан или провалился.

Плохие правила Next.js middleware часто выглядят нормально при ревью. А потом пользователь открывает приватную страницу с неправильной локалью, на тот же путь приходит бот, и к этому добавляется маркетинговый редирект. Если все проверки живут в одном запутанном блоке, результат кажется случайным.

Одна из частых ошибок — смешивать маршрутизацию и auth в одной цепочке if. Редирект для /pricing не должен стоять рядом с проверкой сессии для /app. Эти правила отвечают на разные вопросы. Одно решает, куда должен попасть URL. Другое решает, кому разрешено войти. Когда они находятся в одном блоке, последующее изменение может поменять сразу оба поведения, и этого никто не заметит.

Ещё одна проблема — прятать списки маршрутов в слишком большом количестве helper-функций. Если публичные пути лежат в одном файле, исключения для локали — в другом, а пути только для ботов — в третьем, никто не видит общей картины. Держите списки рядом с middleware или соберите их в один понятный config-файл. Если коллеге нужно десять минут, чтобы понять, почему /fr/login пропускает auth, значит, всё уже слишком разбросано.

Rewrites тоже создают проблемы, когда запускаются слишком рано. Сначала завершите дешёвые проверки. Сначала проверьте очевидные редиректы, заблокированных ботов и правила локали, а уже потом переписывайте URL во что-то менее читаемое. Как только путь меняется, отладка замедляется, потому что логи больше не совпадают с исходным запросом.

Побочные эффекты только ухудшают ситуацию. Одно правило ставит заголовок, другое его читает. Один helper меняет URL, другой предполагает старое значение. Такой подход экономит несколько строк и потом стоит часов. Каждый этап должен читать запрос, принимать решение и возвращать ответ. Он не должен тихо подготавливать состояние для следующего этапа.

Когда исключений становится всё больше, названия обычно перестают соответствовать реальности. «Публичные маршруты» превращаются в «публичные маршруты, кроме приглашённых пользователей и сценариев сброса пароля». Это тот момент, когда этап пора разделить и переименовать.

Читабельный поток обычно держит эти этапы отдельно:

  • прямые редиректы
  • проверки ботов
  • обработка локали
  • проверки auth
  • финальная логика переписывания

Если правило не подходит своему этапу, не втискивайте его туда. Найдите ему нормальное место, пока оно не стало скрытым поведением.

Короткая проверка перед релизом

Привлеките CTO на частичную занятость
Используйте помощь опытного CTO, чтобы очистить логику маршрутов и не тормозить продуктовую работу.

Хорошие правила Next.js middleware читаются как короткая карта маршрута, а не как набор сюрпризов. Перед merge дайте файл коллеге, который его не писал. Если он не может объяснить поток для трёх примеров запросов примерно за минуту, логика слишком запутана.

Используйте реальные запросы, а не абстрактные рассуждения. Возьмите одну публичную страницу, одну приватную страницу и один странный случай, например бот, который заходит на локализованный маршрут. Запрос вроде /fr/dashboard должно быть легко проследить сверху вниз: сначала проверка локали, потом правило для ботов, затем решение по auth, а после него — понятный выход.

Каждое правило должно делать одну вещь и останавливаться. Правило локали не должно одновременно решать вопрос auth. Правило для ботов не должно тихо переписывать маркетинговую страницу. Когда один блок делает слишком много, вы перестаёте видеть, где начинается поведение, и баги прячутся в промежутках.

Короткая проверка перед релизом помогает больше, чем ещё один комментарий в файле:

  • Новый коллега может описать порядок без догадок.
  • У каждого правила одна задача и один очевидный выход.
  • Любой запрос можно проследить шаг за шагом без прыжков между helper-функциями.
  • Логи показывают, какой этап обработал запрос.
  • Удаление одного правила не меняет несвязанные пути.

Логи важнее, чем многие команды признают. Когда срабатывает редирект, лог должен показывать, какой этап это сделал и почему. auth -> redirect /login — этого достаточно. middleware redirect слишком расплывчато и только тратит время, когда пользователи жалуются на петли или пропавшие страницы.

Один маленький тест часто выявляет плохую архитектуру: закомментируйте одно правило и снова прогоните те же запросы. Если проверка локали ломает обработку ботов или фильтр ботов меняет поведение auth, границы между ними слишком слабые. Чистые правила Next.js middleware выдерживают небольшие удаления, потому что каждый слой отвечает только за одно решение.

Если эта проверка кажется жёсткой, оставьте её жёсткой. Middleware работает до того, как остальная часть приложения вообще успеет помочь. Скрытое поведение здесь быстро расползается, и потом его дорого распутывать.

Что делать, когда файл продолжает расти

Растущий файл middleware обычно значит, что приложение начало осваивать новые правила быстрее, чем команда успевала убирать старые. Некоторое время это нормально. Ненормально становится тогда, когда никто уже не понимает с первого взгляда, почему пользователя перенаправили, почему изменилась локаль или почему заблокировали бота.

Если файл сложно просматривать меньше чем за минуту, сначала разделите данные, а не поток. Оставьте один основной файл middleware, который показывает порядок решений. А шумные части вынесите в небольшие модули с простыми именами.

  • routeMaps.ts для публичных путей, защищённых путей и целей редиректов
  • localeRules.ts для поддерживаемых локалей, локали по умолчанию и проверок пути
  • botRules.ts для проверок user-agent и правил разрешения или запрета
  • authRules.ts для проверок session и логики защищённых маршрутов

Основной файл должен читаться почти как короткий чеклист. Прочитать запрос. Проверить локаль. Проверить правила для ботов. Проверить auth. Вернуть ответ. Такая форма гораздо понятнее и надёжнее, чем файл с разбросанными блоками условий и вызовами helper-функций.

Это особенно важно, когда вы работаете с правилами Next.js middleware в нескольких командах. Один человек добавляет режим предпросмотра, другой — редиректы по странам, а кто-то третий исправляет проблему с crawler. Файл по-прежнему работает, но поведение становится всё менее предсказуемым.

Когда появляются новые продуктовые правила, сразу пересматривайте весь порядок. Посадочная страница партнёра, блокировка по региону или специальный промо-маршрут могут тонко изменить порядок редиректов в Next.js. Если просто прикрепить новое правило внизу, часто появляется поведение, которое всплывает только в production.

Одна простая привычка очень помогает: каждое новое условие в middleware должно отвечать на два вопроса в pull request. Где этому правилу место в порядке? Какое более раннее правило может его перебить? Это занимает две минуты и экономит часы отладки потом.

Если команда продолжает патчить эту область, а одни и те же баги возвращаются снова, короткий архитектурный разбор от Oleg Sotnikov может помочь. Он работает со стартап-командами над архитектурой приложений, AI-first разработкой и бережными production-системами, так что может помочь аккуратно разделить логику маршрутов и не дать потоку авторизации расползтись по всем углам приложения.