Understanding how fetch works in Nuxt 2.12
Nuxt introduces a new fetch
with the latest release of version 2.12. Fetch provides a brand new way to bring data into Nuxt applications.
In this post, we will explore different features of the fetch hook and try to understand how it works.
Fetch Hook and Nuxt Lifecycle
In terms of Nuxt lifecycle hooks, fetch
sits within Vue lifecycle after created
hook. As we already know that, all Vue lifecycle hooks are called with their this
context. The same applies to fetch
hook as well.
Fetch hook is called after the component instance is created on the server-side. That makes this
context available inside the fetch
.
export default {
fetch() {
console.log(this)
}
}
Let’s see what this could mean for page components.
Page Components
With the help of this
context, fetch is able to mutate component’s data directly. It means we can set the component’s local data without having to dispatch Vuex store action or committing mutation from the page component.
As a result, Vuex becomes optional, but not impossible. We can still use this.$store
as usual to access Vuex store if required.
Availability of fetch hook
With fetch
, we can prefetch the data asynchronously in any Vue components. It means, other than page components found in /pages
directory, every other .vue
components found in /layouts
and /components
directories can also benefit from the fetch hook.
Let's see what this could mean for layout and building-block components.
Layout Components
Using new fetch
, now we can make API calls directly from the layout components. This was impossible prior to the release of v2.12.
Possible use cases
- Fetch config data from the back-end in Nuxt layouts to generate footer and navbar dynamically
- Fetch user related data (i.e. user profile, shopping-cart item count) in the navbar
- Fetch site relevant data on
layouts/error.vue
Building-block (Child/Nested) Components
With fetch
hook available in child components as well, we can off-load some of the data-fetching tasks from page-level components, and delegate them over to nested components. This was also impossible prior to the release of v2.12.
This reduces the responsibility of route-level components to a great extent.
Possible use case - We can still pass props to child components, but if the child components need to have their own data-fetching logic, now they can!
Call order of multiple fetch hooks
Since each component can have its own data-fetching logic, you may ask what would be the order in which each of them are called?
Fetch hook is called on server-side once (on the first request to the Nuxt app) and then on client-side when navigating to further routes. But since we can define one fetch hook for each component, fetch hooks are called in sequence of their hierarchy.
Disabling fetch on server-side
In addition, we can even disable fetch on the server-side if required.
export default {
fetchOnServer: false
}
And this way, the fetch hook will only be called on client-side. When fetchOnServer
is set to false, $fetchState.pending
becomes true
when the component is rendered on server-side.
Error Handling
New fetch
handles error at component level. Let’s see how.
Because we’re fetching data asynchronously, the new fetch() provides a $fetchState
object to check whether the request has finished and progressed successfully.
Below is what the $fetchState
object looks like.
$fetchState = {
pending: true | false,
error: null | {},
timestamp: Integer
};
We have three keys,
- Pending - lets you display a placeholder when fetch is being called on client-side
- Error - lets you show an error message
- Timestamp - shows timestamp of the last fetch which is useful for caching with
keep-alive
These keys are then used directly in the template area of the component to show relevant placeholders during the process of fetching data from the API.
<template>
<div>
<p v-if="$fetchState.pending">Fetching posts...</p>
<p v-else-if="$fetchState.error">Error while fetching posts</p>
<ul v-else>
…
</ul>
</div>
</template>
When error occurs at component-level, we can set HTTP status code on server-side by checking process.server
in fetch hook and follow it up with throw new Error()
statement.
async fetch() {
const post = await fetch(`https://jsonplaceholder.typicode.com/posts/${this.$route.params.id}`)
.then((res) => res.json())
if (post.id === this.$route.params.id) {
this.post = post
} else {
// set status code on server and
if (process.server) {
this.$nuxt.context.res.statusCode = 404
}
// use throw new Error()
throw new Error('Post not found')
}
}
Setting the HTTP status code this way is useful for correct SEO.
Fetch as a method
New fetch hook also acts as a method that can be invoked upon user interaction or invoked programmatically from the component methods.
<!-- from template in template -->
<button @click="$fetch">Refresh Data</button>
// from component methods in script section
export default {
methods: {
refresh() {
this.$fetch()
}
}
}
Making Nuxt pages more performant
We can use :keep-alive-props
prop and activated
hook to make Nuxt page components more performant using a new fetch hook.
Nuxt allows caching a certain number of pages in the memory along with their fetched data. And also allows adding a number of seconds before we can re-fetch the data.
For any of the above methods to work, we have to use the keep-alive
prop in generic <nuxt />
and <nuxt-child
> components.
<template>
<div>
<nuxt keep-alive />
</div>
</template>
In addition, we can pass :keep-alive-props
to <nuxt />
component to cache a number of pages along with their fetched data.
:keep-alive-props
prop allow us to indicate the maximum number of pages that should be kept in the memory while we navigate elsewhere within the site.
<nuxt keep-alive :keep-alive-props="{ max: 10 }" />
Above is one way to boost page performance which is more high-level and generic, while the next one drills down to optimize the fetch request calls by using the timestamp
property of $fetchState
and comparing it against the number of seconds delay before it re-fetches the data.
Vue’s activated
hook is used here with Nuxt's keep-alive
prop to re-fetch the data.
export default {
activated() {
// Call fetch again if last fetch more than a minute ago
if (this.$fetchState.timestamp <= Date.now() - 60000) {
this.$fetch()
}
}
}
asyncData vs Fetch
As far as page components are concerned, new fetch
seems way too similar to asyncData()
because they both deal with the local data. But there are some key differences worth taking note of as below.
As of Nuxt 2.12, asyncData
method is still an active feature. Let’s examine some of the key differences between asyncData
and new fetch
.
asyncData
asyncData
is limited to only page-level componentsthis
context is unavailable- Adds payload by returning the data
export default {
async asyncData(context) {
const data = await context.$axios.$get(
`https://jsonplaceholder.typicode.com/todos`
)
// `todos` does not have to be declared in data()
return { todos: data.Item }
// `todos` is merged with local data
}
}
New Fetch
fetch
is available in all Vue componentsthis
context is available- Simply mutates the local data
export default {
data() {
return {
todos: []
}
},
async fetch() {
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com/todos`
)
// `todos` has to be declared in data()
this.todos = data
}
}
Fetch before Nuxt 2.12
If you have been working with Nuxt for a while, then you’ll know that the previous version of fetch
was significantly different.
Is this a breaking change?
No, it isn't. Actually, the old fetch can still be used by passing the
context
as the first argument to avoid any breaking changes in your existing Nuxt applications.
Here’s the list of notable changes in fetch
hook compared with before and after v2.12.
1. Call order of fetch
hook
Before - fetch
hook was called before initiating the component, hence this
wasn’t available inside the fetch hook.
After - fetch
is called after the component instance is created on the server-side when the route is accessed.
2. this
vs context
Before - We had access to the Nuxt context
on page-level components, given that the context
is passed as a first parameter.
export default {
fetch(context) {
// …
}
}
After - We can access this
context just like Vue client-side hooks without passing any parameters.
export default {
fetch() {
console.log(this)
}
}
3. Availability of fetch
hook
Before - Only page (route-level) components were allowed to fetch data on the server-side.
After - Now, we can prefetch the data asynchronously in any Vue components.
4. Call order of fetch
hook
Before - fetch
could be called server-side once (on the first request to the Nuxt app) and client-side when navigating to further routes.
After - New fetch
is the same as an old fetch, but…
…since we can have one fetch
for each component, fetch
hooks are called in sequence of their hierarchy.
5. Error Handling
Before - We used the context.error
function that showed a custom error page when an error occurred during API calls.
After - New fetch
uses the $fetchState
object to handle errors in the template area during API calls.
Error handling is performed at component level.
Does this mean we cannot show users a custom error page like we did prior to Nuxt 2.12?
Yes we can, but only with asyncData()
when it's about page-level component data. When using fetch
, we can utilize this.$nuxt.error({ statusCode: 404, message: 'Data not found' })
to show a custom error page.
Conclusion
New fetch hook brings a lot of improvements and provides more flexibility in fetching data and organizing route-level & building-block components in a whole new way!
It will certainly make you think a little differently when you plan and design your new Nuxt project that requires multiple API calls within the same route.
I hope this article has helped you get acquainted with the new fetch
feature. I'd love to see what you build with it.
Going Full Static
Long awaited features for JAMstack fans has been shipped in v2.13: full static export, improved smart prefetching, integrated crawler, faster re-deploy, built-in web server and new target option for config ⚡️
Nuxt 2: From Terminal to Browser
How we changed the developer experience to stop switching between the terminal and browser.