# Understanding the advanced feature of Next.js caching mechanisms
## Introduction
`Caching` is the technique of storing and retrieving data from memory. `Caching` primary function is to shorten the time it takes to retrieve data. The goal of `caching` is to save information that may come into use later.
[Next.js](https://nextjs.org/) `caching` renders `data queries`, which increases application speed. Although Next.js is a fantastic framework that simplifies the process of developing `server-rendered` `React` applications, there is one major issue: The `caching` mechanism of `Next.js` may result in hard-to-debug and hard-to-fix issues in your code.
`Next.js` will `cache` as much as possible to save costs and increase speed. Unless specified otherwise, `routes` are statically displayed, and `data` `requests` are `cached`.
Whether a `request` is made as part of an initial visit or a follow-up navigation, whether the `route` is displayed statically or dynamically, and whether data is `cached` or uncached all affect how `caching` behaves. Depending on your use case, you may change the `caching` behavior for particular `routes` and `data` `requests`.
If you’re not familiar with `Next.js` `caching` mechanism, you may find out that you’re unable to take advantage of its robust `caching` feature and, instead you always have trouble using `Next.js`.
This article helps you understand how each part of Next.js `cache` operates. Once you grasp `Next.js` `cache`, you can stop struggling and start maximizing its performance improvements.
## Significance of Caching in Optimizing Web Applications
`Caching` is a powerful tool that can significantly improve user experience. Here are some of its benefits:
- Improves Website Performance and Load Times:
- Slow website loading times can lead to user frustration and abandonment. By storing frequently accessed data, `caching` improves website performance and reduces load times, ensuring content is delivered quickly to users.
- Enhances User Experience:
- `Caching` results in faster load times for both websites and apps, leading to an enhanced user experience. Users can access desired content more quickly, especially during periods of heavy traffic or brief outages at the source.
- Lessens the Burden on Networks and Servers:
- `Caching` reduces the strain on networks and servers by serving content directly from the `cache`, thus reducing the number of `queries` made to the source. This improves `server` efficiency, lowers `data` delivery costs, and reduces network congestion.
## Nextjs caching mechanisms
Understanding the [caching mechanisms in Next.js](https://nextjs.org/docs/app/building-your-application/caching) can seem daunting due to its complexity. `Next.js` employs four distinct `caching` levels, each serving a specific purpose. These mechanisms operate at different stages of your application, and their interactions may appear complex.
### Request Memoization ###
Request Memoization is one of `React` inbuilt features that allows you to extend the `fetch API` to automatically `memoize` your `requests` with the same `URL` and `options`. As a result, if you make a `fetch` `request` in one component and then make the same `fetch` request in another, the second `fetch` `request` will not submit a `request` to the server. It will instead use the cached `data` from your initial `fetch` attempt.
```javascript
export async function getUser() {
// Next.js automatically caches the fetch function
const res = await fetch(`https://jsonplaceholder.typicode.com/users/1`);
return res.json();
}
export default async function Page() {
const user = await getUser();
return (
<>
<div>{user.id}</div>
<UserProfile />
</>
);
}
async function UserProfile() {
const user = await getUser();
return <p>{user.name}</p>;
}
```
In the provided example, `getUser()` function shows `request memoization`. Upon the first invocation of `getUser()` function, the returned value is cached in the Request Memoization `cache`. Subsequent calls to `getUser()` within the `UserProfile` component fetch the `data` from the cached result instead of making a new `request` to the `API`.
Request Memoization optimization significantly improves code efficiency and performance by minimizing redundant `fetch` `requests`. Request memoization is necessary for reducing duplicate `fetch` `requests` within a single render cycle, particularly for `GET` `requests`.
Duration: The `cache` remains active throughout the duration of a `server` `request`, persisting until the entire `React` component tree completes its rendering process.
Revalidating: Request memoization is not shared across `server` `requests`; hence, revalidation is unnecessary. During the `fetch` `request`, `memoization` ensures `cache` validity, removing the need for revalidation.
Opting out: To opt out of Request memoization, you can pass in an `AbortController signal` as a `parameter` to the `fetch` `request`.
```javascript
export async function getUser() {
const controller = new AbortController();
const res = await fetch(`https://jsonplaceholder.typicode.com/users/1`, {
controller: controller.signal,
});
return res.json();
}
```
By including these optimizations, `Next.js` effectively manages `caching mechanisms`, enhancing the performance and responsiveness of applications.
### Data cache
`Next.js` offers a built-in `Data` `Cache` feature that retains the outcomes of `data` fetches across `server` `requests` and deployments. This ensures persistence and efficient handling of `data` retrieval from `APIs` or `databases`, enhancing performance and user experience.
When initiating a `fetch` request within `server` components, `Next.js` automatically `caches` the `response` by default. This cached `data` is then used for subsequent `requests`, removing redundant `fetches`.
Let’s have a simple page that send a `fetch` `request` to get a particular post.
```javascript
async function Page() {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/1`);
const data = await res.json();
return (
<div>
<h2>{data.title}</h2>
</div>
);
}
export default Page;
```
When you initiate a `fetch` `request` during rendering, `Next.js` examines the Data Cache to find a potential cached response.
If a cached response exists, it is promptly returned to you and `memoized`.
If there's no cached `response`, the system sends your `request` to the data source, stores the outcome in the Data Cache, and then `memoizes` it.
For uncached data ``(e.g. { cache: 'no-store' })``, the result is always fetched from the data source, and memoized.
Regardless of whether your data is cached or uncached, all your `requests` undergo `memoization`, ensuring you avoid redundant `requests` for the same data during a `React` render pass.
Duration: Data cached from the `fetch` `request` is never cleared until you instruct Nextjs to clear it, Data `cache` remains persistent throughout incoming `request` and deployment unless you choose to `revalidate` it or `opt out`.
Revalidating: Since data `cache` cannot be cleared, there must be a way to `revalidate` it. It can be revalidated in two possible ways:
* Time-based Revalidation: In `time-based revalidation`, we set a certain time limit. When a new `request` is made after this time has passed, the `data` `cache` will clear automatically. This helps keep the `cache` updated by removing outdated information. You can pass the `next.revalidate` option in the `fetch` `request` to set the `cache time-bound`(Note: Always in seconds). Look into the code below:
```javascript
const res = fetch(`https://jsonplaceholder.typicode.com/users/1`, {
next: { revalidate: 3600 },
});
```
With this, Next.js will get to know how many seconds to keep your `data` in the `cache` before it is considered stale.
The first time the `fetch` request is called with the `next.revalidate`, the data will be fetched from the source and it will will cached and stored in Data cache.
Any `fetch` request call within this timeframe passed in the initial `request`, the data returned will be from the Data cache that has been stored and cached.
Any `request` after the time-bound will return the cached data (Now, the data value has become stale).
After the time-bound, Next.js will `revalidate` the data in the background.
When the data is successfully obtained, Next.js will update the Data Cache with the new information. However, if the background `revalidation` fails, the previous data will stay unaffected.
- On-demand Revalidation: If your data isn't regularly updated, `on-demand revalidation` becomes valuable. Consider a scenario where you've built a blog using a Content Management System (CMS). In this case, leveraging `on-demand revalidation` allows you to `invalidate` the `cache` and `fetch` new `data` whenever a recent article is published, ensuring your content remains up-to-date. Data can be `revalidate` in two different ways:
- revalidatePath function: revalidatePath function allows you to `invalidate` cached content for a specified path lazily, meaning it only occurs when you revisit that `path`. This targeted invalidation prevents unnecessary revalidations across your site, making it possible for efficient cache management.
```javascript
import { revalidatePath } from "next/cache";
export async function pubBlog({ state }) {
createArticle(state);
revalidatePath(`/location/${state}`);
}
```
The `revalidatePath` function accepts a string path and clears the cache for all `fetch` requests on that route.
- revalidateTag function: revalidateTag function gives you control to `invalidate` cached content associated with a specific `tag` in `Next.js`. When you call it, it purges cached pages and data matching that tag, triggering fresh generation when you next visit those pages.
If you want to be really clear about which `fetch` requests to check again, you can use the `revalidateTag` function.
```javascript
const res = fetch(`https://.../${state}`, {
next: { tags: ["location-state"] },
});
```
Opting out: To `opt out` of `data` `cache` can be carried out in different way. Let’s take a look at the example for each of them.
- No-store:
```javascript
const res = fetch(`https://… `, {
cache: "no-store",
})
```
Passing cache: ``"no-store"`` to your `fetch` request, you are telling Next.js not to `cache` this request in the Data Cache. And this is sometime useful when you intend to change the data on each request.
- Unstable_noStore:
```javascript
import { unstable_noStore as noStore } from "next/cache"
function getProducts() {
noStore()
const res = fetch(`https://.../${products}`)
}
```
Opting out of caching on a per-component or per-function basis is a fantastic way for you to have more control. Unlike other methods, which opt out of the Data Cache for the entire page, this approach allows you greater flexibility.
- Dynamic function:
```javascript
export const dynamic = 'force-dynamic'
```
Suppose you intend to change the behavior on how the entire page is cached, now limiting it to a specific `fetch` `request`. In that case, you can include this code snippet at the top level of the file, which will remove the Data cache entirely and make the page dynamic. By supplying `cache`: `"no-store"` to your `fetch` `request`, you instruct Next.js not to `cache` this `request` in the Data Cache. This is handy when you frequently change data and want to get it fresh every time.
- Revalidate:
```javascript
export const revalidate = 0
```
This is also similar to the `force dynamic` mentioned above; once you equate the `revalidate to 0`, it will tell `Nextjs` to ensure that all the fetch `requests` on the particular page are not cached or stored.
### Full route cache
When you render pages for your clients in `Next.js`, you employ both `HTML` and the `React Server Component Payload (RSCP)` to guide `client components` on how to interact with `server components` for page rendering. The Full Route Cache comes into play here, storing both `HTML` and `RSCP` for static pages throughout the build process.
As you build your application, `Next.js` automatically renders and `caches` `routes`. This optimization allows cached `routes` to be served instead of rendering on the `server` for each request, resulting in faster page loads.
The main advantage of this `cache` is that it enables you to `cache` static pages at build time, removing the need to generate such pages dynamically.
Consider the following example:
```javascript
import Link from "next/link";
async function getBlog() {
const blogPosts = await fetch("https://jsonplaceholder.typicode.com/posts");
return await blogPosts.json();
}
export default async function Page() {
const blogInfo = await getBlog();
return (
<div>
<h1>Blog Posts</h1>
<ul>
{blogInfo?.map((post) => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{`${post.body.slice(0, 20)}...`}</p>
<p>
<Link href={`/blog/${post.id}`}>Read more</Link>
</p>
</li>
))}
</ul>
</div>
);
}
```
In the provided code snippet, the Page lacks `dynamic data`, allowing for `caching` during the `build time`. The complete `route` `cache` stores both `HTML` and `RSCP`, significantly enhancing server speed when a user requests access to that particular `route`.
Duration: By default, your Full Route Cache is persistent, meaning that the render output is cached across your user requests.
Revalidating: The only way this `HTML/RSCP` will be updated is through:
* Revalidating data: Revalidating the Data Cache will, in turn, `invalidate` the Router Cache by re-rendering `server` components and `caching` the new render output.
* Redeployment: Unlike the Data Cache, which persists across deployments, new deployments clear the Full Route Cache.
Opting out:
To opt out of the Full Route Cache,you will have to opt out of the Data Cache. If the data needed for the page is not cached in the Data Cache, you can consider the second approach: incorporate dynamic elements like `headers`, `cookies`, `searchParams`, or `dynamic URL` `parameters` in your page.
### Router cache
In Next.js, you'll encounter the Router Cache, which functions differently compared to traditional `server-side` `caching`. Instead of `caching` on the `server`, it stores `cache` data on the `client-side`. Understanding its functionality is crucial for you to avoid potential issues and optimize performance. Basically, it stores the `routes` you've visited locally on your device, enabling quicker access to cached versions rather than fetching fresh content from the `server`. Mastering its operation allows you to manage any challenges effectively.
How the Router Cache Works:
```javascript
import Link from "next/link";
export default async function Page() {
const blogInfo = await getBlog();
return (
<div>
<h1>Blog Posts</h1>
<ul>
{blogInfo?.map((post) => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{`${post.body.slice(0, 20)}...`}</p>
<p>
<Link href={`/blog/${post.id}`}>Read more</Link>
</p>
</li>
))}
</ul>
</div>
);
}
```
Next.js `caches` the `route` segments you visit and preloads likely `routes` based on `<Link>` components in your viewport. This makes your navigation experience smoother.
Navigate backward and forward instantly since the app `caches` visited `routes`. Explore new `routes` quickly, thanks to prefetching and partial rendering. No need for full-page refreshes between navigations, keeping your `React` and browser `states` intact.
Duration: The behavior of the `Router Cache` varies depending on the `route` type and `duration` of storage. For static `routes`, the `cache` persists for 5 minutes, while for `dynamic` `routes`, it lasts only 30 seconds. If you revisit a `static route` within 5 minutes, the cached version is used. After 5 minutes, a `server request` fetches the new `HTML/RSCP`. Dynamic `routes` follow a similar process but with a 30-second cache instead of 5 minutes.
This cache is session-based and clears when you close the tab or refresh the page.
Revalidating: You have the option to manually `revalidate` the Router Cache by clearing the `data cache` from a `server` action using `revalidatePath/revalidateTag`. Additionally, invoking the `router.refresh function`, obtained from the `useRouter` hook on your `client-side` code, forces the client to refetch the current page.
Opting out: There's no direct way for you to opt out of the Router Cache. However, given the various methods available for cache revalidation, it's not a significant concern for you to worry about.
## Pros and cons of Nextjs cache mechanism
### Pros
- In Next.js, the `caching` mechanism works to remove redundant data `fetching for you`. When data is fetched and cached, This approach helps reduce the `server` load, providing you with more efficient `data handling`.
- Effective caching strategies involve understanding the trade-offs between immediate consistency and the performance benefits of serving stale content.
- Next.js `cache` mechanism introduces advanced `revalidation patterns`, allowing developers to fine-tune how their application manages `data fetching`, `caching`, and `validation`. Using `time-based` and `event-driven` `revalidation`, these helps developers build strong systems that maintain data up to date without losing user experience.
### Cons
- In Next.js 14, you can perform `server-side` data fetching using third-party libraries. These libraries often come with their own `methods` for `caching` and `revalidation`, offering you more control or additional capabilities compared to the built-in retrieval.
- Caching can speed up data `fetching` and `rendering`, but it also increases memory usage. Caching large amounts of data may strain `server` resources, impacting overall performance.
- Be mindful of `cache` invalidation challenges. When server data changes, Next.js caching mechanism might struggle to update the cache, leading to users receiving outdated information.
## How do cache interact with each other
Understanding how caching techniques interact with each other is crucial for effective implementation.
### Data Cache and Full Route Cache
- When `revalidating` or `opting-out` of the Data Cache, the Full Route Cache is invalidated since the render result depends on the `data`.
- The Data Cache remains unaffected by `invalidating` or `opting-out` of the Full Route Cache. This allows for dynamically displaying a `route` with both cached and uncached data.
### Data cache and client-side router cache
- Revalidating the Data Cache in a Route Handler doesn't immediately `invalidate` the Router Cache, as it's not tied to a specific `route`.
- To promptly `invalidate` both the Data Cache and Router Cache, you have to use `revalidatePath` or `revalidateTag` in a `Server Action`.
## Conclusion
In conclusion, understanding Next.js caching mechanisms is crucial for optimizing your web application's `performance`. `Caching`, at various levels such as `Request Memoization`, `Data Cache`, `Full Route Cache`, and `Router Cache`, plays a pivotal role in reducing load times, enhancing user experience, and efficiently managing `server` resources. Each caching level operates distinctly, and knowing how to leverage features like `time-based` and `on-demand revalidation` allows you to fine-tune your application for optimal results.