Suggest an editImprove this articleRefine the answer for “What is the difference between useFetch and useAsyncData in Nuxt?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**useFetch** auto-generates a cache key from the URL and options, handling simple HTTP requests with minimal code. **useAsyncData** requires a manual key but accepts any async function. ```js const { data } = await useFetch('/api/users') const { data } = await useAsyncData('users', () => $fetch('/api/users')) ``` **Key:** one URL = `useFetch`; custom logic or multiple calls = `useAsyncData`.Shown above the full answer for quick recall.Answer (EN)Image**useFetch** is a shorthand for HTTP requests with automatic cache keying by URL, while **useAsyncData** runs any async code and caches the result under a key you provide manually. ## Theory ### TL;DR - `useFetch` is like a restaurant app: give it a URL, it tracks the request and caches the result automatically. - `useAsyncData` is cooking yourself: full control over every step, you name the recipe for caching. - Main difference: `useFetch` auto-generates a cache key from the URL and options; `useAsyncData` requires you to pass a string key. - Single API endpoint? `useFetch`. Filtering, joining, or multiple calls? `useAsyncData`. - Decision shortcut: if you can write it as one URL, use `useFetch`. ### Quick example ```vue <!-- useFetch: cache key is '/api/users' --> <script setup> const { data: users, pending } = await useFetch('/api/users') </script> <!-- useAsyncData: manual key 'active-users', custom logic --> <script setup> const { data } = await useAsyncData('active-users', () => $fetch('/api/users').then(users => users.filter(u => u.active)) ) </script> ``` `useFetch` caches the raw response under the URL. `useAsyncData` caches the filtered result under `'active-users'`. Same source, different outputs, different keys. ### Key difference `useFetch` hashes the URL and request options into a unique cache key automatically, so it reacts to route changes without extra setup. `useAsyncData` uses whatever string you pass as the key, which gives room for arbitrary async code but puts the responsibility for key uniqueness on you. If two pages share the same key with different data shapes, the last fetch wins and you get stale output. ### When to use - Fetching a single endpoint directly: `useFetch('/api/posts')` - Dynamic route data that changes with the URL: `useFetch` (reacts automatically) - Filtering or transforming the response: `useAsyncData` (wrap `$fetch` in your logic) - Multiple parallel requests: `useAsyncData` with `Promise.all` inside the handler - Client-only data like WebSocket state: `useAsyncData` with `server: false` ### Comparison table | Feature | useFetch | useAsyncData | |---|---|---| | Cache key | Auto (URL + options hash) | Manual (required string) | | Handler | URL string | Any async function | | Reactivity | Auto on route change | Manual via key | | Default fetcher | `$fetch` | None, provide your own | | Lazy mode | Built-in option | Built-in option | | SSR payload | Auto-included | Auto-included if key is unique | | Best for | Quick API calls like `/api/posts` | Filter, join, or complex logic | ### How caching works Both composables register async tasks with Nuxt's SSR renderer, suspend page render until the handler resolves, and serialize results into the HTML payload for hydration. `useFetch` passes the result of `hash(url + options)` as the key into `useNuxtApp().payload.data`. `useAsyncData` injects your string directly into the same store. On the client, if the key already exists in the payload, no second request fires. ### Common mistakes **Missing key in useAsyncData** ```js // Wrong - key is required in Nuxt 3 const { data } = await useAsyncData(async () => $fetch('/api/posts')) // Correct const { data } = await useAsyncData('posts', async () => $fetch('/api/posts')) ``` **Static key for dynamic data** ```js // Wrong - id changes but key stays 'posts', so you always get the first result const { data } = await useAsyncData('posts', () => $fetch(`/api/posts/${id}`)) // Correct - key changes with id const { data } = await useAsyncData(`posts-${id}`, () => $fetch(`/api/posts/${id}`)) ``` This is the mistake I see most often in code reviews. People notice data does not update when navigating between records, add a `watch`, and still wonder why it breaks. The key was the issue the whole time. **Using useFetch when you need to transform the response** ```js // Works, but data is the raw response with no filtering const { data } = await useFetch('/api/search', { body: { q: 'nuxt' } }) // Better when you need to parse or filter const { data } = await useAsyncData('search-nuxt', () => $fetch('/api/search', { method: 'POST', body: { q: 'nuxt' } }) .then(res => res.results.filter(r => r.published)) ) ``` ### Real-world usage - Nuxt Content: `useAsyncData('content', () => queryContent().find())` - Nuxt Auth (sidebase): `useFetch('/api/auth/me')` for session data - Supabase + Nuxt: `useAsyncData(`posts-${userId}`, () => supabase.from('posts').select())` for row-level security - Cache invalidation after mutation: `refreshNuxtData('key')` to refetch, `clearNuxtData('key')` to drop the value ### Follow-up questions **Q:** What happens if two components use `useAsyncData` with the same key but different handlers? **A:** The second call reuses the cached result from the first. Intentional for shared data, but if the handlers differ you get wrong output. Always use unique keys per data shape. **Q:** Can `useFetch` handle POST requests? **A:** Yes. Pass `method: 'POST'` and `body` as options. But if you also need to transform the response, switching to `useAsyncData` keeps the intent clearer. **Q:** What does `server: false` do in `useAsyncData`? **A:** It skips the handler during SSR and only runs it on the client. Use this for data that depends on browser APIs or should not land in the HTML payload, like live dashboard widgets. **Q:** In a large app, how do you invalidate `useAsyncData` cache after a mutation? **A:** Call `refreshNuxtData('key')` to trigger a refetch, or `clearNuxtData('key')` to remove the cached value entirely. Pair this with the `refresh()` method returned from `useFetch` for optimistic updates. **Q:** How does `useFetch` handle reactive query params? **A:** Pass a reactive object to the `query` option. `useFetch` watches it and refetches automatically when any value changes, which is one reason it beats a raw `$fetch` call inside a `watchEffect`. ## Examples ### Product list by dynamic category ```vue <!-- pages/products/[category].vue --> <script setup> const route = useRoute() // Cache key becomes '/api/products/electronics?limit=20&sort=price' // Refetches automatically when route.params.category changes const { data: products, pending } = await useFetch( `/api/products/${route.params.category}`, { query: { limit: 20, sort: 'price' } } ) </script> <template> <div v-if="pending">Loading...</div> <ul v-else> <li v-for="p in products" :key="p.id">{{ p.name }}</li> </ul> </template> ``` `useFetch` is enough here because the URL already captures all variation. No manual key, no watcher needed. ### Dashboard with parallel requests and partial failure handling ```vue <script setup> const { data: userId } = useAuth() // your auth composable // Unique key per user, parallel fetches, handles partial failures const { data: dashboard, error } = await useAsyncData( `dashboard-${userId.value}`, async () => { const [stats, recent, notifications] = await Promise.allSettled([ $fetch('/api/stats'), $fetch('/api/recent-activity'), $fetch('/api/notifications') ]) // Stats are non-negotiable - throw if missing if (stats.status === 'rejected') throw new Error('Stats unavailable') return { stats: stats.value, recent: recent.status === 'fulfilled' ? recent.value : [], notifications: notifications.status === 'fulfilled' ? notifications.value : [] } }, { default: () => null } ) </script> ``` `useAsyncData` handles this because three separate calls, a merge step, and per-user caching are not what `useFetch` was designed for. The key includes `userId` so each user gets their own cached payload. For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.