When user requests a webpage, Next.js fetches the required data and render the page into an HTML document to serve to the user. However, rendering the page for every request can be very time-consuming and puts a lot of pressure on the server, especially for high-traffic applications.
Next.js resolves this issue by memorizing the fetched data and caching the rendered page, so these resource-intensive tasks only need to be performed once, improving the app's performance and delivering a smooth user experience.
In this lesson, we are going to investigate the caching mechanism of Next.js, which helps you better understand how to unitize these features to create better applications.
Request cache
Next.js follows a component based design system where each page can be divided into different reusable components, creating a tree structure.
Sometimes, different components may need to share the same information, but fetching the same data repeatedly for the same request would be inefficient and waste valuable resources.
Using Next.js, you don't have to fetch the data at the top of the tree and pass the data down through layers upon layers of props, and instead, Next.js automatically fixes this issue by extending the fetch()
API.
When you use fetch()
to request data, Next.js will memorize that data and share across different components of the webpage.
For example, notice that in the following example, we fetched the same data twice:
1export default async function Page() {
2 async function getData() {
3 const res = await fetch("https://example.com/api");
4 return res.json();
5 }
6
7 let item = await getItem(); // Data will be fetched from API and memorized
8
9 let item = await getItem(); // Data will be fetched from cached result
10
11 return (
12 <div className="m-4">
13 <p>{item.body}</p>
14 </div>
15 );
16}
The first time the data is requested, it will be fetched and stored in a cache, and for subsequent requests, the data will be retrieved directly from the cache.
Note that the request memorization only applies to GET
requests. Next.js will not memorize them when you send request using POST
, DELETE
, or other methods.
1await fetch("https://example.com/api", { method: "POST" });
Data cache
The request memorization cache only lives for as long as the rendering process. It will be automatically purged as soon as the page finished rendering.
However, what if you need the cache to live longer than that? For example, it is possible for multiple users to request the same page. Ideally, you don't want to request the same data over and over again for every request.
Next.js comes with a Data Cache that persists across different requests. This is also done automatically with the fetch()
API.
The fetched data will be stored in Data Cache, and automatically available to subsequent request.
Revalidating data cache
Which leads to a new problem, what happens when the data stored in cache becomes outdated? Caching is a powerful tool, but can lead to stale data if not managed properly. To address this issue, it is best to set up a mechanism to revalidate and refresh the cached data.
Next.js offers two ways to revalidate cached data, time-based revalidating and on-demand revalidating.
Time-based revalidation
Time-based validation allows you to set a time interval, after which the cached data will be automatically purged. And during the next request, the data will be fetched again and stored in the Data Cache.
To set up time-based revalidation, specify a next.revalidate
option inside the fetch()
method.
1fetch("https://example.com/api", { next: { revalidate: 3600 } });
next.revalidate
accepts an integer value indicating the number of seconds the fetched data will stay in Data Cache before being purged. In the above example, 3600
means the data will be saved for 1 hour (60 seconds * 60 minutes)
On-demand revalidation
On-demand revalidation allows you to manually trigger revalidation whenever necessary. Next.js comes with two methods that helps you set this up, revalidatePath()
and revalidateTag()
.
revalidatePath()
accepts a route segment, and when executed, the cached data for that particular route will be revalidated.
1revalidatePath("/posts/123"); // All cached data for route /posts/123 will be revalidated
revalidateTag()
, on the other hand, needs to work with the next.tags
option:
1fetch("https://example.com/api", { next: { tags: ["a", "b", "c"] } });
This option allows you to set an array of tags for the cached data, and then you can revalidate them based on their tags using the revalidateTag()
method.
1revalidateTag("a"); // All cached data with the tag "a" will be revalidated
Note that both revalidatePath()
and revalidateTag()
methods must be executed on the server side, in either API routes or server actions.
Opting out of data cache
It is not recommended, but if you want to opt out of Data Cache completely, by setting the cache
option to no-store
:
1fetch("https://example.com/api", { cache: "no-store" });
This will make sure the data is always retrieved from the data source, but will impact the app's performance significantly, so please proceed with caution.