middleware

Nuxt предоставляет middleware для запуска кода перед переходом по заданному маршруту.

Nuxt даёт настраиваемую middleware маршрута (в терминах Vue Router — хуки навигации): код, который выполняется перед переходом на маршрут.

Существует три вида middleware для маршрутов:

  1. Анонимная (или встроенная) middleware маршрута определяется непосредственно на странице.
  2. Именованная middleware маршрута, лежащая в middleware/ и подгружаемая асинхронным импортом при использовании на странице.
  3. Глобальная middleware маршрута в middleware/ с суффиксом .global, выполняемая при каждом изменении маршрута.

Первые два вида middleware можно определить в definePageMeta.

Имена middleware нормализуются до kebab-case: myMiddleware становится my-middleware.
Middleware маршрута запускается в части Vue-приложения Nuxt. Несмотря на похожее название, они полностью отличаются от серверных middleware, которые запускаются в части сервера приложения Nitro.

Использование

Middleware маршрута — это хуки навигации: им передаются текущий и целевой маршрут.

middleware/my-middleware.ts
export default defineNuxtRouteMiddleware((to, from) => {
  if (to.params.id === '1') {
    return abortNavigation()
  }
  // В реальном приложении вы, вероятно, не будете перенаправлять каждый маршрут на `/`,
  // однако важно проверить `to.path` перед перенаправлением, иначе вы
  // можете получить бесконечный цикл редиректа
  if (to.path !== '/') {
    return navigateTo('/')
  }
})

Nuxt предоставляет два глобальных хелпера, которые можно вернуть из middleware:

  1. navigateTo — перенаправление на указанный маршрут.
  2. abortNavigation — отмена навигации, опционально с ошибкой.

В отличие от навигационных гвардов в vue-router, третий аргумент next() не передаётся, а редирект и отмена навигации задаются возвращаемым значением.

Возможные возвращаемые значения:

  • ничего (простой return или отсутствие возврата вообще) - не блокирует навигацию и переходит к следующей функции middleware, если таковая имеется, или завершает навигацию по маршруту
  • return navigateTo('/') — перенаправляет по указанному пути; на сервере по умолчанию используется код 302 Found
  • return navigateTo('/', { redirectCode: 301 }) — перенаправление с кодом 301 Moved Permanently
  • return abortNavigation() - останавливает текущую навигацию
  • return abortNavigation(error) - отклоняет текущую навигацию с ошибкой
Узнать больше Docs > 3 X > API > Utils > Navigate To.
Узнать больше Docs > 3 X > API > Utils > Abort Navigation.
Для редиректов и остановки навигации лучше использовать перечисленные выше хелперы. Иные варианты возврата из документации vue-router могут сработать, но без гарантий совместимости в будущем.

Порядок middleware

Middleware работают в следующем порядке:

  1. Глобальные middleware
  2. Порядок middleware, определяемый страницей (если несколько middleware объявлены массивом)

Например, предположим, что у вас есть следующие middleware и компонент:

middleware/ directory
-| middleware/
---| analytics.global.ts
---| setup.global.ts
---| auth.ts
pages/profile.vue
<script setup lang="ts">
definePageMeta({
  middleware: [
    function (to, from) {
      // Пользовательская middleware
    },
    'auth',
  ],
})
</script>

Можно ожидать, что middleware будут запущены в следующем порядке:

  1. analytics.global.ts
  2. setup.global.ts
  3. Пользовательская встроенная middleware
  4. auth.ts

Порядок глобальных middleware

По умолчанию глобальные middleware выполняются в алфавитном порядке на основе имени файла.

Однако иногда нужен явный порядок. Например, setup.global.ts должен выполняться раньше analytics.global.ts. Тогда задайте глобальным middleware префикс с «алфавитной» нумерацией.

Directory structure
-| middleware/
---| 01.setup.global.ts
---| 02.analytics.global.ts
---| auth.ts
Если вы новичок в «алфавитной» нумерации, помните, что имена файлов сортируются как строки, а не как числовые значения. Например, 10.new.global.ts будет предшествовать 2.new.global.ts. Вот почему в примере номера из одной цифры имеют префикс 0.

Когда запускаются middleware

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

Однако, если вы хотите избежать такого поведения, вы можете сделать следующее:

middleware/example.ts
export default defineNuxtRouteMiddleware((to) => {
  // пропустить middleware на сервере
  if (import.meta.server) {
    return
  }
  // полностью пропустить middleware на стороне клиента
  if (import.meta.client) {
    return
  }
  // или пропустить middleware только при начальной загрузке клиента
  const nuxtApp = useNuxtApp()
  if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) {
    return
  }
})

Так происходит даже если на сервере в middleware выброшена ошибка и отрендерена страница ошибки: middleware всё равно выполнится снова в браузере.

Отрисовка страницы ошибки — это совершенно отдельная загрузка страницы, что означает, что любая зарегистрированная middleware будет запущена снова. Вы можете использовать useError в middleware, чтобы проверить, обрабатывается ли ошибка.

Доступ к маршруту в middleware

Всегда используйте параметры to и from в middleware для доступа к следующему и предыдущему маршруту. Избегайте композабла useRoute() в этом контексте. В middleware нет понятия «текущего маршрута»: навигация может быть отменена или перенаправлена. В таком контексте useRoute() будет давать неверные данные.

Иногда вы вызываете композабл, который внутри использует useRoute(), из‑за чего предупреждение появляется даже без прямого вызова в middleware. Это приводит к той же проблеме, поэтому передавайте маршрут аргументом в функции, которые вызываются из middleware.
// @errors: 2304
export default defineNuxtRouteMiddleware((to) => {
  // передаём маршрут в функцию, чтобы не вызывать `useRoute()` внутри middleware
  doSomethingWithRoute(to)

  // ❌ так появится предупреждение и это не рекомендуется
  callsRouteInternally()
})

Динамическое добавление middleware

Глобальную или именованную middleware маршрута можно добавить вручную с помощью вспомогательной функции addRouteMiddleware(), например, из плагина.

export default defineNuxtPlugin(() => {
  addRouteMiddleware('global-test', () => {
    console.log('эта глобальная middleware была добавлена в плагин и будет запускаться при каждом изменении маршрута')
  }, { global: true })

  addRouteMiddleware('named-test', () => {
    console.log('эта именованная middleware была добавлена в плагин и переопределит любую существующую middleware с тем же именем')
  })
})

Пример

Directory Structure
-| middleware/
---| auth.ts

В файле страницы вы можете сослаться на эту middleware маршрута:

<script setup lang="ts">
definePageMeta({
  middleware: ['auth'],
  // или middleware: 'auth'
})
</script>

Теперь, прежде чем переход на эту страницу сможет быть завершен, будет запущена middleware маршрута auth.

Прочитайте и отредактируйте живой пример в Docs > 3 X > Examples > Routing > Middleware.

Настройка middleware во время сборки

Вместо использования definePageMeta на каждой странице, вы можете добавить именованную middleware маршрута в хуке pages:extend.

nuxt.config.ts
import type { NuxtPage } from 'nuxt/schema'

export default defineNuxtConfig({
  hooks: {
    'pages:extend' (pages) {
      function setMiddleware (pages: NuxtPage[]) {
        for (const page of pages) {
          if (/* некоторое условие */ Math.random() > 0.5) {
            page.meta ||= {}
            // Обратите внимание, что это переопределит любые middleware, заданные в `definePageMeta` на странице.
            page.meta.middleware = ['named']
          }
          if (page.children) {
            setMiddleware(page.children)
          }
        }
      }
      setMiddleware(pages)
    },
  },
})