Nuxt Components
Building blocks of your Nuxt application
What are Components in Nuxt?
Nuxt components are Vue components with automatic imports, file-based naming, and built-in optimization.
Nuxt Magic: Components in the
components/ directory are automatically imported - no need for manual imports!
Component Directory Structure
components/
├── AppHeader.vue → <AppHeader />
├── AppFooter.vue → <AppFooter />
├── base/
│ ├── Button.vue → <BaseButton />
│ └── Input.vue → <BaseInput />
└── product/
├── Card.vue → <ProductCard />
└── List.vue → <ProductList />
Zero Config: Nuxt automatically registers all components in the
components/ directory!
Auto Imports
Nuxt automatically imports components, composables, and utilities without explicit imports.
Basic Component Usage
<!-- components/AppHeader.vue -->
<template>
<header>
<h1>My Nuxt App</h1>
</header>
</template>
<!-- pages/index.vue -->
<template>
<div>
<!-- No import needed! -->
<AppHeader />
<h2>Welcome!</h2>
</div>
</template>
Nested Directory Components
<!-- components/base/Button.vue -->
<template>
<button class="btn">
<slot />
</button>
</template>
<!-- Usage in any page/component -->
<template>
<div>
<!-- Component name = directory + filename -->
<BaseButton>Click Me</BaseButton>
</div>
</template>
Component Naming Conventions
| File Path | Component Name | Usage |
|---|---|---|
components/Button.vue |
Button | <Button /> |
components/base/Button.vue |
BaseButton | <BaseButton /> |
components/user/Profile.vue |
UserProfile | <UserProfile /> |
components/UserProfileCard.vue |
UserProfileCard | <UserProfileCard /> |
Tip: Use PascalCase for component names in directories and kebab-case or PascalCase in templates.
Built-in Nuxt Components
Nuxt provides several built-in components for common use cases.
<NuxtLink> - Navigation
<template>
<nav>
<!-- Internal links -->
<NuxtLink to="/">Home</NuxtLink>
<NuxtLink to="/about">About</NuxtLink>
<NuxtLink to="/blog/post-1">Blog Post</NuxtLink>
<!-- External links -->
<NuxtLink to="https://nuxt.com" external>Nuxt Docs</NuxtLink>
<!-- Active link styling -->
<NuxtLink
to="/dashboard"
active-class="active"
exact-active-class="exact-active"
>
Dashboard
</NuxtLink>
</nav>
</template>
<NuxtPage> - Page Rendering
<!-- app.vue or layouts -->
<template>
<div>
<AppHeader />
<!-- Renders the current page -->
<NuxtPage />
<AppFooter />
</div>
</template>
<NuxtLayout> - Layout System
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<!-- pages/index.vue -->
<template>
<div>
<h1>Home Page</h1>
</div>
</template>
<script setup>
// Use a specific layout
definePageMeta({
layout: 'dashboard'
})
</script>
<NuxtLoadingIndicator> - Progress Bar
<!-- app.vue -->
<template>
<div>
<!-- Shows progress bar during page transitions -->
<NuxtLoadingIndicator color="#00DC82" />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
<NuxtImg> - Image Optimization
<template>
<div>
<!-- Optimized image with @nuxt/image module -->
<NuxtImg
src="/images/hero.jpg"
alt="Hero image"
width="800"
height="600"
loading="lazy"
/>
<!-- With transformations -->
<NuxtImg
src="/images/avatar.jpg"
width="100"
height="100"
format="webp"
quality="80"
/>
</div>
</template>
Note:
<NuxtImg> requires the @nuxt/image module to be installed.
Client-Only Rendering
Render components only on the client-side using <ClientOnly>.
Basic Usage
<template>
<div>
<h1>This renders on both server and client</h1>
<!-- Only renders on client-side -->
<ClientOnly>
<BrowserOnlyComponent />
</ClientOnly>
</div>
</template>
With Fallback
<template>
<div>
<ClientOnly>
<!-- Client-side content -->
<InteractiveMap />
<!-- Server-side fallback -->
<template #fallback>
<div>Loading map...</div>
</template>
</ClientOnly>
</div>
</template>
When to Use Client-Only
- Components that use browser-only APIs (window, document, localStorage)
- Third-party libraries without SSR support
- Components with different server/client hydration
- Heavy visualizations or animations
Example: Browser Storage
<template>
<ClientOnly>
<div>
<p>Last visit: {{ lastVisit }}</p>
</div>
</ClientOnly>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const lastVisit = ref('')
onMounted(() => {
// Safe to use localStorage in mounted
lastVisit.value = localStorage.getItem('lastVisit') || 'First visit!'
localStorage.setItem('lastVisit', new Date().toISOString())
})
</script>
Dynamic & Lazy Components
Improve performance by lazy loading components when needed.
Lazy Component Prefix
<template>
<div>
<!-- Regular import (bundled immediately) -->
<AppHeader />
<!-- Lazy loaded (separate chunk) -->
<LazyAppFooter />
<!-- Only loaded when visible -->
<LazyHeavyChart />
</div>
</template>
Manual Dynamic Import
<template>
<div>
<button @click="showModal = true">Open Modal</button>
<component
v-if="showModal"
:is="Modal"
@close="showModal = false"
/>
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
const showModal = ref(false)
// Only loaded when needed
const Modal = defineAsyncComponent(() =>
import('~/components/Modal.vue')
)
</script>
Performance Benefits
| Approach | Bundle | Use Case |
|---|---|---|
<Component /> |
Main bundle | Critical, above-the-fold components |
<LazyComponent /> |
Separate chunk | Below-the-fold, modal, tabs |
defineAsyncComponent |
On-demand | Conditional rendering, user interactions |
Best Practice: Use
Lazy prefix for components below the fold or loaded conditionally.
Component Best Practices
1. Use Auto Imports
<!-- ✅ Good - Auto imported -->
<template>
<BaseButton>Click Me</BaseButton>
</template>
<!-- ❌ Bad - Manual import (unnecessary) -->
<template>
<BaseButton>Click Me</BaseButton>
</template>
<script setup>
import BaseButton from '~/components/base/Button.vue' // Not needed!
</script>
2. Component Organization
components/
├── base/ # Reusable UI components
│ ├── Button.vue
│ └── Input.vue
├── layout/ # Layout components
│ ├── Header.vue
│ └── Footer.vue
├── feature/ # Feature-specific components
│ ├── UserProfile.vue
│ └── ProductCard.vue
└── App*.vue # App-level components
3. Use Composables for Logic
<!-- composables/useAuth.ts -->
export const useAuth = () => {
const user = useState('user', () => null)
const login = async (credentials) => {
// Login logic
}
const logout = () => {
user.value = null
}
return { user, login, logout }
}
<!-- components/UserMenu.vue -->
<script setup>
const { user, logout } = useAuth() // Auto imported!
</script>
4. TypeScript Props
<!-- components/ProductCard.vue -->
<script setup lang="ts">
interface Props {
title: string
price: number
image?: string
featured?: boolean
}
const props = withDefaults(defineProps<Props>(), {
image: '/placeholder.jpg',
featured: false
})
</script>
<template>
<div :class="{ featured: props.featured }">
<img :src="props.image" :alt="props.title" />
<h3>{{ props.title }}</h3>
<p>${{ props.price }}</p>
</div>
</template>
Pro Tip: Use the Nuxt DevTools to visualize your component tree and analyze bundle sizes!