components (компоненты)

В директории components/ размещаются Vue-компоненты приложения.

Nuxt автоматически импортирует компоненты из этой директории (вместе с компонентами из модулей).

Структура каталогов
-| components/
---| AppHeader.vue
---| AppFooter.vue
app/app.vue
<template>
  <div>
    <AppHeader />
    <NuxtPage />
    <AppFooter />
  </div>
</template>

Имена компонентов

Для компонентов во вложенных папках имя строится из пути и имени файла, повторяющиеся сегменты убираются:

Структура каталогов
-| components/
---| base/
-----| foo/
-------| Button.vue

Имя компонента: <BaseFooButton />.

Рекомендуется, чтобы имя файла совпадало с именем компонента. В примере выше можно переименовать Button.vue в BaseFooButton.vue.

Чтобы автоимпорт использовал только имя файла без пути, задайте pathPrefix: false в расширенной форме конфига:

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

Как в Nuxt 2: ~/components/Some/MyComponent.vue будет доступен как <MyComponent>, а не <SomeMyComponent>.

Динамические компоненты

Для синтаксиса Vue <component :is="someComputedComponent"> нужен хелпер resolveComponent или импорт из #components с передачей в проп is.

app/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 к имени компонента. Код компонента подгрузится только когда он понадобится.

app/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>

Отложенная (ленивая) гидрация

Ленивые компоненты помогают контролировать размер чанков, но не всегда улучшают производительность в рантайме, если загружаются сразу. Отложенная гидрация позволяет включать интерактивность компонентов только при появлении в зоне видимости или когда браузер освободится.

Стратегии гидрации

У Nuxt есть встроенные стратегии; для одного ленивого компонента можно выбрать только одну.

Любое изменение пропа у компонента с отложенной гидрацией сразу запускает гидрацию (например, смена пропа у компонента с hydrate-never).
Встроенная отложенная гидрация работает только в SFC и требует, чтобы проп был указан в шаблоне (не через v-bind с объектом). С прямым импортом из #components не работает.

hydrate-on-visible

Гидрация при появлении в viewport.

app/pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible />
  </div>
</template>
Узнать больше Опции IntersectionObserver.

hydrate-on-idle

Гидрация в момент простоя браузера. Можно передать число — максимальная задержка в мс.

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

hydrate-on-interaction

Гидрация после указанного события (click, mouseover и т.д.). По умолчанию: pointerenter, click, focus.

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

hydrate-on-media-query

Гидрация при совпадении media query с окном.

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

hydrate-after

Гидрация через указанную задержку (мс).

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

hydrate-when

Гидрация по булевому условию.

app/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

Компонент не гидратируется.

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

Событие гидрации

Компоненты с отложенной гидрацией эмитят @hydrated при гидрации.

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

<script setup lang="ts">
function onHydrate () {
  console.log('Component has been hydrated!')
}
</script>

Ограничения и рекомендации

  1. Приоритет контенту в viewport: не откладывайте гидрацию для критичного контента над сгибом.
  2. Условный рендер: при v-if="false" достаточно обычного ленивого компонента без отложенной гидрации.
  3. Общее состояние: осторожнее с общим состоянием (v-model) между компонентами — обновление может запустить гидрацию у всех.
  4. Стратегии по назначению: каждая стратегия под свою задачу. hydrate-when — для компонентов, которые не всегда нужно гидратировать; hydrate-after — когда можно подождать; hydrate-on-idle — когда можно ждать простоя браузера.
  5. Не используйте hydrate-never для интерактивных компонентов — компонент с взаимодействием пользователя должен гидратироваться.

Прямой импорт

Компоненты можно явно импортировать из #components, обходя автоимпорт.

app/pages/index.vue
<script setup lang="ts">
import { LazyMountainsList, NuxtLink } 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: [
    { path: '~/calendar-module/components' },
    { path: '~/user-module/components', pathPrefix: false },
    { path: '~/components/special-components', prefix: 'Special' },
    '~/components',
  ],
})
Вложенные директории нужно добавлять первыми.

Компоненты из npm

Для автоимпорта компонентов из npm-пакета используйте addComponent в локальном модуле.

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

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

Расширения файлов компонентов

