middleware

Nuxt는 특정 라우트로 이동하기 전에 코드를 실행하기 위한 미들웨어를 제공합니다.

Nuxt는 애플리케이션 전반에서 사용할 수 있는 라우트 미들웨어 프레임워크를 제공하며, 특정 라우트로 이동하기 전에 실행하고 싶은 코드를 추출하기에 이상적입니다.

라우트 미들웨어에는 세 가지 종류가 있습니다:

  1. 익명(또는 인라인) 라우트 미들웨어는 페이지 안에서 직접 정의됩니다.
  2. 명명된 라우트 미들웨어는 app/middleware/ 에 위치하며, 페이지에서 사용될 때 비동기 import를 통해 자동으로 로드됩니다.
  3. 전역 라우트 미들웨어는 app/middleware/.global 접미사를 붙여 두며, 모든 라우트 변경 시 실행됩니다.

앞의 두 종류의 라우트 미들웨어는 definePageMeta 안에서 정의할 수 있습니다.

미들웨어의 이름은 케밥 케이스(kebab-case)로 정규화됩니다: myMiddlewaremy-middleware 가 됩니다.
라우트 미들웨어는 Nuxt 앱의 Vue 부분 안에서 실행됩니다. 이름은 비슷하지만, 앱의 Nitro 서버 부분에서 실행되는 서버 미들웨어와는 완전히 다릅니다.

Usage

라우트 미들웨어는 내비게이션 가드로, 현재 라우트와 다음 라우트를 인자로 받습니다.

middleware/my-middleware.ts
export default defineNuxtRouteMiddleware((to, from) => {
  if (to.params.id === '1') {
    return abortNavigation()
  }
  // 실제 앱에서는 아마 모든 라우트를 `/` 로 리다이렉트하지 않을 것입니다.
  // 하지만 리다이렉트 전에 `to.path` 를 확인하는 것이 중요합니다.
  // 그렇지 않으면 무한 리다이렉트 루프에 빠질 수 있습니다.
  if (to.path !== '/') {
    return navigateTo('/')
  }
})

Nuxt는 미들웨어에서 직접 반환할 수 있는 두 개의 전역 헬퍼를 제공합니다.

  1. navigateTo - 주어진 라우트로 리다이렉트합니다.
  2. abortNavigation - 내비게이션을 중단하며, 선택적으로 에러 메시지를 전달할 수 있습니다.

vue-routernavigation guards 와는 달리, 세 번째 인자인 next() 는 전달되지 않으며, 리다이렉트나 라우트 취소는 미들웨어에서 값을 반환하는 방식으로 처리됩니다.

가능한 반환 값은 다음과 같습니다:

  • 아무것도 반환하지 않음(단순히 return 하거나 아예 반환하지 않음) - 내비게이션을 막지 않으며, 다음 미들웨어 함수(있다면)로 넘어가거나 라우트 내비게이션을 완료합니다.
  • return navigateTo('/') - 주어진 경로로 리다이렉트하며, 서버 측에서 리다이렉트가 발생하면 리다이렉트 코드를 302 Found 로 설정합니다.
  • return navigateTo('/', { redirectCode: 301 }) - 주어진 경로로 리다이렉트하며, 서버 측에서 리다이렉트가 발생하면 리다이렉트 코드를 301 Moved Permanently 로 설정합니다.
  • return abortNavigation() - 현재 내비게이션을 중단합니다.
  • return abortNavigation(error) - 현재 내비게이션을 에러와 함께 거부합니다.
Read more in Docs > 4 X > API > Utils > Navigate To.
Read more in Docs > 4 X > API > Utils > Abort Navigation.
리다이렉트 수행이나 내비게이션 중단에는 위의 헬퍼 함수들을 사용할 것을 권장합니다. vue-router 문서에 설명된 다른 가능한 반환 값들도 동작할 수 있지만, 향후 브레이킹 체인지가 발생할 수 있습니다.

Middleware Order

미들웨어는 다음 순서로 실행됩니다:

  1. 전역 미들웨어
  2. 페이지에 정의된 미들웨어 순서(배열 문법으로 여러 미들웨어가 선언된 경우)

예를 들어, 다음과 같은 미들웨어와 컴포넌트가 있다고 가정해 봅시다:

app/middleware/ directory
-| middleware/
---| analytics.global.ts
---| setup.global.ts
---| auth.ts
pages/profile.vue
<script setup lang="ts">
definePageMeta({
  middleware: [
    function (to, from) {
      // 커스텀 인라인 미들웨어
    },
    'auth',
  ],
})
</script>

미들웨어는 다음 순서로 실행될 것으로 예상할 수 있습니다:

  1. analytics.global.ts
  2. setup.global.ts
  3. 커스텀 인라인 미들웨어
  4. auth.ts

Ordering Global Middleware

기본적으로 전역 미들웨어는 파일 이름을 기준으로 알파벳 순서대로 실행됩니다.

