nuxt logo

문서 번역(비공식)

components

components/ 디렉토리는 모든 Vue 컴포넌트를 저장하는 곳입니다.

Nuxt는 이 디렉토리 내의 모든 컴포넌트(사용 중인 모듈에 의해 등록된 컴포넌트와 함께)를 자동으로 가져옵니다.

Directory Structure
-| components/
---| AppHeader.vue
---| AppFooter.vue
app.vue
<template>
  <div>
    <AppHeader />
    <NuxtPage />
    <AppFooter />
  </div>
</template>

컴포넌트 이름

중첩된 디렉토리에 컴포넌트가 있는 경우:

Directory Structure
-| components/
---| base/
-----| foo/
-------| Button.vue

... 컴포넌트의 이름은 경로 디렉토리와 파일 이름을 기반으로 하며, 중복된 세그먼트는 제거됩니다. 따라서 컴포넌트의 이름은 다음과 같습니다:

<BaseFooButton />

명확성을 위해 컴포넌트의 파일 이름이 그 이름과 일치하도록 권장합니다. 따라서 위의 예에서 Button.vueBaseFooButton.vue로 이름을 변경할 수 있습니다.

경로가 아닌 이름만을 기반으로 컴포넌트를 자동으로 가져오고 싶다면, 구성 객체의 확장된 형태를 사용하여 pathPrefix 옵션을 false로 설정해야 합니다:

nuxt.config.ts
export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
      pathPrefix: false, // [!code ++]
    },
  ],
});

이렇게 하면 Nuxt 2에서 사용된 것과 동일한 전략을 사용하여 컴포넌트를 등록합니다. 예를 들어, ~/components/Some/MyComponent.vue<MyComponent>로 사용 가능하며 <SomeMyComponent>로 사용되지 않습니다.

동적 컴포넌트

Vue <component :is="someComputedComponent"> 구문을 사용하려면, Vue에서 제공하는 resolveComponent 헬퍼를 사용하거나 #components에서 컴포넌트를 직접 가져와 is prop에 전달해야 합니다.

예를 들어:

pages/index.vue
<script setup lang="ts">
import { SomeComponent } from '#components'

const MyButton = resolveComponent('MyButton')
</script>

<template>
  <component :is="clickable ? MyButton : 'div'" />
  <component :is="SomeComponent" />
</template>

동적 컴포넌트를 처리하기 위해 resolveComponent를 사용하는 경우, 컴포넌트의 이름 외에는 아무것도 삽입하지 않도록 주의하세요. 이는 리터럴 문자열이어야 하며 변수가 아니어야 합니다. 문자열은 컴파일 단계에서 정적으로 분석됩니다.

대안으로, 권장되지는 않지만, 모든 컴포넌트를 전역적으로 등록할 수 있으며, 이는 모든 컴포넌트에 대해 비동기 청크를 생성하고 애플리케이션 전체에서 사용할 수 있게 합니다.

  export default defineNuxtConfig({
    components: {
+     global: true,
+     dirs: ['~/components']
    },
  })

또한 ~/components/global 디렉토리에 일부 컴포넌트를 배치하거나 파일 이름에 .global.vue 접미사를 사용하여 선택적으로 일부 컴포넌트를 전역적으로 등록할 수 있습니다. 위에서 언급했듯이, 각 전역 컴포넌트는 별도의 청크로 렌더링되므로 이 기능을 과도하게 사용하지 않도록 주의하세요.

global 옵션은 컴포넌트 디렉토리별로 설정할 수도 있습니다.

동적 가져오기

컴포넌트를 동적으로 가져오려면(컴포넌트를 지연 로드하는 것으로도 알려져 있음) 컴포넌트 이름에 Lazy 접두사를 추가하기만 하면 됩니다. 이는 컴포넌트가 항상 필요한 것은 아닐 때 특히 유용합니다.

Lazy 접두사를 사용하면 적절한 순간까지 컴포넌트 코드를 로드하는 것을 지연시킬 수 있으며, 이는 JavaScript 번들 크기를 최적화하는 데 도움이 될 수 있습니다.

pages/index.vue
<script setup lang="ts">
const show = ref(false)
</script>

<template>
  <div>
    <h1>Mountains</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="show = true">Show List</button>
  </div>
