Custom Composables

Reusable Logic in composables/

Custom Composables

Any file under composables/ exports named composables that are auto-imported across the app, perfect for wrapping useState, useFetch, or any reusable reactive logic.

4 min read Level 2/5 #nuxt#composables
What you'll learn
  • Write a composable file under the composables directory
  • Compose useState and useFetch inside a single composable
  • Type your return shape so consumers get full inference

The composables/ directory is Nuxt’s home for shared reactive logic. Every named export becomes auto-imported app-wide — no manual import statements needed.

A Simple Composable

// composables/useUser.ts
export interface User {
  id: string
  name: string
}

export const useUser = () => useState<User | null>('user', () => null)

Now any page or component can grab it:

<script setup lang="ts">
const user = useUser()
</script>

<template>
  <p v-if="user">Hi {{ user.name }}</p>
  <p v-else>Not signed in</p>
</template>

Composing Other Composables

Composables compose. Here is one that fetches the current user and caches the result:

// composables/useCurrentUser.ts
export const useCurrentUser = async () => {
  const user = useUser()
  if (!user.value) {
    const { data } = await useFetch<User>('/api/me')
    user.value = data.value
  }
  return user
}

Returning Refs Preserves Reactivity

If you return a plain value, consumers get a snapshot. Return refs (or reactive objects) to keep reactivity intact:

// composables/useCart.ts
export const useCart = () => {
  const items = useState<string[]>('cart', () => [])
  const count = computed(() => items.value.length)
  const add = (id: string) => items.value.push(id)
  return { items, count, add }
}

Naming Conventions

  • File names use kebab-case or camelCase — Nuxt scans the default export and any named exports.
  • By convention, composables start with use (matches Vue’s rules-of-hooks style).
  • Nested folders work, but only the leaf file names need to be unique.
Runtime Config →