По умолчанию компонентами считаются файлы с расширениями из extensions в nuxt.config. Ограничить можно через extensions в объявлении директории:

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

Клиентские компоненты

Компонент только для клиента: суффикс .client в имени. Он рендерится только после монтирования.

Структура каталогов
| components/
--| Comments.client.vue
app/pages/example.vue
<template>
  <div>
    <Comments />
  </div>
</template>
Работает только с автоимпортом Nuxt и импортом из #components. Явный импорт по пути не делает компонент клиентским.
У .client компонентов шаблон доступен в onMounted() только после await nextTick() в колбэке.
Аналогичный эффект у компонента <ClientOnly>.

Серверные компоненты

Серверные компоненты позволяют рендерить отдельные компоненты на сервере. Их можно использовать в Nuxt даже при статической генерации — для смешения динамики, серверного HTML и статической разметки.

Серверные компоненты бывают самостоятельными (islands) или в паре с клиентским компонентом.

Гайд Daniel Roe по Nuxt Server Components.

Автономные серверные компоненты

Всегда рендерятся на сервере (islands). При обновлении пропов выполняется сетевой запрос и обновляется HTML на месте.

Экспериментальная возможность; включается в nuxt.config: experimental.componentIslands: true.

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

Компоненты только для сервера — суффикс .server, доступны по всему приложению.

Структура каталогов
-| components/
---| HighlightedMarkdown.server.vue
app/pages/example.vue
<template>
  <div>
    <HighlightedMarkdown markdown="# Headline" />
  </div>
</template>

Под капотом используется <NuxtIsland> — ему передаются проп lazy и слот #fallback.

Серверные компоненты и островки должны иметь один корневой элемент (HTML-комментарии считаются элементами).
Пропы передаются в серверные компоненты через query-параметры URL, объём данных ограничен длиной URL.
Островки внутри островков добавляют накладные расходы.
Слоты и клиентские компоненты внутри островков в основном поддерживаются только для SFC.

Клиентские компоненты внутри серверных

Нужно experimental.componentIslands.selectiveClient: true. Атрибут nuxt-client на дочернем компоненте подключает его на клиенте.

app/components/ServerWithClient.vue
<template>
  <div>
    <HighlightedMarkdown markdown="# Headline" />
    <Counter nuxt-client :count="5" />
  </div>
</template>
Слоты клиентских компонентов работают только при experimental.componentIsland.selectiveClient: 'deep' и рендерятся на сервере, на клиенте не интерактивны.

Контекст серверного компонента

При рендере server-only или island компонента <NuxtIsland> делает fetch; ответ — NuxtIslandResponse. Создаётся отдельное Vue-приложение и «островной» контекст; контекст островка изолирован от остального приложения. Плагины при рендере островка запускаются снова, если у них не задано env: { islands: false }.

Маршрутный мидлвар при рендере островков не выполняется.

В островном компоненте контекст доступен через nuxtApp.ssrContext.islandContext (формат может меняться, пока островки экспериментальны).

В паре с клиентским компонентом

Пара .server + .client — две «половинки» одного компонента для разной реализации на сервере и клиенте.

Структура каталогов
-| components/
---| Comments.client.vue
---| Comments.server.vue
app/pages/example.vue
<template>
  <div>
    <Comments />
  </div>
</template>

Nuxt отрисует Comments.server на сервере, затем после монтирования подставит Comments.client.

Встроенные компоненты Nuxt

Nuxt поставляется с компонентами вроде <ClientOnly> и <DevOnly>. Подробнее в API.

Узнать больше Docs > 4 X > API.

Авторам библиотек

Регистрация директории компонентов с автоимпортом и tree-shaking — через addComponentsDir из @nuxt/kit в Nuxt-модуле.

Структура каталогов
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.ts
import { addComponentsDir, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup () {
    const resolver = createResolver(import.meta.url)
    addComponentsDir({
      path: resolver.resolve('./components'),
      prefix: 'awesome',
    })
  },
})

В проекте: modules: ['awesome-ui/nuxt'] в nuxt.config, затем в шаблоне <AwesomeButton>, <awesome-alert>. Компоненты подгружаются по мере использования и поддерживают HMR.

Прочитайте и отредактируйте живой пример в Docs > 4 X > Examples > Features > Auto Imports.