Nuxt Composables — Auto-Imported State and Utilities in Nuxt 3
In this tutorial, you will learn about Nuxt Composables. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn Nuxt 3 composables: useState, useFetch, useHead, useRoute, and custom auto-imported composables for shared reactive logic.
In this lesson, you'll understand the built-in Nuxt composables and how to create your own auto-imported composables.
What You'll Learn
How to use built-in composables (useState, useFetch, useHead, useRoute, useRouter), create custom composables, and understand auto-import rules.
Why It Matters
Composables encapsulate reactive logic. Nuxt auto-imports them from composables/, making shared state and utilities available everywhere without imports.
flowchart LR
A[composables/] --> B[Auto-imported]
B --> C[useCounter]
B --> D[useAuth]
B --> E[useNotifications]
C --> F[Any Page/Component]
D --> F
E --> F
style A fill:#00dc82,color:#fff
useState
Share reactive state across components:
<script setup>
// State is shared across all components using this composable
const count = useState('counter', () => 0);
const increment = () => count.value++;
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
</div>
</template>
Output: The counter state is shared. If used in multiple components, they all see the same value. The state persists during SSR and hydration.
useFetch
Fetch data from API endpoints:
<script setup>
const { data: posts, pending, error, refresh } = await useFetch('/api/posts', {
params: { page: 1, limit: 10 },
key: 'post-list'
});
// With immediate: false to defer fetching
const { data: user } = await useFetch('/api/user', {
immediate: false
});
</script>
<template>
<div>
<p v-if="pending">Loading...</p>
<p v-if="error">Error: {{ error.message }}</p>
<div v-for="post in posts" :key="post.id">
<h3>{{ post.title }}</h3>
</div>
<button @click="refresh()">Refresh</button>
</div>
</template>
Output: useFetch fetches data during SSR and provides it to the client without a second request. The pending state handles loading, refresh re-fetches.
useHead
Set page metadata and SEO tags:
<script setup>
const title = ref('About Us');
const description = ref('Learn about our mission');
useHead({
title,
meta: [
{ name: 'description', content: description },
{ property: 'og:title', content: title },
{ property: 'og:description', content: description }
],
link: [
{ rel: 'canonical', href: 'https://example.com/about' }
]
});
</script>
Output: The <head> tag is updated with the title, meta description, Open Graph tags, and canonical URL. Changes to reactive refs update the head reactively.
useRoute and useRouter
Access route info and navigate programmatically:
<script setup>
const route = useRoute();
const router = useRouter();
// Access params and query
console.log(route.params.slug);
console.log(route.query.page);
// Navigate
const goToPost = (slug) => {
router.push(`/blog/${slug}`);
};
const goBack = () => {
router.back();
};
</script>
Custom Composables
Create auto-imported composables in composables/:
// composables/useCounter.ts
export const useCounter = (initialValue = 0) => {
const count = useState('counter', () => initialValue);
const increment = () => count.value++;
const decrement = () => count.value--;
const reset = () => count.value = initialValue;
return {
count: readonly(count),
increment,
decrement,
reset
};
};
Usage in any component:
<script setup>
const { count, increment } = useCounter(10);
</script>
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">+</button>
</div>
</template>
Output: The custom composable is auto-imported. It encapsulates counter logic with a clean API.
Common Mistakes
- Not using a unique key for
useState: The string key must be unique across the app. Duplicate keys share state unintentionally. - Forgetting
awaitwithuseFetch:useFetchreturns promises in<script setup>. Withoutawait, the data is initially undefined. - Using
useFetchoutside<script setup>:useFetchrequires a Nuxt component context. Use$fetchfor standalone API calls. - Mutating
datadirectly: Usedata.value = newDataor the providedrefresh()method. Direct mutation may not trigger reactivity. - Creating composables outside
composables/: Files outside this directory aren't auto-imported. Manual imports are needed.
Practice Questions
What is the purpose of
useState? Answer: It creates reactive state shared across components with SSR support. The string key ensures uniqueness.How does
useFetchhandle SSR? Answer: It fetches data on the server during SSR, serializes it, and provides it to the client without re-fetching on hydration.How do you create an auto-imported composable? Answer: Create a file in
composables/that exports a function starting withuse. Nuxt auto-imports it.What is the difference between
useStateandref? Answer:useStateis shared across components and supports SSR.refis local to a single component instance.
Challenge
Create a composable useLocalStorage that persists state to localStorage with SSR-safe hydration. The composable should read from localStorage on mount and write on changes.
Mini Project
Build a shopping cart using composables: useCart for cart state (items, add, remove, total), useProductList for fetching products, and useNotifications for toast messages on add/remove.
FAQ
What's Next
Learn about Nuxt Data Fetching for advanced data fetching with useFetch, useAsyncData, and server-side data handling.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro