From nuxt-v5
Guides Nuxt 5 data management with useFetch, useAsyncData, useState, and Pinia for creating composables, fetching data, managing state, and debugging reactive/SSR issues.
npx claudepluginhub secondsky/claude-skills --plugin nuxt-v5This skill uses the workspace's default tool permissions.
Composables, data fetching, and state management patterns for Nuxt 5 applications.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Composables, data fetching, and state management patterns for Nuxt 5 applications.
Use when: creating custom composables, fetching data with useFetch or useAsyncData, managing global state with useState, integrating Pinia, debugging reactive data issues, or implementing SSR-safe state patterns.
| Method | Use Case | SSR | Caching | Reactive |
|---|---|---|---|---|
useFetch | Simple API calls | Yes | Yes | Yes |
useAsyncData | Custom async logic | Yes | Yes | Yes |
$fetch | Client-side only, events | No | No | No |
Load references/composables.md when:
Load references/data-fetching.md when:
useState creates SSR-safe, shared reactive state that persists across component instances.
// composables/useCounter.ts
export const useCounter = () => {
const count = useState('counter', () => 0)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = 0
return { count, increment, decrement, reset }
}
// CORRECT: Shared state (singleton pattern)
export const useAuth = () => {
const user = useState('auth-user', () => null)
return { user }
}
// WRONG: Creates new instance every call!
export const useAuth = () => {
const user = ref(null)
return { user }
}
Rule: Use useState for shared/global state. Use ref for local component state only.
// Nuxt 5: clearNuxtState resets state to its initial value, not undefined
const count = useState('counter', () => 42)
count.value = 100
clearNuxtState('counter')
// count.value is now 42 (the initial default), not undefined
// composables/useAuth.ts
export const useAuth = () => {
const user = useState<User | null>('auth-user', () => null)
const isAuthenticated = computed(() => !!user.value)
const isLoading = useState('auth-loading', () => false)
const login = async (email: string, password: string) => {
isLoading.value = true
try {
const data = await $fetch('/api/auth/login', {
method: 'POST',
body: { email, password }
})
user.value = data.user
return { success: true }
} catch (error) {
return { success: false, error: error.message }
} finally {
isLoading.value = false
}
}
const logout = async () => {
await $fetch('/api/auth/logout', { method: 'POST' })
user.value = null
navigateTo('/login')
}
const checkSession = async () => {
if (import.meta.server) return
try {
const data = await $fetch('/api/auth/session')
user.value = data.user
} catch {
user.value = null
}
}
return { user, isAuthenticated, isLoading, login, logout, checkSession }
}
// composables/useLocalStorage.ts
export const useLocalStorage = <T>(key: string, defaultValue: T) => {
const data = useState<T>(key, () => defaultValue)
if (import.meta.client) {
const stored = localStorage.getItem(key)
if (stored) {
data.value = JSON.parse(stored)
}
watch(data, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
}
return data
}
const { data, error, pending, refresh } = await useFetch('/api/users')
const { data: users } = await useFetch('/api/users', {
method: 'GET',
query: { limit: 10, offset: 0 },
headers: { 'X-Custom-Header': 'value' }
})
<script setup lang="ts">
const page = ref(1)
const search = ref('')
const { data: users, pending } = await useFetch('/api/users', {
query: {
page,
search,
limit: 10
}
})
</script>
const { data: userNames } = await useFetch('/api/users', {
transform: (users) => users.map(u => u.name)
})
const { data } = await useAsyncData('dashboard', async () => {
const [users, posts, stats] = await Promise.all([
$fetch('/api/users'),
$fetch('/api/posts'),
$fetch('/api/stats')
])
return { users, posts, stats }
})
const { data, error, status } = await useFetch('/api/users')
if (error.value) {
console.error('Error:', error.value.message)
console.error('Status:', error.value.statusCode)
}
if (status.value === 'error') {
showError(error.value)
}
const { data, refresh, execute } = await useFetch('/api/users', {
immediate: false
})
await execute()
await refresh()
await refresh({ dedupe: true })
// Nuxt 5 default: Shallow reactivity
const { data } = await useFetch('/api/user')
data.value.name = 'New Name' // Won't trigger reactivity!
// Enable deep reactivity for mutations
const { data } = await useFetch('/api/user', {
deep: true
})
data.value.name = 'New Name' // Now works!
// Or refresh instead of mutating
const { data, refresh } = await useFetch('/api/user')
await $fetch('/api/user', { method: 'PATCH', body: { name: 'New Name' } })
await refresh()
const { data } = await useFetch('/api/users', {
key: 'users-list',
dedupe: 'cancel',
getCachedData: (key, nuxtApp) => {
return nuxtApp.payload.data[key]
}
})
const { data, pending } = useLazyFetch('/api/users')
const { data: data2, pending: pending2 } = useLazyAsyncData('users', () => $fetch('/api/users'))
const submitForm = async () => {
const result = await $fetch('/api/submit', {
method: 'POST',
body: formData.value
})
}
// composables/useCart.ts
interface CartItem {
id: string
name: string
price: number
quantity: number
}
export const useCart = () => {
const items = useState<CartItem[]>('cart-items', () => [])
const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
const addItem = (product: Omit<CartItem, 'quantity'>) => {
const existing = items.value.find(i => i.id === product.id)
if (existing) {
existing.quantity++
} else {
items.value.push({ ...product, quantity: 1 })
}
}
const removeItem = (id: string) => {
items.value = items.value.filter(i => i.id !== id)
}
const clearCart = () => {
items.value = []
}
return { items, total, addItem, removeItem, clearCart }
}
bun add pinia @pinia/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
})
// stores/auth.ts
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null as User | null,
token: null as string | null
}),
getters: {
isAuthenticated: (state) => !!state.user,
userName: (state) => state.user?.name ?? 'Guest'
},
actions: {
async login(email: string, password: string) {
const { user, token } = await $fetch('/api/auth/login', {
method: 'POST',
body: { email, password }
})
this.user = user
this.token = token
},
logout() {
this.user = null
this.token = null
}
}
})
// WRONG
export const useAuth = () => {
const user = ref(null)
return { user }
}
// CORRECT
export const useAuth = () => {
const user = useState('auth-user', () => null)
return { user }
}
// WRONG - Causes hydration mismatch!
const { data } = await useFetch('/api/users', {
transform: (users) => users.sort(() => Math.random() - 0.5)
})
// CORRECT
const { data } = await useFetch('/api/users', {
transform: (users) => users.sort((a, b) => a.name.localeCompare(b.name))
})
// WRONG
const { data } = await useFetch('/api/user')
data.value.name = 'New Name'
// CORRECT - Enable deep
const { data } = await useFetch('/api/user', { deep: true })
// CORRECT - Replace entire value
data.value = { ...data.value, name: 'New Name' }
// CORRECT - Refresh after mutation
await $fetch('/api/user', { method: 'PATCH', body: { name: 'New Name' } })
await refresh()
Data Not Refreshing When Params Change:
{ query: { page } } where page = ref(1).valueHydration Mismatch with useState:
useState('unique-key', () => value)Math.random() or Date.now() in initial valuesState Lost on Navigation:
useState instead of ref for persistent stateclearNuxtState not working as expected:
undefinedVersion: 5.0.0 | Last Updated: 2026-03-30 | License: MIT