플러그인

플러그인 (Plugins)

Vue 앱이 만들어지는 시점에 Vue 플러그인이나 전역 헬퍼, 커스텀 디렉티브 같은 것을 연결하고 싶을 때가 있어요. Nuxt는 이걸 위한 플러그인 시스템을 기본으로 제공해서, app/plugins/ 디렉터리에 파일을 두면 Vue 애플리케이션 생성 시점에 자동으로 로드해 줘요.

출처: 공식문서

본문

app/plugins/ 디렉터리의 모든 파일은 자동으로 등록되기 때문에 nuxt.config에 따로 추가할 필요가 없어요. 파일 이름에 .server 또는 .client 접미사를 붙이면 해당 플러그인을 서버 쪽 또는 클라이언트 쪽에서만 로드할 수 있어요.

등록되는 플러그인

디렉터리 최상위 파일(또는 하위 디렉터리의 index 파일)만 플러그인으로 자동 등록돼요.

-| plugins/
---| foo.ts      // 스캔됨
---| bar/
-----| baz.ts    // 스캔 안 됨
-----| foz.vue   // 스캔 안 됨
-----| index.ts  // 현재 스캔되지만 deprecated

이 경우 foo.tsbar/index.ts만 등록돼요. 하위 디렉터리에 플러그인을 추가하려면 nuxt.config.tsapp/plugins 옵션을 쓰면 돼요.

// nuxt.config.ts
export default defineNuxtConfig({
  plugins: [
    '~/plugins/bar/baz',
    '~/plugins/bar/foz',
  ],
})

플러그인 만들기

플러그인에 전달되는 인자는 nuxtApp 하나뿐이에요.

// plugins/hello.ts
export default defineNuxtPlugin((nuxtApp) => {
  // nuxtApp 으로 무언가 하기
})

객체 문법 플러그인

더 고급 사용을 위해 객체 문법으로도 정의할 수 있어요. 다만 객체 문법을 쓰면 속성이 정적으로 분석되어 더 최적화된 빌드를 만들어 주기 때문에, 런타임에서 정의하면 안 돼요. 예를 들어 enforce: import.meta.server ? 'pre' : 'post'처럼 쓰면 앞으로의 최적화를 망칠 수 있어요.

// plugins/hello.ts
export default defineNuxtPlugin({
  name: 'my-plugin',
  enforce: 'pre', // 또는 'post'
  async setup (nuxtApp) {
    // 일반 함수형 플러그인과 동일
  },
  hooks: {
    // 여기서 Nuxt 앱 런타임 훅을 직접 등록할 수 있어요
    'app:created' () {
      const nuxtApp = useNuxtApp()
      // 훅에서 무언가 하기
    },
  },
  env: {
    // 서버 전용 또는 island 컴포넌트를 렌더링할 때 플러그인이 돌지 않게 하려면 false 로 설정
    islands: true,
  },
})

등록 순서

파일 이름 앞에 'alphabetical' 번호를 붙이면 플러그인이 등록되는 순서를 제어할 수 있어요. 어떤 플러그인이 다른 플러그인에 의존하는 상황일 때 유용해요.

plugins/
 | - 01.myPlugin.ts
 | - 02.myOtherPlugin.ts

여기서 02.myOtherPlugin.ts01.myPlugin.ts가 주입한 모든 것에 접근할 수 있어요. 'alphabetical' 번호에 익숙하지 않다면, 파일 이름이 숫자가 아니라 문자열로 정렬된다는 점을 기억하세요. 예를 들어 10.myPlugin.ts2.myOtherPlugin.ts보다 먼저 옵니다. 그래서 예시처럼 한 자리 숫자 앞에 0을 붙이는 거예요.

로딩 전략

병렬 플러그인

기본적으로 Nuxt는 플러그인을 순차적으로 로드해요. 어떤 플러그인을 병렬로 정의하면 그 플러그인의 실행이 끝나기를 기다리지 않고 다음 플러그인을 로드해요.

// plugins/my-plugin.ts
export default defineNuxtPlugin({
  name: 'my-plugin',
  parallel: true,
  async setup (nuxtApp) {
    // 다음 플러그인이 즉시 실행됨
  },
})

의존성이 있는 플러그인

