Introducing Client-Side Caching in Nuxt 3.8
Hey everybody, welcome back to another video on this channel. With Nuxt 3.8 release, there has been an amazing addition to useFetch and useAsyncData. Let's check out what it is.
Alright, most of you heard, so caching is a very, very common and important mechanism throughout the whole web development world and probably even beyond that. But today we'll not focus on the server side. We'll not talk about Nitro and route rules and defineCachedEventHandler. That's for another video in the future. We will talk about the client side because in Nuxt's latest version 3.8, there has been an addition to useFetch and useAsyncData, which is the getCachedData function. With it, we can avoid unnecessary or superfluous refetches of data and even define our own caching mechanism to avoid this refetches. Let's have a look.
Project Setup and Initial Data Fetch
Alright, we have a very standard and plain Nuxt application, version 3.8 as mentioned before. The Nuxt config is empty, and we only have an index page and an about page, both just linking to each other and saying on which page they are.
Okay, next we want to fetch some data because, well, that would be good if we want to cache them eventually. So let's use useFetch here, and I really like the icanhazdadjoke API for some juicy dad jokes. Also, we want to set the headers here to ensure that the API will not return plain text but JSON. That's a specific thing. And now let's log data.joke here. Also, maybe let's type that with any here because for the sake of the video, we're a bit lazy. No big deal.
The Problem: Unnecessary Refetches on Navigation
Alright, and let's check out the browser now and see what will be returned. Here we go, the index page, and we got a nice joke: What do you call a dictionary on drugs? High definition.
Well, it's a good one though. But let's see what happens if we go to the about page, like navigating around and back to the index page. And we see, oh, that's another joke. That's a different one, and the API has been called. And same happens if you navigate again and again. That's great if we want to refresh the data, but what if we say, 'Okay, every user gets one random joke that is persisted throughout the lifetime of the application,' so until there's a hard reload? Well, that's possible.
Implementing Basic Caching with getCachedData
And let's implement that straight away in the code. So to do that, as mentioned before, we use getCachedData. And the key that's present here as an argument, as a parameter, that's the key that Nuxt uses to save the data which is fetched by useFetch or useAsyncData in a similar way, inside the payload. So what we want to do next is, we actually, or what we're going to do next is to get the Nuxt app in here because we need it a bit, because somehow we need to access the database on that key. And we can do that by saying, 'Okay, let's in here return nuxtApp.payload.data.' So if it's in the payload, or nuxtApp.static.data. So that way we ensure if the data has been fetched by useFetch, then we can definitely get it. These are two different ways, depending on, okay, are you hydrating, are you not hydrating yet? And I don't want to go too much into internals here, but that's the easy way to go.
But what getCachedData actually is doing is if you return a nullish value, that means, 'Oh, please refetch the data,' or fetch the data if you haven't fetched it yet. So if you just return nothing like this, it will always just fetch data from useFetch. That's the default behavior. But if we return anything else, well, then exactly that data will be used. And of course, you have all the chances here to use whatever you want. So we can even define our own little caching mechanism in a bit. So right now, what this is doing is, 'Okay, if the data is fetched already, just return it, and otherwise, please fetch it.'
So once again, let's jump in the browser and have a look what's happening. Okay, and here we go. The hard reload is in. We see another joke, that's fine. And what happens if we switch to the about page and back? And yes, we achieved it. It's the same joke, perfect. Also, no API calls happening. The XHR filter, it's all empty. That's great. So we just fetch it once. So if we refresh the page, takes a little bit, we get another joke. That happens on the server side. There is no XHR call here, except for the manifest, we can ignore that. And once again, if we switch, that's all fine.
But what now if you want to say, 'Okay, it's great, the random joke is nice, but somehow we want to change the data after a certain TTL has passed.' So to do that, first, I would suggest we have some kind of fetched at date, because without the date, like a TTL date, what should we do? And either you get that from your API, that's nice, but here we just build our own ones with the transform function. The transform function just takes the input, so the return of useFetch, and now we can customize it to our liking. So let's just return everything from input here and also a fetchedAt attribute which is equivalent to new Date(). So this is basically our base on TTL. Now we know when this was fetched.
Building a Custom Time-To-Live (TTL) Cache
And in the getCachedData down here, well, we can adapt that and we can use it. So first, let's do the following. Let's just say const data so we get the data here. If it's not there, which can happen if no data has been fetched yet, let's just return, because that's the indicator, 'Hey, please get cache data.' Don't do anything, get the data just as usual. Good, nice.
What's next? Well, what's next? We have to check, is the data too old? And how do you do that? Well, the good part is we have this fetchedAt. That's in data.fetchedAt. It's right in there. So let's just create something called, I don't know, expirationDate here with new dates around that. And let's set the time. I would always suggest to use something like date functions for that, or Luxon, Day.js, or what not. I will just do it bare-bones here. And let's just say, let's get the time and we add 10 seconds to it. So 10 * 1000, 10,000 milliseconds. Fine.
And now let's create an isExpired variable that will just check if data is too old or not. So let's take the expiration date with the new time. So like expiration date in 10 seconds and check if the current time is bigger. So if the date is already passed. And now we say if it's expired, then once again, we return, which means here, refetch data because, as you remember, returning something nullish, which is undefined or null, and if you just write return, it's very equivalent to return undefined, we're good to go. And otherwise, we take the happy path of, 'Okay, if it's not expired, if there is data, cool, let's just return it.'
So okay, we have a joke. We go back and forth. Nothing happens, perfect. No API call, nothing. But ah, there we go. The 10 seconds already passed, it's too quick, and we get another joke. The API was hit correctly. That is because our getCachedData function was triggered. And here, once again, 10 seconds are just too fast. Usually you should put, I guess, two minutes or something. So whenever the user would like get the data, getCachedData will be evaluated and checked, should it refetch or should it just return the old one? And that's exactly what we want. We build our own little caching system. Of course, this is very rudimentary. You can improve it by a lot.
Summary and Conclusion
But now you know what the idea of it is.
Alright, let's summarize it. Nuxt 3.8 introduced an amazing new function as an option for useFetch and useAsyncData which is called getCachedData. With it, you can build your own little cache around useFetch and useAsyncData, either saying 'don't fetch data at all, just use it as soon as it's in there, and then a hard refresh there will be a new one,' or even define your own TTL system and logic to refresh the data if it's necessary.
And after that, I'm more than sure that some of you will jump straight away in the project and implement their own version of getCachedData. So after this, please let me know, will you use it after all? Did it work out? And how does your custom logic look like? What will you use it for, your use cases? I'm really curious to know all of them. Also, if the whole video helps you, please let me know in the comments and don't forget to like and subscribe to my channel to don't miss out any further videos about Nuxt, Vue, TypeScript, JavaScript and so on. See you in the next one and happy hacking.