Universal data fetching in Nuxt.js
AsyncData vs Fetch vs Middlewares vs Vue Hooks
There are several ways to fetch data in a Nuxt application. On top of using the traditional Vue.js hooks like mounted, there are additional lifecycle events that leverage Nuxt ability to load data during Server-side rendering. This allows your page to render with all of its required data available which improves performance and overall user experience.
The problem with multiple ways to fetch data on the server is that the differences between them sometimes cause confusion. In addition, a change to the fetch hook was introduced during Nuxt version 2.12 which added another layer of complexity.
Mounted hook
Let's get this out of the way first. Fetching data during component initialization is still possible. This way the data are fetched on the client and the page load is not blocked. This is useful for nonessential data and does not affect server response time at all.
AsyncData
This is a special Nuxt lifecycle hook available only in page components. It will be triggered during server-side rendering before the component instance is created, and on the client when navigating. Navigation until the API call is fulfilled will be blocked. It has access to the Nuxt context (which also contains the store instance) but not in the component instance (this)
which is the main difference with fetch hook. The return value of this function will be shallow merged with the component data.
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.description }}</p>
</div>
</template>
<script>
export default {
async asyncData({ params, $http }) { // This could also be an action dispatch
const post = await $http.$get(`/posts/${params.id}`) return {
post
}
}
}
</script>
Legacy Fetch — (Before 2.12)
The old fetch hook worked much like asyncData does. It’s still available for backward compatibility when a context argument is used. The main difference between them is that fetch runs after component initialization which means that it has access to the component instance. The fetch hook is not blocking which means route navigation will happen and a loading state needs to be handled by the component.
Both asyncData and legacy fetch have access to the store and can only be placed in page-level components.
New Fetch — (After 2.12)
The updated fetch hook is another way to make API calls during server-side rendering but can be placed in any component of a page.
When called during page navigation or client-side rendering, it is not blocking and exposes $fetchState at the component level for rendering loading states and errors.
The $fetchState
anatomy
pending
is a Boolean that allows you to display a placeholder when fetch is being called on the client-side.error
is either null or an Error thrown by the fetch hook- the
timestamp
is a timestamp of the last fetch, useful for caching with keep-alive
<template>
<p v-if="$fetchState.pending">Fetching mountains...</p>
<p v-else-if="$fetchState.error">An error occurred :(</p>
<div v-else>
<h1>Nuxt Mountains</h1>
<ul>
<li v-for="mountain of mountains">{{ mountain.title }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
mountains: []
}
},
async fetch() { // This could also be an action dispatch
this.mountains = await fetch(
'https://api.nuxtjs.dev/mountains'
).then(res => res.json()) }
}
</script>
In addition, you can manually call fetch in your component (to e.g. reload its async data) by calling this.$fetch()
.
Additional options
fetchOnServer
(default:true
): When changing this to false fetch will only be called on the client behaving like fetching data in the mounted hook.fetchDelay
(default:200
): Fetching delay in milliseconds
If multiple components of a page have a fetch hook they will be called in parallel.
Middlewares
Middlewares are custom functions that can be run before rendering either a page or a layout. They also accept the context as a parameter which means they have access to the store and can make API calls. The only downside in using them is that multiple middlewares run in sequence and if they contain API calls they won’t be done in parallel.
import http from 'http'
export default function ({ route }) {
return http.post('http://my-stats-api.com', {
url: route.fullPath
})
}
NuxtServerInit
This is a special hook that runs during Vuex store initialization. This is the best place to fetch data that needs to be available across the whole application, like configurations, feature toggles, authentication info, etc.
Conclusion
A lot of options but in reality, it’s pretty straightforward:
If you want to fetch configuration data that need to be globally available during the initial load then use the NuxtServerInit hook.
If you want to fetch data on the server and block navigation during client-side rendering then use AsyncData
If you want to fetch data on the server without blocking navigation during client-side rendering then use Fetch. (If you are using Nuxt v2.12+ you have the flexibility to place this hook in a component level and leverage the $fetchState
helper.)
If you want to fetch the same data on multiple pages which don’t share the same layout you might want to consider a middleware.