When I implemented API requests on the client side, I used the axios
library. Using the library looked more beneficial to me over fetch
. react-query
allowed me to utilize cache
in an easy way.
I didn't really try Next.js 13 and I recently read about the cache
of fetch
on the doc, and it was a bit confusing. It says the "'force-cache' is the default, and can be omitted". Although I didn't often use the fetch
function, I don't think the fetch
call uses cache
by default. It later turns out this is about Next.js. However, somehow it makes me get doubt what I know about fetch
.
In MDN:Request: cache property, it says "If there is a match and it is fresh, it will be returned from the cache." by default when using fetch
. That was what I didn't know. I wanted to see the result depending on a cache, but there are only theory parts out there about it. I ended up deciding to do the experiment by myself and share it on my blog thinking that there may be some people like me.
Overview
- Cache
-
Fetch Cache with VanilaJS
- VanilaJS on the web browser.
-
Fetch Cache with Next.js
- Nextjs in SSR.
- Wrap up
Cache
You can see the details about the cache in the MDN docs.
- default
- force-cache
- only-if-cached
- reload
- no-store
- no-cache
In this article, I'm not going to cover the no-cache
cache. Since Conditional requests is involved in it, implementing it would be a little away from the main topic.
Fetch Cache with VanilaJS
This is the structure of this example.
If cache-control is enabled, the random-items API responds with the data that includes the header 'Cache-control: private, max-age=5'.
max-age
tells the client how long they have to keep it in the cache. I'm first going to show you without the cache-control header, with the cache-control header, to see the differences between them.
cache-control off
default
If I click the default button,
As we expect, it fetches the data from the server. Let's click the default button once more.
The data has been fetched from the server again.
force-cache
Let's keep the data and click the force-cache button.
You see the text "from disk cache". It makes sense. Because the cache type is force-cache
.
Let's refresh to clear the data.
Now, let's click the force-cache button.
The data is fetched from the cache again. Let's clear the cache and reload.
The cache should be gone. Let's click the force-cache button.
It didn't find the cached data and requested the data to the server.
If I click the button once more, it will fetch the data from the cache.
only-if-cached
I cleaned up the cache.
Let's click the only-if-cached button.
It occurs an error, let's click the default button once then retry it.
As we expected, it fetched the data from the cache.
no-store
The no-store
doesn't update the cache and fetch the data from the server. I didn't clear the previous one.
Let's click the 'no-store' button.
Now, to see if the data that 'no-store' retrieved is cached, click the force-cache button.
The data comes from the cache, which means the no-store
doesn't store the data in the cache.
reload
The reload
always fetches the data from the server but the data will be cached in the local disk.
Let's click the reload button.
It fetched the data from the server. Now, click the force-cache data button, and let's see if the data that just got from the server is cached.
The reload
updated the cache data accordingly.
cache-control on
default
We are going to see only the default
cache, the rest of the caches work the same way.
I clicked the default button and it looks the same as we already saw in the previous section.
The difference is that there is the Cache-Control
header in the response headers.
Let's click the default button within 5 sec before the cache is invalid.
It retrieved the data from the cache. If the cache is fresh, it uses the cached data while force-cache
and only-if-cached
retrieve the data from the cache regardless of the state of the cache.
After 5 seconds, the cache becomes stale. Then if we click the default button,
It requests the data to the server.
Fetch Cache with Next.js
In this example, since I use server components, API requests will happen on the server side. So, we can't see the requests on the dev tool.
I set the revalidate of the random-number
API to 0, otherwise, when it's built, the API will be cached and return the same response.
[fetch-with-all-cache.tsx]
export default async function FetchWithAllCachePage() {
const elements = [];
let api = await fetch('http://localhost:3000/api/random-number', {
cache: 'default',
// next: {
// revalidate: 3,
// },
});
elements.push(<div key="default">default: {(await api.json()).number}</div>);
api = await fetch('http://localhost:3000/api/random-number', {
cache: 'force-cache',
});
elements.push(
<div key="force-cache">force-cache: {(await api.json()).number}</div>
);
api = await fetch('http://localhost:3000/api/random-number', {
cache: 'no-store',
});
elements.push(
<div key="no-store">no-store: {(await api.json()).number}</div>
);
api = await fetch('http://localhost:3000/api/random-number', {
cache: 'reload',
});
elements.push(<div key="reload">reload: {(await api.json()).number}</div>);
api = await fetch('http://localhost:3000/api/random-number', {
cache: 'only-if-cached',
mode: 'same-origin',
});
elements.push(
<div key="only-if-cached">only-if-cached: {(await api.json()).number}</div>
);
return <div>{elements}</div>;
}
This is the code of the test page. All the results will be displayed when the page loads.
Development - Dev Server
Except for the only-if-cached
cache, the rest of them have the same value.
To see if the values are changed, let's refresh the page.
Initially, I was expecting that reload
may make a change in the cache. But it doesn't. The only-if-cached
has changed while there is no difference in the default
and the force-cache
.
Let's refresh one more.
It worked the same way as the previous try. Actually, I found something interesting somehow. If I do a hard reload.
It changes all the results. One thing I found out here is that the values of each cache can be different at least in the dev server. We will see if it works the same way with the built files.
Lastly, I will apply the revalidate
to see if the cache is updated after some time instead of the default
cache.
let api = await fetch('http://localhost:3000/api/random-number', {
// cache: 'default',
next: {
revalidate: 3,
},
});
The cache is valid for 3 seconds.
Now, let's refresh the page before the time has passed.
The value of the default is still the same. Let's reload after 3 seconds.
The one that used the revalidate
option has been updated. It works exactly the same as the document explains.
However, it's updated when the page is hard reloaded as it did in the previous example.
Production - Build
I undid the revalidate option to the default
.
The default
and force-cache
have the same number and the no-store
and reload
have the same number. Only the only-if-cached
has a different number.
Let's hard reload to see if the values of default
and force-cache
.
In the built project, the values haven't changed after hard reloading. I think it was because of the development environment.
Let's test the revalidate
option. It will probably work the same.
Before 3 seconds (revalidate), if I refresh the page.
The value is the same.
And after the validation time, it's updated.
Wrap Up
It's done. While I was doing the experiment by myself. I realized that I totally misunderstood the concept of fetch
.
There is a way you can cache the data on the client side and caching is also working by interacting with a server.
What made me confused was the usage of fetch
on the server side with Next.js. But since fetch
is called from the server side, it's up to the implementation how they implement it. So, basically, the differences in the values of each cache in Next.js don't matter unless the cache works differently from their guide. What we need to do to use caching in the SSR of Next.js is to follow their guide.
I hope this experiment is helpful for someone else.
Happy Coding!
Top comments (5)
can we have a look at the source code, can you publish it?
found it in your github repo
github.com/hsk-kr/understanding-fe...
Oh, I'm sorry, I haven't been here for a while, so I didn't reply.
Jep, that's the repo! I'm glad it looks like you found it useful ☺️ Have a nice day 👍
Great article. Thanks alot for sharing.
I'm glad you found it helpful ☺️