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
| Option | What it does |
|---|---|
query | Parameters after ? |
method | HTTP method, GET by default |
pick | Keep only the listed fields in the data |
default | The value of data until a response arrives |
lazy | Do not wait for data before showing the page |
server | false 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.