</template>

지연된(또는 Lazy) 하이드레이션

Lazy 컴포넌트는 앱의 청크 크기를 제어하는 데 유용하지만, 조건부로 렌더링되지 않는 한 런타임 성능을 항상 향상시키지는 않습니다. 실제 애플리케이션에서는 일부 페이지에 많은 콘텐츠와 많은 컴포넌트가 포함될 수 있으며, 대부분의 경우 페이지가 로드되자마자 모든 컴포넌트가 상호작용할 필요는 없습니다. 모든 컴포넌트를 즉시 로드하면 성능에 부정적인 영향을 미칠 수 있습니다.

앱을 최적화하려면 더 중요한 작업이 완료될 때까지 또는 컴포넌트가 보일 때까지 일부 컴포넌트의 하이드레이션을 지연시키고 싶을 수 있습니다.

Nuxt는 lazy(또는 지연된) 하이드레이션을 사용하여 컴포넌트가 상호작용 가능해지는 시점을 제어할 수 있도록 지원합니다.

하이드레이션 전략

Nuxt는 다양한 내장 하이드레이션 전략을 제공합니다. lazy 컴포넌트당 하나의 전략만 사용할 수 있습니다.

현재 Nuxt의 내장 lazy 하이드레이션은 단일 파일 컴포넌트(SFC)에서만 작동하며, 템플릿에서 prop을 정의해야 합니다(v-bind를 통해 prop 객체를 확산하는 대신). 또한 #components에서 직접 가져오는 경우에는 작동하지 않습니다.

hydrate-on-visible

컴포넌트가 뷰포트에 보일 때 하이드레이션합니다.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible />
  </div>
</template>
이것도 참고 IntersectionObserver 옵션

이것은 Vue의 내장 hydrateOnVisible 전략을 사용합니다.

hydrate-on-idle

브라우저가 유휴 상태일 때 컴포넌트를 하이드레이션합니다. 이는 컴포넌트를 가능한 빨리 로드해야 하지만 중요한 렌더링 경로를 차단하지 않아야 할 때 적합합니다.

최대 타임아웃으로 작용하는 숫자를 전달할 수도 있습니다.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-idle />
  </div>
</template>

이것은 Vue의 내장 hydrateOnIdle 전략을 사용합니다.

hydrate-on-interaction

지정된 상호작용(예: 클릭, 마우스오버) 후에 컴포넌트를 하이드레이션합니다.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-interaction="mouseover" />
  </div>
</template>

이벤트나 이벤트 목록을 전달하지 않으면 기본적으로 pointerenterfocus에서 하이드레이션됩니다.

이것은 Vue의 내장 hydrateOnInteraction 전략을 사용합니다.

hydrate-on-media-query

윈도우가 미디어 쿼리와 일치할 때 컴포넌트를 하이드레이션합니다.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
  </div>
</template>

이것은 Vue의 내장 hydrateOnMediaQuery 전략을 사용합니다.

hydrate-after

지정된 지연 시간(밀리초) 후에 컴포넌트를 하이드레이션합니다.

pages/index.vue
<template>
  <div>
    <LazyMyComponent :hydrate-after="2000" />
  </div>
</template>

hydrate-when

불리언 조건에 따라 컴포넌트를 하이드레이션합니다.

pages/index.vue
<template>
  <div>
    <LazyMyComponent :hydrate-when="isReady" />
  </div>
</template>
<script setup lang="ts">
const isReady = ref(false)
function myFunction() {
  // 사용자 정의 하이드레이션 전략을 트리거합니다...
  isReady.value = true
}
</script>

hydrate-never

컴포넌트를 절대 하이드레이션하지 않습니다.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-never />
  </div>
</template>

하이드레이션 이벤트 수신

모든 지연된 하이드레이션 컴포넌트는 하이드레이션될 때 @hydrated 이벤트를 발생시킵니다.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible @hydrated="onHydrate" />
  </div>
</template>

<script setup lang="ts">
function onHydrate() {
  console.log("컴포넌트가 하이드레이션되었습니다!")
}
</script>

주의사항 및 모범 사례

