Skip to content

Nuxt Composables — Auto-Imported State and Utilities in Nuxt 3

DodaTech Updated 2026-06-28 4 min read

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

  1. Not using a unique key for useState: The string key must be unique across the app. Duplicate keys share state unintentionally.
  2. Forgetting await with useFetch: useFetch returns promises in <script setup>. Without await, the data is initially undefined.
  3. Using useFetch outside <script setup>: useFetch requires a Nuxt component context. Use $fetch for standalone API calls.
  4. Mutating data directly: Use data.value = newData or the provided refresh() method. Direct mutation may not trigger reactivity.
  5. Creating composables outside composables/: Files outside this directory aren't auto-imported. Manual imports are needed.

Practice Questions

  1. What is the purpose of useState? Answer: It creates reactive state shared across components with SSR support. The string key ensures uniqueness.

  2. How does useFetch handle SSR? Answer: It fetches data on the server during SSR, serializes it, and provides it to the client without re-fetching on hydration.

  3. How do you create an auto-imported composable? Answer: Create a file in composables/ that exports a function starting with use. Nuxt auto-imports it.

  4. What is the difference between useState and ref? Answer: useState is shared across components and supports SSR. ref is 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

Can I use Vue's `ref` and `reactive` in Nuxt?

: Yes. They work as in standard Vue 3. However, useState is preferred for SSR-safe shared state.

Do composables support TypeScript?

: Yes. Composable files can be .ts or .js. Use generics for type-safe composables.

Are composables tree-shaken?

: Yes. Unused composables are excluded from the production bundle.

Can composables use lifecycle hooks?

: Yes. Composable functions can use onMounted, watch, computed, and other Vue Composition API functions.

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