What Causes This Warning?
This warning appears when useFetch cannot create a unique cache key from the request body. This typically happens with non-serializable data like File objects, circular references, or complex objects.
The Problem
<script setup>
// ❌ Wrong - File objects can't be hashed
const file = ref<File | null>(null)
const { data } = await useFetch('/api/upload', {
method: 'POST',
body: { file: file.value } // File can't be serialized for cache key
})
// ❌ Wrong - Circular reference
const obj = { name: 'test' }
obj.self = obj // Circular!
const { data } = await useFetch('/api/data', {
method: 'POST',
body: obj
})
// ❌ Wrong - Function in body
const { data } = await useFetch('/api/data', {
method: 'POST',
body: {
callback: () => console.log('test') // Functions can't be serialized
}
})
</script>
The Fix
Provide a Custom Key
<script setup>
const file = ref<File | null>(null)
// ✅ Correct - provide unique key manually
const { data } = await useFetch('/api/upload', {
method: 'POST',
body: { file: file.value },
key: `upload-${file.value?.name}-${file.value?.size}`
})
</script>
Use $fetch for Non-Cacheable Requests
<script setup>
const file = ref<File | null>(null)
// ✅ Correct - use $fetch directly (no caching)
async function uploadFile() {
const formData = new FormData()
formData.append('file', file.value!)
const result = await $fetch('/api/upload', {
method: 'POST',
body: formData
})
return result
}
</script>
Serialize Complex Objects
<script setup>
const complexData = ref({
nested: { deeply: { value: 1 } },
date: new Date()
})
// ✅ Correct - create serializable version for key
const serializedKey = computed(() =>
JSON.stringify({
nested: complexData.value.nested,
date: complexData.value.date.toISOString()
})
)
const { data } = await useFetch('/api/data', {
method: 'POST',
body: complexData,
key: `data-${serializedKey.value}`
})
</script>
Common Scenarios
File Upload
<script setup>
const file = ref<File | null>(null)
// ✅ Best practice - use $fetch for file uploads
async function handleUpload() {
if (!file.value) return
const formData = new FormData()
formData.append('file', file.value)
try {
const result = await $fetch('/api/upload', {
method: 'POST',
body: formData
})
console.log('Uploaded:', result)
} catch (error) {
console.error('Upload failed:', error)
}
}
</script>
<template>
<input type="file" @change="e => file = e.target.files?.[0]" />
<button @click="handleUpload">Upload</button>
</template>
Dynamic Form Data
<script setup>
const form = reactive({
name: '',
email: '',
items: []
})
// ✅ Use a computed key based on stable values
const formKey = computed(() => `form-${form.name}-${form.email}`)
const { data, execute } = useFetch('/api/submit', {
method: 'POST',
body: form,
key: formKey,
immediate: false
})
async function submitForm() {
await execute()
}
</script>
With Timestamps
<script setup>
const payload = ref({
data: 'some data',
timestamp: new Date()
})
// ✅ Convert Date to string for hashing
const { data } = await useFetch('/api/data', {
method: 'POST',
body: computed(() => ({
...payload.value,
timestamp: payload.value.timestamp.toISOString()
})),
key: `data-${payload.value.timestamp.getTime()}`
})
</script>
Avoiding Cache Entirely
<script setup>
// ✅ Disable caching for POST requests
const { data } = await useFetch('/api/action', {
method: 'POST',
body: { action: 'trigger' },
getCachedData: () => null // Never use cache
})
</script>
Understanding the Cache Key
Nuxt creates cache keys by hashing:
- URL
- Method
- Query parameters
- Request body (for POST/PUT/PATCH)
When the body can’t be hashed, you need to provide a manual key.
<script setup>
// How Nuxt builds the key internally (simplified)
const autoKey = computed(() => {
return hash({
url: '/api/data',
method: 'POST',
body: { /* serializable object */ }
})
})
// When body isn't serializable, provide your own
const manualKey = computed(() => `my-unique-key-${someStableValue}`)
</script>
Best Practices
For Mutations, Use $fetch
<script setup>
// ✅ Mutations (POST/PUT/DELETE) often don't need caching
const createUser = async (userData) => {
return await $fetch('/api/users', {
method: 'POST',
body: userData
})
}
const updateUser = async (id, userData) => {
return await $fetch(`/api/users/${id}`, {
method: 'PUT',
body: userData
})
}
</script>
For Queries, Use useFetch
<script setup>
// ✅ Queries (GET) benefit from caching
const { data: users } = await useFetch('/api/users')
// With query params (auto-hashed)
const { data: filteredUsers } = await useFetch('/api/users', {
query: { status: 'active' }
})
</script>
Hybrid Approach
<script setup>
// Query with useFetch (cached)
const { data: users, refresh } = await useFetch('/api/users')
// Mutation with $fetch (not cached)
async function addUser(userData) {
await $fetch('/api/users', {
method: 'POST',
body: userData
})
// Refresh the cached query
await refresh()
}
</script>
Quick Checklist
- Check if body contains File, Blob, or non-serializable types
- Use
keyoption to provide manual cache key - Use
$fetchdirectly for file uploads - Convert Date objects to strings
- Remove functions/circular references from body
- Consider
getCachedData: () => nullto disable caching