Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Fetching data: useFetch

useFetch is a composable for loading data in a component. When a page is opened for the first time, it runs the request on the server, puts the result into the HTML and passes the data to the browser. The browser does not repeat the request, and the user sees a ready page right away.

The post list from the server

Replace the array in app/pages/posts/index.vue with an API request:

<!-- app/pages/posts/index.vue -->
<script setup lang="ts">
const { data: posts } = await useFetch('/api/posts')
</script>
 
<template>
  <section>
    <h2>Posts</h2>
    <PostCard v-for="post in posts" :key="post.id" :post="post" />
  </section>
</template>

useFetch returns an object; its data field is a ref with the server response, renamed to posts here. The await makes the server wait for the data before sending the HTML. Open /posts: the list comes from server/api/posts.get.ts. View the page source in the browser: the post titles are already in the HTML.

Types for free

Nuxt knows what server/api/posts.get.ts returns and infers the type of posts itself. The editor suggests the fields id, title and text, and a typo like post.titel is underlined.

The post page

On app/pages/posts/[id].vue the request address depends on the parameter:

<!-- app/pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useFetch(() => `/api/posts/${route.params.id}`)
</script>
 
<template>
  <article v-if="post">
    <h2>{{ post.title }}</h2>
    <p>{{ post.text }}</p>
  </article>
</template>

The address is passed as a function: when you go from /posts/1 to /posts/2, the function returns a new address and useFetch loads the other post. The address /posts/1 shows the first post from the storage.

Query parameters

A search string is passed with the query option. If you put a ref into it, the request repeats on every change:

<!-- app/pages/posts/index.vue -->
<script setup lang="ts">
const search = ref('')
const { data: posts } = await useFetch('/api/posts', {
  query: { search }
})
</script>
 
<template>
  <section>
    <input v-model="search" placeholder="Search titles">
    <PostCard v-for="post in posts" :key="post.id" :post="post" />
  </section>
</template>

Start typing "nuxt" and the list narrows: useFetch sends the request /api/posts?search=nuxt.

Main options

OptionWhat it does
queryParameters after ?
methodHTTP method, GET by default
pickKeep only the listed fields in the data
defaultThe value of data until a response arrives
lazyDo not wait for data before showing the page
serverfalse loads only in the browser

What changed in Nuxt 4

In Nuxt 4, data is undefined before the response arrives or after an error (in Nuxt 3 it was null). Also, data is shallowly reactive by default: replacing the whole value is tracked, but changing a field inside the object is not.

Common mistake

Writing posts.value.length in <script setup> without a check. If the server answers with an error, data is undefined, and the page crashes with Cannot read properties of undefined. Use posts.value?.length in the script, and show data inside a v-if in the template.

Thanks for reading!

Donate to the site — we work hard for you.