Nuxt gives you three ways to load data: useFetch, useAsyncData and $fetch. They look similar, but picking the wrong one causes double requests or hydration warnings. Here is when to use each.

useFetch: page and component data#

Use it in <script setup> for data the page needs to render. It runs on the server, sends the result to the browser in the page payload, and does not fetch again during hydration.

const { data: projects, pending, error } = await useFetch('/api/projects', {
  query: { page: 1 }
})

useAsyncData: custom logic#

Use it when the data does not come from a single URL, for example when you combine two calls or use an SDK:

const { data } = await useAsyncData('home', async () => {
  const [projects, articles] = await Promise.all([
    $fetch('/api/projects'),
    $fetch('/api/articles')
  ])
  return { projects, articles }
})

Give every call a unique key. Two calls with the same key share one result.

$fetch: user actions#

Use plain $fetch inside event handlers like form submits, "Load more" buttons and search. These run only in the browser, after a click, so there is nothing to hydrate.

async function sendMessage() {
  await $fetch('/api/contact', { method: 'POST', body: form })
}

Avoid hydration mismatches#

A mismatch happens when the server and the browser render different HTML. Common causes:

  • Calling $fetch at the top of <script setup>, which fetches twice.
  • A key that changes between server and browser, such as a different base URL in the key.
  • Rendering dates or random values without a stable seed.

Quick rule#

  • Needed to render the page: useFetch.
  • Several sources or custom logic: useAsyncData.
  • After a click or submit: $fetch.