15 KiB
navigation.icon | description |
---|---|
uil:channel | Nuxt provides composables to handle data fetching within your application. |
Data fetching
Nuxt comes with two composables and a built-in library to perform data-fetching in browser or server environments: useFetch
, useAsyncData
and $fetch
.
Used together, they ensure cross-environment compatibility and efficient caching and avoid duplicate network calls.
useFetch
is the most straightforward way to handle data fetching in a component setup function.
On the other hand, when wanting to make a network request based on user interaction, $fetch
is almost always the right handler to go for.
If you need more fine-grained control, you can use useAsyncData
and $fetch
independently.
The two composables share a common set of options and patterns that we will detail in the last sections.
Why using specific composables?
When using a framework like Nuxt that can perform calls and render pages on both client and server environments, some challenges must be addressed. This is why Nuxt provides composables to wrap your queries.
Network calls duplication
The useFetch
and useAsyncData
composables ensure that once an API call is made on the server, the data is properly forwarded to the client in the payload. This JavaScript object is accessible through useNuxtApp().payload
and is used on the client to avoid refetching the same data when the code is executed in the browser.
::alert{icon=⚙️} Use the Nuxt DevTools to inspect this data in the payload tab. ::
Suspense
Nuxt uses Vue’s <Suspense>
component under the hood to prevent navigation before every async data is available to the view. The data fetching composables can help you leverage this feature and use what suits best on a per-calls basis.
::alert{icon=👉}
These composables are auto-imported and can be used in setup
functions or lifecycle hooks
::
useFetch
useFetch
is the most straightforward way to perform data fetching. It is a wrapper around the useAsyncData
composable and $fetch
utility.
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
<template>
Page visits: {{ count }}
</template>
::ReadMore{link="/docs/api/composables/use-fetch"} ::
::LinkExample{link="/docs/examples/features/data-fetching"} ::
$fetch
The ofetch
library is built on top of the fetch
API and adds handy features to it:
- Works the same way in browser, Node or worker environments
- Automatic response parsing
- Error handling
- Auto-retry
- Interceptors
::alert{icon=📘} Read the full documentation of ofetch ::
ofetch is auto-imported by Nuxt and used by the useFetch
composable.
It can also be used in your whole application with the $fetch
alias:
const users = await $fetch('/api/users').catch((error) => error.data)
::alert{type="warning"}
Beware that using only $fetch
will not provide the benefits described in the first section of this page. It is recommended to use $fetch
when posting data to an event handler, when doing client-side only logic, or combined with useAsyncData
.
::
::ReadMore{link="/docs/api/utils/dollarfetch"} ::
useAsyncData
useFetch
receives a URL and gets that data, whereas useAsyncData
might have more complex logic. useFetch(url)
is nearly equivalent to useAsyncData(url, () => $fetch(url))
- it's developer experience sugar for the most common use case.
There are some cases when using the useFetch
composable is not appropriate, for example when a CMS or a third-party provide their own query layer. In this case, you can use useAsyncData
to wrap your calls and still keep the benefits provided by the composable:
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))
::alert{icon=👉}
The first argument of useAsyncData
is the unique key used to cache the response of the second argument, the querying function. This argument can be ignored by directly passing the querying function. In that case, it will be auto-generated.
::
::ReadMore{link="/docs/api/composables/use-async-data"} ::
Options
useAsyncData
and useFetch
return the same object type and accept a common set of options as their last argument. They can help you control the composables behavior, such as navigation blocking, caching or execution.
Lazy
By default, data fetching composables will wait for the resolution of their asynchronous function before navigating to a new page by using Vue’s Suspense. This feature can be ignored on client-side navigation with the lazy
option. In that case, you will have to manually handle loading state using the pending
value.
<script setup lang="ts">
const { pending, data: posts } = useFetch('/api/posts', {
lazy: true
})
</script>
<template>
<!-- you will need to handle a loading state -->
<div v-if="pending">
Loading ...
</div>
<div v-else>
<div v-for="post in posts">
<!-- do something -->
</div>
</div>
</template>
You can alternatively use useLazyFetch
and useLazyAsyncData
as convenient methods to perform the same.
const { pending, data: posts } = useLazyFetch('/api/posts')
::ReadMore{link="/docs/api/composables/use-lazy-fetch"} ::
::ReadMore{link="/docs/api/composables/use-lazy-async-data"} ::
Client-only fetching
By default, data fetching composables will perform their asynchronous function on both client and server environments. Set the server
option to false
to only perform the call on the client-side. On initial load, the data will not be fetched before hydration is complete so you have to handle a pending state, though on subsequent client-side navigation the data will be awaited before loading the page.
Combined with the lazy
option, this can be useful for data that is not needed on the first render (for example, non-SEO sensitive data).
/* This call will only be performed on the client */
const { pending, data: posts } = useFetch('/api/comments', {
lazy: true,
server: false
})
The useFetch
composable is meant to be invoked in setup method or called directly at the top level of a function in lifecycle hooks, otherwise you should use $fetch
method.
Minimize payload size
The pick
option helps you to minimize the payload size stored in your HTML document by only selecting the fields that you want returned from the composables.
<script setup lang="ts">
/* only pick the fields used in your template */
const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'] })
</script>
<template>
<h1>{{ mountain.title }}</h1>
<p>{{ mountain.description }}</p>
</template>
If you need more control or map over several objects, you can use the transform
function to alter the result of the query.
const { data: mountains } = await useFetch('/api/mountains', {
transform: (mountains) => {
return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
}
})
Caching and refetching
Keys
useFetch
and useAsyncData
use keys to prevent refetching the same data.
useFetch
uses the provided URL as a key. Alternatively, akey
value can be provided in theoptions
object passed as a last argument.useAsyncData
uses its first argument as a key if it is a string. If the first argument is the handler function that performs the query, then a key that is unique to the file name and line number of the instance ofuseAsyncData
will be generated for you.
::alert{icon=📘}
To get the cached data by key, you can use useNuxtData
::
Refresh and execute
If you want to fetch or refresh data manually, use the execute
or refresh
function provided by the composables. (execute
is an alias for refresh
that works in exactly the same way but is more semantic for cases when immediate: false
).
<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>
<template>
<div>
<p>{{ data }}</p>
<button @click="refresh">Refresh data</button>
</div>
</template>
::alert{icon=📘}
To globally refetch or invalidate cached data, see clearNuxtData
and refreshNuxtData
.
::
Watch
To re-run your fetching function each time other reactive values in your application change, use the watch
option.
const { data, error, refresh } = await useFetch('/api/users', {
/* Changing the id will trigger a refetch */
watch: [id]
})
const id = ref(1)
Passing Headers and cookies
When we call $fetch
in the browser, user headers like cookie
will be directly sent to the API. But during server-side-rendering, since the $fetch
request takes place 'internally' within the server, it doesn't include the user's browser cookies, nor does it pass on cookies from the fetch response.
Pass Client Headers to the API
We can use useRequestHeaders
to access and proxy cookies to the API from server-side.
The example below adds the request headers to an isomorphic $fetch
call to ensure that the API endpoint has access to the same cookie
header originally sent by the user.
<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])
const { data } = await useFetch('/api/me', { headers })
</script>
::alert{type="warning"} Be very careful before proxying headers to an external API and just include headers that you need. Not all headers are safe to be bypassed and might introduce unwanted behavior. Here is a list of common headers that are NOT to be proxied:
host
,accept
content-length
,content-md5
,content-type
x-forwarded-host
,x-forwarded-port
,x-forwarded-proto
cf-connecting-ip
,cf-ray
::
Pass Cookies From Server-side API Calls on SSR Response
If you want to pass on/proxy cookies in the other direction, from an internal request back to the client, you will need to handle this yourself.
import { appendResponseHeader, H3Event } from 'h3'
export const fetchWithCookie = async (event: H3Event, url: string) => {
const res = await $fetch.raw(url)
const cookies = (res.headers.get('set-cookie') || '').split(',')
for (const cookie of cookies) {
appendResponseHeader(event, 'set-cookie', cookie)
}
return res._data
}
<script setup lang="ts">
// This composable will automatically pass cookies to the client
const event = useRequestEvent()
const result = await fetchWithCookie(event, '/api/with-cookie')
onMounted(() => console.log(document.cookie))
</script>
Options API support
Nuxt 3 provides a way to perform asyncData
fetching within the Options API. You must wrap your component definition within defineNuxtComponent
for this to work.
<script>
export default defineNuxtComponent({
/* Use the fetchKey option to provide a unique key */
fetchKey: 'hello',
async asyncData () {
return {
hello: await $fetch('/api/hello')
}
}
})
</script>
::Alert
Using <script setup lang="ts">
is the recommended way of declaring Vue components in Nuxt 3.
::
::ReadMore{link="/docs/api/utils/define-nuxt-component"} ::
Serialization
When fetching data from the server
directory, the response is serialized using JSON.stringify
. However, since serialization is limited to only JavaScript primitive types, Nuxt does its best to convert the return type of $fetch
and useFetch
to match the actual value.
::alert{icon=👉}
You can learn more about JSON.stringify
limitations here.
::
Example
export default defineEventHandler(() => {
return new Date()
})
<script setup lang="ts">
// Type of `data` is inferred as string even though we returned a Date object
const { data } = await useFetch('/api/foo')
</script>
Custom serializer function
To customize the serialization behavior, you can define a toJSON
function on your returned object. If you define a toJSON
method, Nuxt will respect the return type of the function and will not try to convert the types.
export default defineEventHandler(() => {
const data = {
createdAt: new Date(),
toJSON() {
return {
createdAt: {
year: this.createdAt.getFullYear(),
month: this.createdAt.getMonth(),
day: this.createdAt.getDate(),
},
}
},
}
return data
})
<script setup lang="ts">
// Type of `data` is inferred as
// {
// createdAt: {
// year: number
// month: number
// day: number
// }
// }
const { data } = await useFetch('/api/bar')
</script>
Using an alternative serializer
Nuxt does not currently support an alternative serializer to JSON.stringify
. However, you can return your payload as a normal string and utilize the toJSON
method to maintain type safety.
In the example below, we use superjson as our serializer.
import superjson from 'superjson'
export default defineEventHandler(() => {
const data = {
createdAt: new Date(),
// Workaround the type conversion
toJSON() {
return this
}
}
// Serialize the output to string, using superjson
return superjson.stringify(data) as unknown as typeof data
})
<script setup lang="ts">
import superjson from 'superjson'
// `date` is inferred as { createdAt: Date } and you can safely use the Date object methods
const { data } = await useFetch('/api/superjson', {
transform: (value) => {
return superjson.parse(value as unknown as string)
},
})
</script>