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.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, предоставляемый Vue, или импортировать компонент напрямую из #components и передать его в свойство is.

Например:

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>Горы</h1>
    <LazyMountainsList v-if="show" />
    <button
      v-if="!show"
      @click="show = true"
    >
      Показать список
    </button>
  </div>
</template>

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

Префикс Lazy у компонентов помогает контролировать размер чанков, но не всегда улучшает производительность в рантайме: код всё равно подгружается сразу, если компонент не скрыт условием. На страницах с большим количеством контента и компонентов далеко не всем нужна интерактивность сразу после загрузки; если всё грузить сразу, это бьёт по производительности.

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

Nuxt поддерживает ленивую (отложенную) гидратацию и позволяет задать момент, когда компонент становится интерактивным.

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

Доступен набор встроенных стратегий. Для одного ленивого компонента можно использовать только одну стратегию.

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

hydrate-on-visible

Гидратирует компонент, когда он попадает в область просмотра.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible />
  </div>
</template>
Подробнее о настройках для hydrate-on-visible.
Под капотом используется встроенная в 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>

Если не передать событие или список событий, по умолчанию гидратация произойдёт на pointerenter, click и focus.

Под капотом используется встроенная в 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. Условный рендеринг: если у ленивого компонента стоит v-if="false", отдельная отложенная гидратация часто не нужна — достаточно обычного ленивого компонента.
  3. Общее состояние: учитывайте общий v-model между компонентами — обновление модели в одном может запустить гидратацию во всех связанных.
  4. Назначение стратегий: у каждой своя ниша:
    • hydrate-when — когда компонент не всегда должен гидратироваться;
    • hydrate-after — когда можно подождать фиксированное время;
    • hydrate-on-idle — когда можно дождаться простоя браузера.
  5. Интерактивность и hydrate-never: для компонентов, с которыми пользователь должен взаимодействовать, не используйте hydrate-never.

Прямые импорты

Вы также можете явно импортировать компоненты из #components, если хотите, или если вам необходимо обойти функцию автоматического импорта Nuxt.

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

const show = ref(false)
</script>

<template>
  <div>
    <h1>Горы</h1>
    <LazyMountainsList v-if="show" />
    <button
      v-if="!show"
      @click="show = true"
    >
      Показать список
    </button>
    <NuxtLink to="/">Главная</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',
    })
  },
})

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

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

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

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

Если компонент предназначен для рендера только на стороне клиента, вы можете добавить суффикс .client к своему компоненту.

Directory Structure
| components/
--| Comments.client.vue
pages/example.vue
<template>
  <div>
    <!-- этот компонент будет отображаться только на стороне клиента -->
    <Comments />
  </div>
</template>
Эта функция работает только с автоимпортами Nuxt и импортами #components. Явный импорт таких компонентов по их реальному пути не преобразует их в клиентские компоненты.
Компоненты .client рендерятся только после монтирования. Чтобы получить доступ к отрисованному шаблону с помощью onMounted(), добавьте await nextTick() в коллбэк хука onMounted().
Аналогичного результата можно добиться и с помощью компонента <ClientOnly>.

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

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

Серверные компоненты могут использоваться как по отдельности, так и в паре с клиентским компонентом.

Прочитайте руководство Дэниела Ро по серверным компонентам Nuxt.

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

Отдельные серверные компоненты, которые всегда будут отображаться на сервере, также известны как island-компоненты.

Когда их свойства обновляются, это приводит к сетевому запросу, который обновляет отображаемый 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>
    <!--
      это будет автоматически отрендерено на сервере, то есть ваши библиотеки парсинга и подсветки markdown не будут включены в ваш клиентский бандл.
     -->
    <HighlightedMarkdown markdown="# Заголовок" />
  </div>
</template>

Компоненты, предназначенные только для сервера, используют <NuxtIsland> под капотом, что означает, что свойство lazy и слот #fallback передаются им.

Серверные компоненты (и islands) должны иметь один корневой элемент. (Комментарии HTML также считаются элементами.)
Пропсы серверных компонентов передаются через параметры строки запроса URL и ограничены её длиной; не передавайте через пропсы слишком большие объёмы данных.
Будьте осторожны при вложении островов друг в друга: каждый остров добавляет накладные расходы.
Большинство функций для серверных компонентов и island-компонентов, таких как слоты и клиентские компоненты, доступны только для однофайловых компонентов.

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

Для работы этой функции в вашей конфигурации должен быть указан experimental.componentIslands.selectiveClient.

Вы можете частично гидратировать компонент, установив атрибут nuxt-client для компонента, который вы хотите загрузить на стороне клиента.

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

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

При рендеринге серверного или island-компонента <NuxtIsland> выполняет запрос; в ответ приходит NuxtIslandResponse. На сервере это внутренний запрос; в браузере такой запрос виден на вкладке «Сеть», если остров рендерится на клиенте.

Отсюда следует:

  • На сервере для формирования NuxtIslandResponse создаётся отдельное приложение Vue.
  • Во время рендеринга компонента создаётся свой контекст острова (island context).
  • К контексту острова нельзя обратиться из остального приложения, и из острова недоступен контекст приложения: серверный и island-компонент изолированы от остальной части приложения.
  • Плагины снова запускаются при рендере острова, если в объектном синтаксисе плагина не задано env: { islands: false }.
Middleware маршрута не выполняется при рендеринге island-компонентов. Middleware относится к маршрутизации страниц, а не к рендерингу компонентов.

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

Слоты могут быть интерактивными и заключаются в <div> с display: contents;.

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

В этом случае компоненты .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 Nuxt.

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

Создавать библиотеки компонентов Vue с автоматическим отсечением неиспользуемого кода (tree-shaking) и регистрацией компонентов очень просто. ✨

Для регистрации директории компонентов в Nuxt-модуле используйте метод addComponentsDir из @nuxt/kit.

Представьте себе такую структуру директорий:

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 { addComponentsDir, createResolver, defineNuxtModule } from '@nuxt/kit'

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

    // Добавьте директорию ./components в список
    addComponentsDir({
      path: resolver.resolve('./components'),
      prefix: 'awesome',
    })
  },
})

Готово. Теперь в проекте можно подключить UI-библиотеку как модуль Nuxt в nuxt.config:

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

... и напрямую использовать компоненты модуля (с префиксом awesome-) в нашем pages/index.vue:

<template>
  <div>
    Моя <AwesomeButton>UI кнопка</AwesomeButton>!
    <awesome-alert>Это алерт!</awesome-alert>
  </div>
</template>

Это автоматически импортирует компоненты только в том случае, если они используются, а также поддерживает HMR при обновлении компонентов в node_modules/awesome-ui/components/.

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