지연된 하이드레이션은 성능 이점을 제공할 수 있지만, 올바르게 사용하는 것이 중요합니다:

  1. 뷰포트 내 콘텐츠 우선순위 지정: 중요한, 접히기 전의 콘텐츠에 대해 지연된 하이드레이션을 피하세요. 즉시 필요하지 않은 콘텐츠에 가장 적합합니다.

  2. 조건부 렌더링: lazy 컴포넌트에서 v-if="false"를 사용할 때, 지연된 하이드레이션이 필요하지 않을 수 있습니다. 일반적인 lazy 컴포넌트를 사용할 수 있습니다.

  3. 공유 상태: 여러 컴포넌트 간에 공유 상태(v-model)를 주의 깊게 관리하세요. 한 컴포넌트에서 모델을 업데이트하면 해당 모델에 바인딩된 모든 컴포넌트에서 하이드레이션이 트리거될 수 있습니다.

  4. 각 전략의 의도된 사용 사례 사용: 각 전략은 특정 목적에 최적화되어 있습니다.

    • hydrate-when은 항상 하이드레이션이 필요하지 않을 수 있는 컴포넌트에 가장 적합합니다.
    • hydrate-after는 특정 시간 동안 기다릴 수 있는 컴포넌트에 적합합니다.
    • hydrate-on-idle은 브라우저가 유휴 상태일 때 하이드레이션할 수 있는 컴포넌트에 적합합니다.
  5. 상호작용 컴포넌트에 hydrate-never 사용 피하기: 사용자가 상호작용해야 하는 컴포넌트는 절대 하이드레이션되지 않도록 설정해서는 안 됩니다.

직접 가져오기

Nuxt의 자동 가져오기 기능을 우회하고 싶거나 필요할 경우 #components에서 컴포넌트를 명시적으로 가져올 수도 있습니다.

pages/index.vue
<script setup lang="ts">
import { NuxtLink, LazyMountainsList } from '#components'

const show = ref(false)
</script>

<template>
  <div>
    <h1>Mountains</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="show = true">Show List</button>
    <NuxtLink to="/">Home</NuxtLink>
  </div>
</template>

사용자 정의 디렉토리

기본적으로 ~/components 디렉토리만 스캔됩니다. 다른 디렉토리를 추가하거나 이 디렉토리의 하위 폴더 내에서 컴포넌트를 스캔하는 방법을 변경하려면 구성에 추가 디렉토리를 추가할 수 있습니다:

nuxt.config.ts
export default defineNuxtConfig({
  components: [
    // ~/calendar-module/components/event/Update.vue => <EventUpdate />
    { path: '~/calendar-module/components' },

    // ~/user-module/components/account/UserDeleteDialog.vue => <UserDeleteDialog />
    { path: '~/user-module/components', pathPrefix: false },

    // ~/components/special-components/Btn.vue => <SpecialBtn />
    { path: '~/components/special-components', prefix: 'Special' },

    // `~/components`의 하위 디렉토리에 적용할 오버라이드를 원하는 경우 마지막에 오는 것이 중요합니다.
    //
    // ~/components/Btn.vue => <Btn />
    // ~/components/base/Btn.vue => <BaseBtn />
    '~/components'
  ]
})

모든 중첩 디렉토리는 먼저 추가되어야 하며, 순서대로 스캔됩니다.

npm 패키지

npm 패키지에서 컴포넌트를 자동으로 가져오고 싶다면, 로컬 모듈에서 addComponent를 사용하여 등록할 수 있습니다.

import { addComponent, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    // import { MyComponent as MyAutoImportedComponent } from 'my-npm-package'
    addComponent({
      name: 'MyAutoImportedComponent',
      export: 'MyComponent',
      filePath: 'my-npm-package',
    })
  },
})

컴포넌트 확장

기본적으로, nuxt.config.ts의 extensions 키에 지정된 확장자를 가진 모든 파일은 컴포넌트로 처리됩니다. 컴포넌트로 등록할 파일 확장자를 제한해야 하는 경우, 컴포넌트 디렉토리 선언의 확장된 형태와 extensions 키를 사용할 수 있습니다:

nuxt.config.ts
export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
      extensions: ['.vue'], // [!code ++]
    }
  ]
})

클라이언트 컴포넌트

컴포넌트가 클라이언트 측에서만 렌더링되어야 하는 경우, 컴포넌트에 .client 접미사를 추가할 수 있습니다.

