
Choose among Nuxt’s $fetch, useFetch, useAsyncData, and lazy variants based on rendering, interaction, and data needs, while avoiding common pitfalls with keys, caching, SSR, and payloads.
Reza Baar
November 10, 2026
Work down the list and stop at the first yes.
Is it a user action or a side effect (submitting, deleting, clicking)?
Use $fetch in the handler. There is no render to wait for.
Does it need to be in the HTML on first paint, for SEO or to avoid a loading flash?
useFetch if the source is a URL. useAsyncData if it is an SDK, a DB, or several calls composed together.
Can the page render before this data arrives?
Use useLazyFetch or useLazyAsyncData, then handle the loading state in the template.
Is it browser only, user specific, and not needed for SEO?
Add server: false to whichever you picked above.
Are you already inside a handler, a server route, or a plugin?
Use $fetch. Never nest one data composable inside another.
| What it is | SSR | Deduped by key | In payload | |
|---|---|---|---|---|
useFetch(url, opts) | useAsyncData plus $fetch, auto key | Yes | Yes | Yes |
useAsyncData(key, fn, opts) | Wraps any async function | Yes | Yes | Yes |
useLazy* variants | Same, with lazy: true | Yes, non blocking | Yes | Yes |
$fetch(url, opts) | Configured ofetch instance | Only where called | No | No, refetches on hydration |
During SSR, $fetch to your own /api/* hits the handler directly, no HTTP roundtrip.
| Option | Default | Notes |
|---|---|---|
key | auto for useFetch | Cache and dedup identity. Required for useAsyncData |
server | true | false skips SSR |
lazy | false | true does not block navigation |
immediate | true | false waits for execute() |
watch | reactive opts | Array of sources, or false to opt out |
transform | none | Reshapes before caching. Shrinks the payload |
pick | none | Keeps listed top level keys |
default | null | Give it [] or {} to skip null guards |
dedupe | cancel | defer keeps the first call instead |
deep | false | data is a shallow ref. true only if you mutate nested props |
getCachedData | payload reuse | Reuses on hydration and client nav, not on manual refresh() |
const { data, status, error, refresh, execute, clear } = await useFetch('/api/products')
data | Result, or default until resolved |
status | idle, pending, success, error. Branch on this, not on data |
error | Thrown error, resets to null on successful retry |
refresh() | Refetch, keeps current data visible |
execute() | For immediate: false |
clear() | Resets state for that key |
await is optional. The server waits before rendering either way.
Export from composables/. Both are compiler macros, so Nuxt injects dedup keys at build time.
// app/composables/useAPI.ts
export const useAPI = createUseFetch({
baseURL: 'https://api.example.com',
onRequest ({ options }) {
const { session } = useUserSession()
if (session.value?.token) {
options.headers.set('Authorization', `Bearer ${session.value.token}`)
}
},
async onResponseError ({ response }) {
if (response.status === 401) await navigateTo('/login')
},
})
const { data: profile } = await useAPI('/me')
Function form when defaults need composables or runtime config:
export const useApiFetch = createUseFetch(callerOptions => ({
...callerOptions,
baseURL: callerOptions.baseURL ?? useRuntimeConfig().public.baseApiUrl,
}))
createUseAsyncData works the same way. Best home for a shared getCachedData policy.useFetch. Reserved and compiler transformed.computed, ref, or getter is enough.signal from an AbortController for manual cancellation.dedupe already cancels the in flight request for the same key.clearNuxtData(key) drops the cache so the next mount refetches. Use after a mutation.$fetch in <script setup> fetches twice. Nothing enters the payload.$fetch inside handlers.v-for. Pass key./products/1 and /products/2 share stale data.transform does not run on a cached read. What was cached is already transformed.pick or transform.data returning a string means an accidental import { useFetch } from '@vueuse/core'.await in setup, context is lost.Testable now with future.compatibilityVersion: 5.
clear() and clearNuxtData() restore your default instead of setting data to undefined.clearNuxtState() resets to the useState init value, matching clearNuxtData.Ready to prove your Nuxt skills?
Get certified in NuxtKeep learning Nuxt. Then get certified when you are ready.

Type-Safe Server Routes
Type-Safe Server Routes: End-to-End Types from server/api to Your Components How Nuxt infers response types from your server routes so useFetch and $fetch calls are fully typed without manual interfaces.
Reza Baar
Sep 16, 2026

Building an MCP Server in Your Nuxt App
Building an MCP Server in Your Nuxt App with the MCP Toolkit. How to expose your Nuxt app's data and actions to AI assistants using @nuxtjs/mcp-toolkit, with tools, resources, and prompts.
Reza Baar
Sep 2, 2026

Background Jobs with Nitro Tasks API
How to run one-off and scheduled background work in Nuxt using Nitro's Tasks API, and what to watch for per deployment platform.
Reza Baar
Aug 19, 2026