Nuxt automatically imports any components in this directory (along with components that are registered by any modules you may be using).
-| components/
---| AppHeader.vue
---| AppFooter.vue
<template>
<div>
<AppHeader />
<NuxtPage />
<AppFooter />
</div>
</template>
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 />
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>.
If you want to use the Vue <component :is="someComputedComponent"> syntax, you need to use the resolveComponent helper provided by Vue or import the component directly from #components and pass it into is prop.
For example:
<script setup lang="ts">
import { SomeComponent } from '#components'
const MyButton = resolveComponent('MyButton')
</script>
<template>
<component :is="clickable ? MyButton : 'div'" />
<component :is="SomeComponent" />
</template>
resolveComponent to handle dynamic components, make sure not to insert anything but the name of the component, which must be a literal string and not be or contain a variable. The string is statically analyzed at the compilation step.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, or by using a .global.vue suffix in the filename. As noted above, each global component is rendered in a separate chunk, so be careful not to overuse this feature.
global option can also be set per component directory.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. 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.
<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 components are great for controlling the chunk sizes in your app, but they don't always enhance runtime performance, as they still load eagerly unless conditionally rendered. In real-world applications, some pages may include a lot of content and a lot of components, and most of the time not all of them need to be interactive as soon as the page is loaded. Having them all load eagerly can negatively impact performance.
In order to optimize your app, you may want to delay the hydration of some components until they're visible, or until the browser is done with more important tasks.
Nuxt supports this using lazy (or delayed) hydration, allowing you to control when components become interactive.
Nuxt provides a range of built-in hydration strategies. Only one strategy can be used per lazy component.
hydrate-never will cause it to hydrate)v-bind). It also does not work with direct imports from #components.hydrate-on-visibleHydrates the component when it becomes visible in the viewport.
<template>
<div>
<LazyMyComponent hydrate-on-visible />
</div>
</template>
hydrateOnVisible strategy.hydrate-on-idleHydrates the component when the browser is idle. This is suitable if you need the component to load as soon as possible, but not block the critical rendering path.
You can also pass a number which serves as a max timeout.
<template>
<div>
<LazyMyComponent hydrate-on-idle />
</div>
</template>
hydrateOnIdle strategy.hydrate-on-interactionHydrates the component after a specified interaction (e.g., click, mouseover).
<template>
<div>
<LazyMyComponent hydrate-on-interaction="mouseover" />
</div>
</template>
If you do not pass an event or list of events, it defaults to hydrating on pointerenter, click and focus.
hydrateOnInteraction strategy.hydrate-on-media-queryHydrates the component when the window matches a media query.
<template>
<div>
<LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
</div>
</template>
hydrateOnMediaQuery strategy.hydrate-afterHydrates the component after a specified delay (in milliseconds).
<template>
<div>
<LazyMyComponent :hydrate-after="2000" />
</div>
</template>
hydrate-whenHydrates the component based on a boolean condition.
<template>
<div>
<LazyMyComponent :hydrate-when="isReady" />
</div>
</template>
<script setup lang="ts">
const isReady = ref(false)
function myFunction () {
// trigger custom hydration strategy...
isReady.value = true
}
</script>
hydrate-neverNever hydrates the component.
<template>
<div>
<LazyMyComponent hydrate-never />
</div>
</template>
All delayed hydration components emit a @hydrated event when they are hydrated.
<template>
<div>
<LazyMyComponent
hydrate-on-visible
@hydrated="onHydrate"
/>
</div>
</template>
<script setup lang="ts">
function onHydrate () {
console.log('Component has been hydrated!')
}
</script>
Delayed hydration can offer performance benefits, but it's essential to use it correctly:
v-if="false" on a lazy component, you might not need delayed hydration. You can just use a normal lazy component.v-model) across multiple components. Updating the model in one component can trigger hydration in all components bound to that model.hydrate-when is best for components that might not always need to be hydrated.hydrate-after is for components that can wait a specific amount of time.hydrate-on-idle is for components that can be hydrated when the browser is idle.hydrate-never on interactive components: If a component requires user interaction, it should not be set to never hydrate.You can also explicitly import components from #components if you want or need to bypass Nuxt's auto-importing functionality.
<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>
By default, only the ~/components directory is scanned. If you want to add other directories, or change how the components are scanned within a subfolder of this directory, you can add additional directories to the configuration:
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' },
// It's important that this comes last if you have overrides you wish to apply
// to sub-directories of `~/components`.
//
// ~/components/Btn.vue => <Btn />
// ~/components/base/Btn.vue => <BaseBtn />
'~/components',
],
})
If you want to auto-import components from an npm package, you can use addComponent in a local module to register them.
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',
})
},
})
<template>
<div>
<!-- the component uses the name we specified and is auto-imported -->
<MyAutoImportedComponent />
</div>
</template>
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 defineNuxtConfig({
components: [
{
path: '~/components',
extensions: ['.vue'], },
],
})
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>
#components imports. Explicitly importing these components from their real paths does not convert them into client-only components..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 allow server-rendering individual components within your client-side apps. It's possible to use server components within Nuxt, even if you are generating a static site. That makes it possible to build complex sites that mix dynamic components, server-rendered HTML and even static chunks of markup.
Server components can either be used on their own or paired with a client component.
Standalone server components will always be rendered on the server, also known as Islands components.
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>
Server-only components use <NuxtIsland> under the hood, meaning that lazy prop and #fallback slot are both passed down to it.
experimental.componentIslands.selectiveClient within your configuration to be true.You can partially hydrate a component by setting a nuxt-client attribute on the component you wish to be loaded client-side.
<template>
<div>
<HighlightedMarkdown markdown="# Headline" />
<!-- Counter will be loaded and hydrated client-side -->
<Counter
nuxt-client
:count="5"
/>
</div>
</template>
experimental.componentIsland.selectiveClient set to 'deep' and since they are rendered server-side, they are not interactive once client-side.When rendering a server-only or island component, <NuxtIsland> makes a fetch request which comes back with a NuxtIslandResponse. (This is an internal request if rendered on the server, or a request that you can see in the network tab if it's rendering on client-side navigation.)
This means:
NuxtIslandResponse.env: { islands: false } set (which you can do in an object-syntax plugin).Within an island component, you can access its island context through nuxtApp.ssrContext.islandContext. Note that while island components are still marked as experimental, the format of this context may change.
<div> with display: contents;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 on the server then Comments.client once mounted in the browser -->
<Comments />
</div>
</template>
There are a number of components that Nuxt provides, including <ClientOnly> and <DevOnly>. You can read more about them in the API documentation.
Making Vue component libraries with automatic tree-shaking and component registration is super easy. ✨
You can use the addComponentsDir method provided from the @nuxt/kit to register your components directory in your Nuxt module.
Imagine a directory structure like this:
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.ts
-| pages/
---| index.vue
-| nuxt.config.ts
Then in awesome-ui/nuxt.ts you can use the addComponentsDir hook:
import { addComponentsDir, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup () {
const resolver = createResolver(import.meta.url)
// Add ./components dir to the list
addComponentsDir({
path: resolver.resolve('./components'),
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 defineNuxtConfig({
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/.