플러그인이 다른 플러그인이 끝나기를 기다려야 한다면, 그 플러그인의 이름을 dependsOn 배열에 추가하면 돼요.

// plugins/depending-on-my-plugin.ts
export default defineNuxtPlugin({
  name: 'depends-on-my-plugin',
  dependsOn: ['my-plugin'],
  async setup (nuxtApp) {
    // 이 플러그인은 `my-plugin` 의 실행이 끝나기를 기다린 뒤 실행됨
  },
})

컴포저블 사용하기

플러그인 안에서도 컴포저블과 유틸을 쓸 수 있어요. 다만 몇 가지 제약이 있어요.

  • 나중에 등록되는 다른 플러그인에 의존하는 컴포저블은 동작하지 않을 수 있어요. 플러그인은 순서대로, 그리고 다른 것보다 먼저 호출되기 때문이에요
  • Vue.js 라이프사이클에 의존하는 컴포저블은 동작하지 않아요. Vue 컴포저블은 보통 현재 컴포넌트 인스턴스에 묶이는데, 플러그인은 nuxtApp 인스턴스에만 묶이거든요
// app/plugins/hello.ts
export default defineNuxtPlugin((nuxtApp) => {
  const foo = useFoo()
})

헬퍼 제공하기

NuxtApp 인스턴스에 헬퍼를 제공하고 싶다면, 플러그인에서 provide 키 아래로 돌려주면 돼요.

// plugins/hello.ts
export default defineNuxtPlugin(() => {
  return {
    provide: {
      hello: (msg: string) => `Hello ${msg}!`,
    },
  }
})

그리고 컴포넌트에서 이렇게 사용해요.

<!-- app/components/Hello.vue -->
<script setup lang="ts">
const { $hello } = useNuxtApp()
</script>

<template>
  <div>
    {{ $hello('world') }}
  </div>
</template>

헬퍼 제공보다는 컴포저블을 쓰는 걸 강력히 권장해요. 전역 네임스페이스를 오염시키지 않고 메인 번들 엔트리를 작게 유지할 수 있거든요. 또 플러그인이 refcomputed를 제공하면 컴포넌트 <template>에서 풀리지(unwrap) 않는다는 점도 알아두세요.

플러그인 타입

플러그인에서 헬퍼를 반환하면 자동으로 타입이 잡혀요. useNuxtApp()의 반환과 템플릿 안에서 타입을 확인할 수 있죠. 고급 케이스라면 주입된 속성의 타입을 이렇게 직접 선언할 수 있어요.

// index.d.ts
declare module '#app' {
  interface NuxtApp {
    $hello (msg: string): string
  }
}

declare module 'vue' {
  interface ComponentCustomProperties {
    $hello (msg: string): string
  }
}

export {}

Vue 플러그인

vue-gtag 같은 Vue 플러그인을 사용하려면 Nuxt 플러그인으로 연결하면 돼요. 먼저 의존성을 설치하고,

npm install --save-dev vue-gtag-next

플러그인 파일을 만들어요.

// app/plugins/vue-gtag.client.ts
import VueGtag, { trackRouter } from 'vue-gtag-next'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(VueGtag, {
    property: {
      id: 'GA_MEASUREMENT_ID',
    },
  })
  trackRouter(useRouter())
})

Vue 디렉티브

비슷한 방식으로 커스텀 Vue 디렉티브도 플러그인에서 등록할 수 있어요.

// plugins/my-directive.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.directive('focus', {
    mounted (el) {
      el.focus()
    },
    getSSRProps (binding, vnode) {
      // 여기서 SSR 전용 props 를 제공할 수 있어요
      return {}
    },
  })
})

Vue 디렉티브를 등록했다면, 한쪽에서만 쓸 게 아니라면 서버·클라이언트 양쪽 모두에 등록해야 해요. 디렉티브가 클라이언트에서만 의미가 있다면 ~/plugins/my-directive.client.ts로 옮기고 서버용 ~/plugins/my-directive.server.ts에 '스텁' 디렉티브를 제공하면 돼요.

더 알아보기

  • nuxtAppuseNuxtApp()의 상세 내용은 API 문서를 참고하세요
  • Vue 커스텀 디렉티브에 대한 더 자세한 내용은 Vue 공식 문서의 Custom Directives를 참고하세요