proxy.ts (преемник middleware Next.js)
Коротко: В Next.js 16 middleware.ts переименован в proxy.ts — в проекте может существовать только один из двух файлов, иначе сборка ломается.
Подробнее: Как и прежний middleware, он выполняется до самой отрисовки/маршрутизации и может переписывать или перенаправлять запросы либо досрочно прерывать со своим ответом (например, 429 при превышении лимита частоты запросов). Matcher (config.matcher) определяет, для каких путей функция вообще вызывается.
Наш контекст: В Emzett proxy.ts намеренно решает ДВЕ задачи в одном файле: глобальное ограничение частоты запросов (широкий matcher, почти каждый запрос, кроме чисто читаемых страниц без форм) и лёгкое перенаправление аутентификации для защищённых областей (только проверка «cookie есть» — полная проверка сеанса по базе данных по-прежнему выполняется на сервере в getCurrentUser()). Согласно AGENTS.md в корне проекта — намеренно задокументированная ловушка Next.js 16, потому что знания из обучения здесь всё ещё предполагают middleware.ts.
Подробности
Matcher как рычаг производительности
matcher в экспортируемом config важен для производительности: если функция выполняется при КАЖДОМ запросе (включая все статические ресурсы), это стоит лишних вызовов edge-функций — каждый вызов означает дополнительную задержку и на большинстве платформ хостинга дополнительные расходы. Типичный шаблон специально исключает то, что никогда не должно проходить через middleware:
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)",
],
};Внутри matcher компилируется в регулярное выражение и проверяется ПЕРЕД каждым запросом — то есть выполняется, даже если сама функция middleware в итоге не запускается. Статические строки matcher (как выше) производительнее, чем собранные динамически методами массивов, потому что Next.js может полностью проанализировать их во время сборки.
Переписывание, перенаправление или прерывание
Внутри функции middleware есть по сути три способа ответить: NextResponse.next() позволяет запросу продолжиться без изменений, NextResponse.redirect(url) отправляет браузеру настоящее перенаправление HTTP (URL в адресной строке заметно меняется), а NextResponse.rewrite(url) отдаёт содержимое с другого внутреннего URL, чтобы браузер этого не замечал (адресная строка не меняется). Для перенаправлений аутентификации обычно подходит redirect() (пользователь должен видеть, что попадает, например, на /login), а rewrite() лучше подходит для внутреннего сопоставления со статическими экспортами или вариантами A/B-тестов.
Взаимодействие с rewrites() из next.config.ts
Важный, легко упускаемый момент: rewrites() из next.config.ts и middleware выполняются на разных фазах обработки запроса — если путь через rewrite указывает на совершенно другое самостоятельное статическое содержимое (например, заранее собранный статический экспорт сайта), а matcher middleware всё ещё его покрывает, ВСЕ загружаемые затем ресурсы этой страницы (CSS, JS, JSON) по отдельности проходят через ту же логику middleware — при глобально применяемом ограничении частоты запросов это легко превышает лимит для одного «просмотра страницы», потому что учитываются десятки отдельных запросов вместо одного. Для таких путей тогда нужно явное исключение в matcher или в собственной логике middleware.
Пределы среды выполнения edge
Middleware/proxy.ts по умолчанию выполняется в среде edge, а не в полноценной среде Node.js — многие API Node (например, нативный доступ fs к файловой системе, некоторые драйверы баз данных) там недоступны. Это одна из причин, почему проверки аутентификации в proxy.ts обычно выполняют лишь лёгкую проверку «cookie есть» (вместо настоящего запроса к базе данных), тогда как настоящая полная проверка сеанса по-прежнему происходит в обычном серверном компоненте или обработчике маршрута с полноценной средой Node.js.
См. также: Next.js App Router, Ограничение частоты запросов, Открытое перенаправление