DEV Community

SeongKuk Han
SeongKuk Han

Posted on

Understanding Fetch 'cache' with VanilaJS and Next.js

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

You can see the details about the cache in the MDN docs.

  1. default
  2. force-cache
  3. only-if-cached
  4. reload
  5. no-store
  6. 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

Server and Client structures

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

initial screen

If I click the default button,

the default button is clicked and the result is shown

As we expect, it fetches the data from the server. Let's click the default button once more.

The data is fetched from the server again

The data has been fetched from the server again.

force-cache

Let's keep the data and click the force-cache button.

The data comes from the cache

You see the text "from disk cache". It makes sense. Because the cache type is force-cache.

Let's refresh to clear the data.

The page has refreshed

Now, let's click the force-cache button.

the data is from the cache

The data is fetched from the cache again. Let's clear the cache and reload.

Empty cache and hard reload

Refreshed page

The cache should be gone. Let's click the force-cache button.

It requested the data from the server

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.

fetched from the cache

only-if-cached

I cleaned up the cache.

first page without cache

Let's click the only-if-cached button.

network error

console error

It occurs an error, let's click the default button once then retry it.

fetched the data from the cache

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.

fetched the data from the remote server

Now, to see if the data that 'no-store' retrieved is cached, click the force-cache button.

the cached data is different from the latest data fetched from the server

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.

fetched the data from the server

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 data 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.

The data is fetched from the server

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.

the default cache retrieved the data from the cache

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,

The data is fetched from the server

It requests the data to the server.


Fetch Cache with Next.js

structure of the example 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>;
}
Enter fullscreen mode Exit fullscreen mode

This is the code of the test page. All the results will be displayed when the page loads.


Development - Dev Server

Random numbers corresponding on each cache

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.

default and force-cache are the same but the rest of them have been changed.

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.

Changed the same way as the previous try

It worked the same way as the previous try. Actually, I found something interesting somehow. If I do a hard reload.

all values have been changed.

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,
    },
  });
Enter fullscreen mode Exit fullscreen mode

The cache is valid for 3 seconds.

the initial page, the result the same as the previous one

Now, let's refresh the page before the time has passed.

the result the same

The value of the default is still the same. Let's reload after 3 seconds.

the cache that used the revalidate option has been updated but the one that used  raw `force-cache` endraw  has not

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.

cache result page

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.

they haven't changed

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.

first try

Before 3 seconds (revalidate), if I refresh the page.

second try

The value is the same.

value is updated

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)

Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

can we have a look at the source code, can you publish it?

Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

found it in your github repo
github.com/hsk-kr/understanding-fe...

Collapse
 
lico profile image
SeongKuk Han

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 πŸ‘

Collapse
 
notzaryab profile image
Zaryab

Great article. Thanks alot for sharing.

Collapse
 
lico profile image
SeongKuk Han

I'm glad you found it helpful ☺️