Nuxt/docs/2.guide/2.directory-structure/1.components.md
2023-02-07 02:09:36 -08:00

11 KiB

navigation.icon title description head.title
IconDirectory components The components/ directory is where you put all your Vue components. components/

Components Directory

The components/ directory is where you put all your Vue components which can then be imported inside your pages or other components (learn more).

Nuxt automatically imports any components in your components/ directory (along with components that are registered by any modules you may be using).

| components/
--| TheHeader.vue
--| TheFooter.vue
<template>
  <div>
    <TheHeader />
    <slot />
    <TheFooter />
  </div>
</template>

Component extensions

By default, any file with an extension specified in the extensions key of nuxt.config.ts is treated as a component. If you need to restrict the file extensions that should be registered as components, you can use the extended form of the components directory declaration and its extensions key:

export default defineNuxtModule({
  components: [
    {
      path: '~/components',
+     extensions: ['.vue'],
    }
  ]
})

Component Names

If you have a component in nested directories such as:

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

... then the component's name will be based on its own path directory and filename, with duplicate segments being removed. Therefore, the component's name will be:

<BaseFooButton />

::alert For clarity, we recommend that the component's filename matches its name. (So, in the example above, you could rename Button.vue to be BaseFooButton.vue.) ::

If you want to auto-import components based only on its name, not path, then you need to set pathPrefix option to false using extended form of the configuration object:

export default defineNuxtConfig({
  components: [
    {
      path: '~/components/',
+     pathPrefix: false,
    },
  ],
});

This registers the components using the same strategy as used in Nuxt 2. For example, ~/components/Some/MyComponent.vue will be usable as <MyComponent> and not <SomeMyComponent>.

Dynamic Components

If you want to use the Vue <component :is="someComputedComponent"> syntax, then you will need to use the resolveComponent helper provided by Vue.

For example:

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

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

::alert{type=warning} If you are using resolveComponent to handle dynamic components, make sure not to insert anything but the name of the component, which must be a string and not a variable. ::

Alternatively, though not recommended, you can register all your components globally, which will create async chunks for all your components and make them available throughout your application.


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

You can also selectively register some components globally by placing them in a ~/components/global directory.

::alert{type=info} The global option can also be set per component directory. ::

Dynamic Imports

To dynamically import a component (also known as lazy-loading a component) all you need to do is add the Lazy prefix to the component's name.

<template>
  <div>
    <TheHeader />
    <slot />
    <LazyTheFooter />
  </div>
</template>

This is particularly useful if the component is not always needed. By using the Lazy prefix you can delay loading the component code until the right moment, which can be helpful for optimizing your JavaScript bundle size.

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

<script>
export default {
  data() {
    return {
      show: false
    }
  }
}
</script>

Direct Imports

You can also explicitly import components from #components if you want or need to bypass Nuxt's auto-importing functionality.

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

<script setup>
  import { NuxtLink, LazyMountainsList } from '#components'
  const show = ref(false)
</script>

<ClientOnly> Component

Nuxt provides the <ClientOnly> component for purposely rendering a component only on client side. To import a component only on the client, register the component in a client-side only plugin.

<template>
  <div>
    <Sidebar />
    <ClientOnly>
      <!-- this component will only be rendered on client-side -->
      <Comments />
    </ClientOnly>
  </div>
</template>

Use a slot as fallback until <ClientOnly> is mounted on client side.

<template>
  <div>
    <Sidebar />
    <!-- This renders the "span" element on the server side -->
    <ClientOnly fallbackTag="span">
      <!-- this component will only be rendered on client side -->
      <Comments />
      <template #fallback>
        <!-- this will be rendered on server side -->
        <p>Loading comments...</p>
      </template>
    </ClientOnly>
  </div>
</template>

.client Components

If a component is meant to be rendered only client-side, you can add the .client suffix to your component.

| components/
--| Comments.client.vue
<template>
  <div>
    <!-- this component will only be rendered on client side -->
    <Comments />
  </div>
</template>

::alert{type=warning} This feature only works with Nuxt auto-imports and #components imports. Explicitly importing these components from their real paths does not convert them into client-only components. ::

::alert{type=warning} .client components are rendered only after being mounted. To access the rendered template using onMounted(), add await nextTick() in the callback of the onMounted() hook. ::

.server Components

.server components can either be used on their own or paired with a .client component.

Standalone server components

Standalone server components will always be rendered on the server. When their props update, this will result in a network request that will update the rendered HTML in-place.

Server components are currently experimental and in order to use them, you need to enable the 'component islands' feature in your nuxt.config:

export default defineNuxtConfig({
  experimental: {
    componentIslands: true
  }
})

Now you can register server-only components with the .server suffix and use them anywhere in your application automatically.

| components/
--| HighlightedMarkdown.server.vue
<template>
  <div>
    <!--
      this will automatically be rendered on the server, meaning your markdown parsing + highlighting
      libraries are not included in your client bundle.
     -->
    <HighlightedMarkdown markdown="# Headline" />
  </div>
</template>

::alert{type=warning} Slots are not supported by server components in their current state of development. ::

::alert{type=info} Please note that there is a known issue with using async code in a server component. It will lead to a hydration mismatch error on initial render. See #18500. Until there is a workaround, server-components must be synchronous. ::

Paired with a .client component

In this case, the .server + .client components are two 'halves' of a component and can be used in advanced use cases for separate implementations of a component on server and client side.

| components/
--| Comments.client.vue
--| Comments.server.vue
<template>
  <div>
    <!-- this component will render Comments.server server-side then Comments.client once mounted in client-side -->
    <Comments />
  </div>
</template>

::alert{type=warning} It is essential that the client half of the component can 'hydrate' the server-rendered HTML. That is, it should render the same HTML on initial load, or you will experience a hydration mismatch. ::

<DevOnly> Component

Nuxt provides the <DevOnly> component to render a component only during development.

The content will not be included in production builds and tree-shaken.

<template>
  <div>
    <Sidebar />
    <DevOnly>
      <!-- this component will only be rendered during development -->
      <LazyDebugBar />
    </DevOnly>
  </div>
</template>

Library Authors

Making Vue component libraries with automatic tree-shaking and component registration is super easy

You can use the components:dirs hook to extend the directory list without requiring user configuration in your Nuxt module.

Imagine a directory structure like this:

| node_modules/
---| awesome-ui/
------| components/
---------| Alert.vue
---------| Button.vue
------| nuxt.js
| pages/
---| index.vue
| nuxt.config.js

Then in awesome-ui/nuxt.js you can use the components:dirs hook:

import { defineNuxtModule } from '@nuxt/kit'
import { fileURLToPath } from 'node:url'

export default defineNuxtModule({
  hooks: {
    'components:dirs'(dirs) {
      // Add ./components dir to the list
      dirs.push({
        path: fileURLToPath(new URL('./components', import.meta.url)),
        prefix: 'awesome'
      })
    }
  }
})

That's it! Now in your project, you can import your UI library as a Nuxt module in your nuxt.config file:

export default {
  modules: ['awesome-ui/nuxt']
}

... and directly use the module components (prefixed with awesome-) in our pages/index.vue:

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

It will automatically import the components only if used and also support HMR when updating your components in node_modules/awesome-ui/components/.

::LinkExample{link="/docs/examples/auto-imports/components"} ::