How to Use Caching in Next.js for Faster Page Loads

blog-banner

When building a web application with a framework like Next.js, performance really matters. Smart caching is useful for Next.js performance optimization. Next.js supports server-side rendering, which can be powerful, but it also puts more load on the server if not handled carefully. That’s where caching comes in. By caching the response data and pages, you can avoid doing the same work over and over again. This also helps your pages to load much faster for users. 

In this blog, we’ll explore different Next.js caching techniques available, such as static site generation, revalidation, and other powerful features that help boost performance. To demonstrate these concepts, we’ll use placeholder user APIs to create server actions and show how caching works in practice. 

Prerequisites 

  1. Basic understanding in JavaScript & React 
  2. Node.js (>=18) and npm/yarn/pnpm 

In this blog, we will understand the below are the mechanisms of caching in Next.js: 

  1. Request Memorization 

  2. Data Cache 

  3. Router Cache 

  4. Caching with SSG & ISR strategies

Request Memorization 

Next.js by default memorizes server requests. In the past, when we needed the same data across multiple components, we needed to fetch it at the top level of the component and pass it to the component through props. 

Using server components, it overrides the browser’s default fetch function with its own memorized version. So, you can safely make the same fetch request in multiple components and React will ensure the data is only fetched once. It will avoid redundant network calls and improve performance. 

export const getUsers = async () => { 
    // memoize the fetch request + response is cached 
  const users= await fetch("https://jsonplaceholder.typicode.com/users"); 
  return users.json(); 
}; 
  
  
// First Component 
export default async function Users() { 

  // makes network request and store result in Request Memorization cache 
  const users = await getUsers();   
  return ( 
    <div> 
     ... 
    </div> 
  ); 
} 
  
// Second Component 
export default async UserDetails() { 

  // uses memorized fetch instead of calling the Api again 
  const users = await getUsers();   
  return ( 
    <div> 
     ... 
    </div> 
  ); 
} 
    

Data Cache 

Request memorization can help avoid duplicate fetching requests within the same request cycle, but it doesn’t solve the problem of sharing cached data across multiple users or devices. This is where Data Cache in Next.js helps out. It allows you to cache data across users, so everyone can access the same cached response instead of hitting the data source each time. 

We can use force-cache, revalidate or no-store options in the fetch function to store the data in cache. 

Force Cache 

By default, Next.js caches all data in server memory. If you don’t specify any caching options in the fetch function, it uses cache: 'force-cache' by default. This means the data will be cached and reused across requests, helping improve performance out of the box with Next.js caching. 

export const getUsers= async () => { 
  const users = await fetch("https://jsonplaceholder.typicode.com/users", { 
    cache : "force-cache" 
  }); 

  return users.json(); 
};  

No Cache 

If you don’t want to cache the data at all, you can use the cache: 'no-store' option in the fetch function. This is useful when you're dealing with data that changes frequently, and you always want to fetch the most up-to-date information on every request. 

Next.js Developer Contact CTA

export const getUsers= async () => { 
  const users = await fetch("https://jsonplaceholder.typicode.com/users", { 
    cache : "no-store" 
  }); 

  return users.json(); 
}; 


Time-Based Revalidation 

If your data changes often, you can use revalidation. You cache the data for a certain time, and once that time is up, you check back with the server to see if the data has changed. If it's still the same, you reuse the cached version otherwise, you update it. This way, you save time and bandwidth while keeping data fresh. 

export const getUsers= async () => { 
  const users = await fetch("https://jsonplaceholder.typicode.com/users", { 
    revalidate: 60 // revalidate the data after 60 seconds 
  }); 

  return users.json(); 
}; 

On-Demand Revalidation 

We can use on-demand revalidation to update cached data without waiting for the default cache expiration. There are two main uses for this: 

  1. revalidateTag(tag: string) - Revalidates all cached data associated with a specific tag. 

  2. revalidatePath(path: string, type?: "page" | "layout") - Revalidates a specific route (path) and revalidates all pages using layout in your application. 

import { revalidateTag } from "next/cache"; 
  
export const getUsers = async () => { 
  const users = await fetch("https://jsonplaceholder.typicode.com/users", { 
    next : { tags : ['USERS'] }  

  });  
  return users.json(); 
}; 
  
