Composables

Building Reusable Composables

Composables are Vue's way of extracting and reusing stateful logic. In Nuxt, composables in the composables/ directory are auto-imported, making them available throughout your app.

What are Composables?

Composables are functions that leverage Vue's Composition API to encapsulate and reuse logic:

// composables/useCounter.ts
export function useCounter(initialValue = 0) {
  const count = ref(initialValue)

  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue

  return { count, increment, decrement, reset }
}

SSR-Safe Composables

When creating composables for Nuxt, be aware of SSR:

// ❌ Not SSR-safe
export function useBrowser() {
  const width = ref(window.innerWidth) // Error on server!
}

// ✅ SSR-safe
export function useBrowser() {
  const width = ref(0)

  onMounted(() => {
    width.value = window.innerWidth
  })

  return { width }
}

Sharing State

Composables can share state across components:

// Shared state (singleton pattern)
const globalState = ref(0)

export function useSharedCounter() {
  const increment = () => globalState.value++
  return { count: globalState, increment }
}

Auto-Import

Nuxt auto-imports composables from:

  • composables/ directory
  • Files exported with export const use... or export function use...

VueUse: Collection of Composables

VueUse is a collection of essential Vue composition utilities. It's highly recommended to use VueUse instead of writing your own composables for common tasks.

Installing VueUse

npm install @vueuse/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@vueuse/nuxt']
})
<script setup>
// Mouse position
const { x, y } = useMouse()

// Window size
const { width, height } = useWindowSize()

// Local storage (with SSR support)
const state = useLocalStorage('my-key', { count: 0 })

// Dark mode
const isDark = useDark()
const toggleDark = useToggle(isDark)

// Clipboard
const { text, copy, copied } = useClipboard()

// Debounced ref
const search = ref('')
const debouncedSearch = useDebounceFn(() => {
  // Perform search
}, 500)
</script>

When to Use VueUse

Use VueUse when:

  • Working with browser APIs (localStorage, clipboard, geolocation)
  • Handling mouse/keyboard events
  • Managing element visibility or size
  • Debouncing/throttling functions
  • Working with dates, arrays, or objects

Write custom composables when:

  • You need business-logic specific to your app
  • VueUse doesn't have what you need
  • You need deeper customization

Best Practice: Check VueUse documentation before building utility composables. It has over 200 functions covering most common needs!

Challenge

Build a useCounter composable with localStorage persistence:

  1. Create composables/useCounter.ts that:
    • Accepts an optional key for localStorage (default: 'counter')
    • Initializes count from localStorage or 0
    • Provides increment, decrement, and reset functions
    • Saves to localStorage on every change (use watch)
    • Only use localStorage on client (SSR-safe)
  2. In app.vue:
    • Use the composable twice with different keys
    • Show that each instance maintains its own persisted state
    • Add reset buttons

Pitfall: Global state in composables persists between requests on the server! Use useState() for SSR-safe global state.

Tip: Always check if process.client before using browser APIs, or use onMounted hook.

If you get stuck, you can check the solution by clicking the button below.


Composables are the building blocks of reusable logic in Vue and Nuxt. They're perfect for encapsulating state management, API calls, form handling, and any logic you want to share across components.

Nuxt Modules
Configuration
Files
Editor
Initializing WebContainer
Mounting files
Installing dependencies
Starting Nuxt server
Waiting for Nuxt to ready
Terminal