하지만 특정 순서를 정의하고 싶을 때가 있을 수 있습니다. 예를 들어, 앞의 시나리오에서 setup.global.tsanalytics.global.ts 보다 먼저 실행되어야 할 수 있습니다. 이 경우, 전역 미들웨어에 '알파벳' 번호 접두사를 붙이는 것을 권장합니다.

Directory structure
-| middleware/
---| 01.setup.global.ts
---| 02.analytics.global.ts
---| auth.ts
'알파벳' 번호 매기기가 처음이라면, 파일 이름은 숫자 값이 아니라 문자열로 정렬된다는 점을 기억하세요. 예를 들어, 10.new.global.ts2.new.global.ts 보다 앞에 옵니다. 이 때문에 예제에서는 한 자리 숫자 앞에 0 을 붙였습니다.

When Middleware Runs

사이트가 서버 렌더링되거나 생성된 경우, 초기 페이지에 대한 미들웨어는 페이지가 렌더링될 때 한 번, 그리고 클라이언트에서 다시 한 번 실행됩니다. 이는 미들웨어가 브라우저 환경을 필요로 할 때(예: 사이트가 정적으로 생성되었거나, 응답을 적극적으로 캐시하거나, 로컬 스토리지에서 값을 읽고자 할 때) 필요할 수 있습니다.

하지만 이 동작을 피하고 싶다면 다음과 같이 할 수 있습니다:

middleware/example.ts
export default defineNuxtRouteMiddleware((to) => {
  // 서버에서 미들웨어 건너뛰기
  if (import.meta.server) {
    return
  }
  // 클라이언트 측에서 미들웨어를 완전히 건너뛰기
  if (import.meta.client) {
    return
  }
  // 또는 초기 클라이언트 로드에서만 미들웨어 건너뛰기
  const nuxtApp = useNuxtApp()
  if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) {
    return
  }
})

이는 서버에서 미들웨어 안에서 에러를 던져 에러 페이지가 렌더링되는 경우에도 마찬가지입니다. 미들웨어는 브라우저에서 다시 실행됩니다.

에러 페이지를 렌더링하는 것은 완전히 별도의 페이지 로드이므로, 등록된 모든 미들웨어가 다시 실행됩니다. 미들웨어 안에서 useError 를 사용해 에러가 처리 중인지 확인할 수 있습니다.

Accessing Route in Middleware

미들웨어에서 다음과 이전 라우트에 접근할 때는 항상 tofrom 파라미터를 사용하세요. 이 컨텍스트에서는 useRoute() 컴포저블 사용을 완전히 피해야 합니다. 미들웨어에는 "현재 라우트"라는 개념이 없습니다. 미들웨어는 내비게이션을 중단하거나 다른 라우트로 리다이렉트할 수 있기 때문입니다. 이 컨텍스트에서 useRoute() 컴포저블은 항상 부정확한 값을 반환하게 됩니다.

때때로, 내부적으로 useRoute() 를 사용하는 컴포저블을 호출할 수 있으며, 이 경우 미들웨어에서 직접 호출하지 않았더라도 이 경고가 발생할 수 있습니다. 이는 위에서 설명한 것과 동일한 문제를 야기하므로, 미들웨어에서 사용될 때는 라우트를 인자로 받도록 함수 구조를 잡는 것이 좋습니다.
export default defineNuxtRouteMiddleware((to) => {
  // 미들웨어에서 `useRoute()` 호출을 피하기 위해 라우트를 함수에 전달
  doSomethingWithRoute(to)

  // ❌ 이는 경고를 출력하며 권장되지 않습니다.
  callsRouteInternally()
})

Adding Middleware Dynamically

플러그인 내부 등에서 addRouteMiddleware() 헬퍼 함수를 사용해 전역 또는 명명된 라우트 미들웨어를 수동으로 추가할 수 있습니다.

export default defineNuxtPlugin(() => {
  addRouteMiddleware('global-test', () => {
    console.log('이 전역 미들웨어는 플러그인에서 추가되었으며 모든 라우트 변경 시 실행됩니다.')
  }, { global: true })

  addRouteMiddleware('named-test', () => {
    console.log('이 명명된 미들웨어는 플러그인에서 추가되었으며, 동일한 이름의 기존 미들웨어를 덮어씁니다.')
  })
})

Example

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

페이지 파일에서 이 라우트 미들웨어를 참조할 수 있습니다:

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

이제 해당 페이지로의 내비게이션이 완료되기 전에 auth 라우트 미들웨어가 실행됩니다.

Read and edit a live example in Docs > 4 X > Examples > Routing > Middleware.

Setting Middleware at Build Time

각 페이지에서 definePageMeta 를 사용하는 대신, 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 (/* some condition */ Math.random() > 0.5) {
            page.meta ||= {}
            // 이는 페이지 안의 `definePageMeta` 에서 설정된 어떤 미들웨어도 덮어쓴다는 점에 유의하세요.
            page.meta.middleware = ['named']
          }
          if (page.children) {
            setMiddleware(page.children)
          }
        }
      }
      setMiddleware(pages)
    },
  },
})