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...orexport 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']
})
Popular VueUse Composables
<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:
- Create
composables/useCounter.tsthat:- Accepts an optional key for localStorage (default: 'counter')
- Initializes count from localStorage or 0
- Provides
increment,decrement, andresetfunctions - Saves to localStorage on every change (use
watch) - Only use localStorage on client (SSR-safe)
- 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.