Directory Structure
| components/
--| Comments.client.vue
pages/example.vue
<template>
  <div>
    <!-- 이 컴포넌트는 클라이언트 측에서만 렌더링됩니다 -->
    <Comments />
  </div>
</template>

이 기능은 Nuxt 자동 가져오기 및 #components 가져오기에만 작동합니다. 이러한 컴포넌트를 실제 경로에서 명시적으로 가져오는 것은 클라이언트 전용 컴포넌트로 변환하지 않습니다.

.client 컴포넌트는 마운트된 후에만 렌더링됩니다. onMounted()를 사용하여 렌더링된 템플릿에 접근하려면, onMounted() 훅의 콜백에서 await nextTick()을 추가하세요.

이것도 참고 api > components > client-only

서버 컴포넌트

서버 컴포넌트를 사용하면 클라이언트 측 앱 내에서 개별 컴포넌트를 서버 렌더링할 수 있습니다. 정적 사이트를 생성하는 경우에도 Nuxt 내에서 서버 컴포넌트를 사용할 수 있습니다. 이를 통해 동적 컴포넌트, 서버 렌더링된 HTML 및 정적 마크업 청크를 혼합하여 복잡한 사이트를 구축할 수 있습니다.

서버 컴포넌트는 독립적으로 사용하거나 클라이언트 컴포넌트와 함께 사용할 수 있습니다.

Nuxt 서버 컴포넌트에 대한 Daniel Roe의 가이드를 읽어보세요.

독립형 서버 컴포넌트

독립형 서버 컴포넌트는 항상 서버에서 렌더링되며, Islands 컴포넌트로도 알려져 있습니다.

이들의 props가 업데이트되면, 네트워크 요청이 발생하여 렌더링된 HTML이 제자리에서 업데이트됩니다.

서버 컴포넌트는 현재 실험적이며, 사용하려면 nuxt.config에서 'component islands' 기능을 활성화해야 합니다:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    componentIslands: true
  }
})

이제 .server 접미사를 사용하여 서버 전용 컴포넌트를 등록하고 애플리케이션 어디에서나 자동으로 사용할 수 있습니다.

Directory Structure
-| components/
---| HighlightedMarkdown.server.vue
pages/example.vue
<template>
  <div>
    <!--
      이는 자동으로 서버에서 렌더링되며, 마크다운 파싱 + 하이라이팅 라이브러리가 클라이언트 번들에 포함되지 않습니다.
     -->
    <HighlightedMarkdown markdown="# Headline" />
  </div>
</template>

서버 전용 컴포넌트는 <NuxtIsland>를 내부적으로 사용하므로, lazy prop과 #fallback 슬롯이 모두 전달됩니다.

서버 컴포넌트(및 islands)는 단일 루트 요소를 가져야 합니다. (HTML 주석도 요소로 간주됩니다.)

Props는 URL 쿼리 매개변수를 통해 서버 컴포넌트에 전달되므로, URL의 가능한 길이에 의해 제한되므로 서버 컴포넌트에 props를 통해 엄청난 양의 데이터를 전달하지 않도록 주의하세요.

다른 islands 내에 islands를 중첩할 때는 각 island가 추가적인 오버헤드를 추가하므로 주의하세요.

서버 전용 컴포넌트 및 island 컴포넌트의 대부분의 기능, 예를 들어 슬롯 및 클라이언트 컴포넌트는 단일 파일 컴포넌트에서만 사용할 수 있습니다.

서버 컴포넌트 내의 클라이언트 컴포넌트

이 기능은 구성에서 experimental.componentIslands.selectiveClient가 true여야 합니다.

nuxt-client 속성을 클라이언트 측에서 로드되기를 원하는 컴포넌트에 설정하여 컴포넌트를 부분적으로 하이드레이션할 수 있습니다.

components/ServerWithClient.vue
<template>
  <div>
    <HighlightedMarkdown markdown="# Headline" />
    <!-- Counter는 클라이언트 측에서 로드되고 하이드레이션됩니다 -->
    <Counter nuxt-client :count="5" />
  </div>
</template>

