Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

useAsyncData and $fetch

useFetch is handy when a page needs one API address. When data is put together from several requests or does not come over HTTP, use useAsyncData: it accepts any async function. Inside that function you usually call $fetch, Nuxt's function for HTTP requests.

$fetch

$fetch is a wrapper around JavaScript's fetch. It parses JSON itself, throws an error on a 4xx or 5xx response, and on the server calls its own /api/... routes directly, without a network request:

<!-- app/pages/index.vue -->
<script setup lang="ts">
async function showCount() {
  const posts = await $fetch('/api/posts')
  alert(`Posts in the blog: ${posts.length}`)
}
</script>
 
<template>
  <section>
    <button @click="showCount">How many posts?</button>
  </section>
</template>

After a click a dialog shows the number of posts. $fetch knows the response type too: posts.length is accepted without errors.

useAsyncData

useAsyncData does with any function what useFetch does with an address: it runs it on the server and passes the result to the browser.

<!-- app/pages/index.vue -->
<script setup lang="ts">
const { data: posts } = await useAsyncData('posts', () => $fetch('/api/posts'))
</script>
 
<template>
  <section>
    <h2>Home</h2>
    <p>Posts in the blog: {{ posts?.length }}</p>
  </section>
</template>

The first argument is the key under which Nuxt stores the data and passes it to the browser. The second is the loading function. The home page shows a line with the number of posts. useFetch('/api/posts') is essentially a shorthand for this call.

Several requests at once

The post page needs the post itself and its comments. Load them in parallel:

<!-- app/pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const id = route.params.id
 
const { data } = await useAsyncData(`post-${id}`, async () => {
  const [post, comments] = await Promise.all([
    $fetch(`/api/posts/${id}`),
    $fetch(`/api/posts/${id}/comments`)
  ])
  return { post, comments }
})
</script>
 
<template>
  <article v-if="data">
    <h2>{{ data.post.title }}</h2>
    <p>{{ data.post.text }}</p>
    <h3>Comments: {{ data.comments.length }}</h3>
    <p v-for="(comment, i) in data.comments" :key="i">
      <b>{{ comment.author }}:</b> {{ comment.text }}
    </p>
  </article>
</template>

Promise.all starts both requests at the same time. At /posts/1 you see the post and its comments.

What to choose

TaskTool
Data from one API address when the page opensuseFetch
Several requests, a database or custom logic when the page opensuseAsyncData with $fetch
A request on a user action: a button, a form submit$fetch

Common mistake

Calling $fetch right in <script setup>: const posts = await $fetch('/api/posts'). The page shows up, but the request runs twice, on the server while rendering and again in the browser during hydration, because $fetch does not pass its result to the browser. For page data wrap $fetch in useAsyncData or use useFetch.

Thanks for reading!

Donate to the site — we work hard for you.