Nuxt data-fetching techniques: Which to use and when
20 Jan 2022
Nuxt is a fantastic option for building web projects, whether it's a full-on site or a single page app. Whatever you're building, there's a good chance that it'll need to retrieve data over AJAX at some point, e.g. from a third-party API.
Fortunately, Nuxt provides a couple of ways to do this, and in this article I'll cover how those ways compare and which to choose when, as well as looking at some alternative approaches.
The fetch hook 🔗
The fetch
hook is the most versatile of Nuxt's data retrieval methods. It can be used in any component, can run server- or client-side (more on that later) and can be re-triggered later after the component has mounted. For this reason, it has access to the component's this
object.
This makes it perfect, say, for fetching data intended for an AJAX-populated HTML table, which may need to be re-populated later if the user interacts with filters or sort controls.
The fetch
hook should return a promise, either explicitly, or implicitly via async
/await
.
export default {
data() { return {
//...
}},
async fetch() {
let url = 'https://api.nuxtjs.dev/mountains';
this.mountains = await this.$http.$get(url);
}
}
Examples in this article use Nuxt's HTTP module, but we could just as easily use the standardised Fetch API.
Once we've retrieved our data, it's up to us what we do with it. Normally, as above, we'd store it somewhere on the component's data - in our case, in an object called mountains
, which our component template can then read from to output the table content via a v-for
loop.
If we're using Vuex, we may prefer to commit it to our state object:
async fetch() {
let url = 'https://api.nuxtjs.dev/mountains';
let mountains = await this.$http.$get(url);
this.$store.commit('mountains', mountains);
}
Fetch hook considerations 🔗
As versatile as the fetch
hook is, there's a few considerations when using it.
First, at what stage in Nuxt's lifecycle and in what environment (server or client) does it run? This depends on the value of the ssr
(server-side rendering) param in your Nuxt config:
- If it's set to true, the fetch hook will run on the server - either pre-render if you're using
target: 'server'
or during static site generation (SSG) if you're usingtarget: 'static'
. - If it's set to false, it will run on the client, on route load
If you're using ssr: true
but for some reason want the fetch
hook to run on the client, not the server, you can force this via the fetchOnServer
component param.
It's also possible to set the minimum execution time the fetch hook will take, via fetchDelay
component param.
This is useful to avoid quick flashes of empty-then-populated content when your request resolves quickly. You can use this time to show a loading spinner, for example.
Also note that, by default, the fetch
hook is not called when the query string changes. This can be changed by mapping the hook to the $route.query
within the component's watch
object.
You can also tell the fetch
hook to listen to query string changes via the watchQuery
property, but this involves more overheads. We'll meet this later when we look at the asyncData
hook.
The fetch state 🔗
The fetch
hook comes with a handy means of interrogating the request state via the this.$fetchState
object. This contains three members:
pending
- a boolean that denotes whether thefetch
hook's promise has resolved yet (client-side only - i.e. ifssr: false
orfetchOnServer: false
)error
- if thefetch
hook throws an error, it can be accessed via this propertytimestamp
- the timestamp of the last time the fetch hook ran. Useful for implementing caching strategies
Like everything in Vue, we can use these bits of info in our reactivity. The pending
property is particularly useful; it allows us to show a loading spinner or wait message to the user until the data is fetched.
<template>
<div v-if='$fetchState.pending'>Please wait...</div>
<div v-else-if='$fetchState.error'>Oh no!</div>
<div v-else>
...
</div>
</template>
The asyncData hook 🔗
The asyncData
hook fetches data just like the fetch hook does, but with three important differences:
- It can be used only in page components
- It can merge response data straight into page component data
- It cannot access the component's
this
instance
asyncData
happens on route change, and is resolved before the page is rendered. This is why you don't have access to this
, because it doesn't exist at the time asyncData
runs.
Instead, the hook is passed the Nuxt context as its only argument so you can still access route info, Nuxt modules etc.
Let's fetch our mountains again, but this time via the asyncData
hook.
export default {
async asyncData() {
let url = 'https://api.nuxtjs.dev/mountains'
let mountains = await this.$http.$get(url);
return {mountains};
}
}
See how we return the response? As far as our page component is concerned, when it came to life it already had a mountains
definition in its data, because asyncData
merged into it.
Refreshing asyncData 🔗
We saw earlier that, with the fetch
hook, it's possible to refresh the fetch data simply by calling this.$fetch()
at a later time after the component is mounted.
But this won't work with asyncData
. Instead, we can either:
- Refresh the page - but this will mean hitting the server again; or, even better...
- ...call
this.$nuxt.refresh()
the refresh()
method of the Nuxt context refreshes re-renders the current page's components and, crucially for our needs, re-fires any asyncData
or fetch
hooks. But if you find yourself often needing to refresh asyncData
, you probably should be using fetch
or some other means instead. asyncData is primarily intended to pump initial data into the page.
The asyncData
hook, by default, treats route query string changes the same way the fetch
hook does - i.e. it doesn't respond to them unless you explicitly tell it to. This looks a little different from how we told the fetch
hook to re-fire on query string changes above, and involves setting the watchQuery
component property:
When the value of the someQSParam
query string param changes value, the component's methods (including fetch()
and asyncData()
) will be re-fired.
Other approaches 🔗
We've looked at the two main approaches in Nuxt to fetch data. But as ever with code, there are alternatives.
Vuex actions 🔗
First up, there's Vuex actions. It's common to handle asynchronous data retrieval within state management modules such as Vuex, and Vuex provides actions as a way to handle this.
Unlike mutations, actions can be asynchronous, which means they're perfect for fetching data. The basic flow is to fetch the data in an action, then commit it to a mutation.
//store/index.js
export const state = () => ({
mountains: []
});
export const mutations = {
setMountains(state, mountains) {
state.mountains = mountains;
}
}
export const actions = {
async setMountians(context) {
let url = 'https://api.nuxtjs.dev/mountains';
let mountains = await this.$http.$get(url);
context.commit('setMountains', mountains);
}
}
We can then 'dispatch' the action from our component when we want to get the data.
I mention this method only in passing. This isn't a tutorial on Vuex, so to learn more here's how Nuxt implements Vuex, and here's the Vuex docs themselves.
Router middleware 🔗
Secondly, we could use router middleware to fetch our data. Middleware runs before components are created, so isn't able to store the response data on the component. Instead, its only option is to store the data in state.
Suppose we wanted to get the latest mountain data for each and every page visit. We can stipulate middleware rules at any of three points in the Nuxt lifecycle:
- in the config (to match certain routes)
- in layout files (pages of a certain layout only)
- in pages (that page only)
We want our middleware to run on all visits to all pages, so we'll set up a catch-all rule in the config.
That tells Nuxt to retrieve and run the main export function in the file /middleware/getMountains.js on every page visit. In there, we can do our request:
//middleware/getMountains.js
export default async ctx => {
let url = 'https://api.nuxtjs.dev/mountains';
let mountains = await ctx.$http.$get(url);
ctx.store.commit('setMountains', mountains);
}
Et voila - our mountains data is now available everywhere just by reading the state.
Summary 🔗
Nuxt provides two main hooks for fetching asynchronous data - fetch()
and asyncData()
.
Use the fetch
hook:
- In any component, not just page components
- When you need to refresh the initial fetch data later, e.g. in response to user input
Use the asyncData
hook:
- In page components only
- To merge fetched data into the page's data
Alternatives including fetching data in Vuex actions or in router middleware.
Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!