이는 서버 컴포넌트 내에서만 작동합니다. 클라이언트 컴포넌트의 슬롯은 experimental.componentIsland.selectiveClient'deep'으로 설정된 경우에만 작동하며, 서버 측에서 렌더링되므로 클라이언트 측에서는 상호작용할 수 없습니다.

서버 컴포넌트 컨텍스트

서버 전용 또는 island 컴포넌트를 렌더링할 때, <NuxtIsland>NuxtIslandResponse를 반환하는 fetch 요청을 만듭니다. (서버에서 렌더링되는 경우 내부 요청이며, 클라이언트 측 탐색에서 렌더링되는 경우 네트워크 탭에서 볼 수 있는 요청입니다.)

이는 다음을 의미합니다:

  • NuxtIslandResponse를 생성하기 위해 서버 측에서 새로운 Vue 앱이 생성됩니다.
  • 컴포넌트를 렌더링하는 동안 새로운 'island 컨텍스트'가 생성됩니다.
  • 나머지 앱의 'island 컨텍스트'에 접근할 수 없으며, island 컴포넌트의 컨텍스트에 접근할 수 없습니다. 즉, 서버 컴포넌트 또는 island는 앱의 나머지 부분과 _격리_되어 있습니다.
  • 플러그인은 island를 렌더링할 때 다시 실행되며, env: { islands: false }가 설정된 경우에는 실행되지 않습니다(객체 구문 플러그인에서 설정할 수 있습니다).

island 컴포넌트 내에서 nuxtApp.ssrContext.islandContext를 통해 island 컨텍스트에 접근할 수 있습니다. island 컴포넌트가 여전히 실험적이므로, 이 컨텍스트의 형식은 변경될 수 있습니다.

슬롯은 상호작용할 수 있으며, display: contents;가 있는 <div>로 래핑됩니다.

클라이언트 컴포넌트와 쌍을 이루는 경우

이 경우, .server + .client 컴포넌트는 컴포넌트의 두 '절반'이며, 서버 및 클라이언트 측에서 컴포넌트의 별도 구현을 위한 고급 사용 사례에 사용할 수 있습니다.

Directory Structure
-| components/
---| Comments.client.vue
---| Comments.server.vue
pages/example.vue
<template>
  <div>
    <!-- 이 컴포넌트는 서버에서 Comments.server를 렌더링한 후 브라우저에서 마운트되면 Comments.client를 렌더링합니다 -->
    <Comments />
  </div>
</template>

내장 Nuxt 컴포넌트

Nuxt는 <ClientOnly><DevOnly>를 포함한 여러 컴포넌트를 제공합니다. API 문서에서 더 읽어볼 수 있습니다.

이것도 참고 api

라이브러리 작성자

자동 트리 셰이킹 및 컴포넌트 등록이 가능한 Vue 컴포넌트 라이브러리를 만드는 것은 매우 쉽습니다. ✨

@nuxt/kit에서 제공하는 addComponentsDir 메서드를 사용하여 Nuxt 모듈에서 컴포넌트 디렉토리를 등록할 수 있습니다.

다음과 같은 디렉토리 구조를 상상해보세요:

Directory Structure
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.ts
-| pages/
---| index.vue
-| nuxt.config.ts

그런 다음 awesome-ui/nuxt.ts에서 addComponentsDir 훅을 사용할 수 있습니다:

import { createResolver, defineNuxtModule, addComponentsDir } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    const resolver = createResolver(import.meta.url)

    // ./components 디렉토리를 목록에 추가합니다
    addComponentsDir({
      path: resolver.resolve('./components'),
      prefix: 'awesome',
    })
  },
})

이게 전부입니다! 이제 프로젝트에서 nuxt.config 파일에 Nuxt 모듈로 UI 라이브러리를 가져올 수 있습니다:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['awesome-ui/nuxt']
})

... 그리고 pages/index.vue에서 모듈 컴포넌트(접두사 awesome-이 붙은)를 직접 사용할 수 있습니다:

<template>
  <div>
    My <AwesomeButton>UI button</AwesomeButton>!
    <awesome-alert>Here's an alert!</awesome-alert>
  </div>
</template>

사용된 경우에만 컴포넌트를 자동으로 가져오며, node_modules/awesome-ui/components/에서 컴포넌트를 업데이트할 때 HMR도 지원합니다.

샘플 코드 편집 및 미리보기examples > features > auto-imports