export default async function Users() { 
  const users = await getUsers(); 
  
  async function submit() { 
    'use server' 
     
    revalidateTag('USERS')  // revalidate the USERS tag action  
    // ... 
  } 
  
  return ( 
<div> 
      { 
        users.map(user => <div key={user.id}>{user.name}</div>) 
      } 
      <form> 
      <button formAction={submit}>Revalidate Users</button> 
      </form> 
    </div> 
  ); 
}  
    

Example: revalidatePath with Placeholder API

import { revalidatePath } from 'next/cache'; 
 
const updateUser = async (id, data) => { 
  await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, { 
    method: 'PUT', 
    body: JSON.stringify(data), 
  }); 
 
  // After updating, invalidate the user page for the specific id 
  revalidatePath(`/user/${id}`); 
}; 
  

Router Cache 

Next.js has a built-in client-side caching mechanism that stores React Server Component data in the browser’s memory. This helps make page-to-page navigation much faster. When you use the <Link> component, it not only handles routing but also prefetches linked pages in the background, allowing for smooth and instant transitions. 

However, if your page contains a large number of <Link> components say, hundreds - Next.js will try to prefetch all of them. This can put a strain on browser memory and potentially cause performance issues. To avoid that, you can turn off prefetching for specific links by adding prefetch={false} to the <Link> component. 

<Link href="/users" prefetch={false}>Go to Users</Link>

Caching in Next.js with SSG & ISR strategies  

Static Site Generation (SSG) 

With SSG (Static Site Generation), the HTML for a page is generated at build time and then cached until the next build. This approach is great for pages where the content doesn’t change often, as it allows them to load extremely fast. In Next.js static site generation, you can use getStaticProps function of Next.js static site generation to fetch the user data at build time. 

Example: Caching a List of Users with Static Generation

import React from 'react'; 
 
const Users = ({ users }) => { 
  return ( 
    <div> 
      <h1>List of Users</h1> 
      <ul> 
        {users.map((user) => ( 
          <li key={user.id}>{user.name}</li> 
        ))} 
      </ul> 
    </div> 
  ); 
}; 
 
export async function getStaticProps() { 
  const res = await fetch('https://jsonplaceholder.typicode.com/users'); 
  const users = await res.json(); 
 
  return { 
    props: { 
      users, 
    }, 
  }; 
} 
 
export default Users; 
  

Why SurekhaTech CTA

Incremental Static Regeneration (ISR) 

Next.js Incremental Static Regeneration (ISR) lets you update static content without rebuilding the entire site. By using the revalidate option inside getStaticProps, you can set a time interval that tells Next.js when to regenerate the page. 

With ISR, users continue to see the cached, statically generated page until that interval expires. Once it does, the next request will trigger a background rebuild of the page.  

  • getStaticPaths defines the dynamic paths that Next.js should generate at build time. 

  • getStaticProps fetches the user data for the individual user page and revalidates the page for a specific amount of time. 

Example: ISR with Placeholder API 

     // pages/user/[id].js
import React from 'react';
 
const UserDetail = ({ user }) => {
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
};
 
export async function getStaticPaths() {
  const res = await fetch('https://jsonplaceholder.typicode.com/users'); 
  const users = await res.json();
 
  const paths = users.map((user) => ({
    params: { id: user.id.toString() },
  }));
 
  return {
    paths,
    fallback: true,
  };
}
 
export async function getStaticProps({ params }) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${params.id}`); 
  const user = await res.json();
 
  return {
    props: {
      user,
    },
  };
}
 
export default UserDetail;
  

Follow this blog for more information about the SSG and SSR.

Server-Side Rendering (SSR) VS. Static Site Generation (SSG) In NextJs 
  

Conclusion

Next.js gives developers powerful tools to handle caching and content updates in smart ways. By taking advantage of these strategies, you can make your pages load faster, reduce the number of repeated server requests, and improve the overall user experience. Whether it's prefetching data or fine-tuning how and when your content updates, applying the right caching techniques can make a big difference in the performance.

Contact us

For Your Business Requirements

Contact us