
No Comments Yet
Be the first to share your thoughts and start the conversation.
You surely have heard about caching. Browsers have cache, some apps have cache, you are probably using some function to “clear cache” to speed up you phone, but what is caching, exactly?
What does it mean for you, a Next.js web developer?
Let’s say you’ve made a cooking recipes app. When user enters a page with recipes list, you fetch() it for them from some API.
And this particular API? It’s very slow. 🐌
Like, very, very slow – it takes 10 seconds to respond, and during this time your user sees nothing but “Loading…” Sounds like awful user experience, right?
The recipes aren’t changing that often, and the new ones are being added every few days – that’s where caching comes into play. You don’t have to fetch() the data every time user enters the list. You can cache it, and serve the list straight from your app, omitting the terribly slow API. User gets recipes instantly, and you don’t even have to make the API call, saving your monthly 1000 calls from the free plan limit. 😅
It's just one of the caching mechanisms available in Next.js to make your app lightning fast. We’ll go over them below.
I’ll assume that you are already familiar with data fetching methods, and differences between client and server components in Next.js. In previous versions of the framework all the ways to fetch data were cached by default. In Next.js 15 the approach was changed – now nothing is cached by default, and we have to opt in to take advantage of the cache.
Let’s consider the above example: We want to show a list of available recipes to the user. It’s a simple app, there is no interactivity, so we use a server component
interface Recipe {
id: string;
title: string;
}
const Recipes = async () => {
const data = await fetch("https://api.example.com/recipes");
const recipes: Recipe[] = await data.json();
return (
<div>
<h1>Recipes:</h1>
<ul>
{recipes.map((recipe) => (
<li key={recipe.id}>{recipe.title}</li>
))}
</ul>
</div>
);
};
export default Recipes;
You run the dev server, everything works fine so you build and deploy your app. The list is of recipes is still there, but when the new recipes are added to the API, they are not showing up on your page. What’s going on, the data shouldn’t be cached by default in Next.js 15, where are the new recipes?
Well, the data is not being cached, but the page isn’t using any Dynamic API, so Next treats it as a static page and renders it once during build, running the fetch() then, to populate the recipes list. In that case we still need to explicitly use the default cache: “no-store” fetch option in order to make the page dynamically rendered.
export const dynamic = "force-dynamic"; // this will force the page to be dynamically rendered
interface Recipe {
id: string;
title: string;
}
const Recipes = async () => {
const data = await fetch("https://api.example.com/recipes", {
cache: "no-store", // this option will make the page dynamic as well
});
const recipes: Recipe[] = await data.json();
return (
<div>
<h1>Recipes:</h1>
<ul>
{recipes.map((recipe) => (
<li key={recipe.id}>{recipe.title}</li>
))}
</ul>
</div>
);
};
export default Recipes;
But now we're back to the beginning. Data is being fetched from the API every time user enters the page, and our app needs to wait for the response. That’s where Incremental Static Regeneration (ISR) comes into play.
Next.js has a way to update the static content without rebuilding the entire site. It reduces the server load by serving prerendered, static pages for most requests, and rebuilding only the parts that require to be updated dynamically. Taking advantage of this feature requires literally just one line of code:
export const revalidate = 3600; // Time in seconds between content revalidations
interface Recipe {
id: string;
title: string;
}
const Recipes = async () => {
const data = await fetch("https://api.example.com/recipes", {
next: { revalidate: 30 }, // You can also use the revalide as a fetch option
});
const recipes: Recipe[] = await data.json();
return (
<div>
<h1>Recipes:</h1>
<ul>
{recipes.map((recipe) => (
<li key={recipe.id}>{recipe.title}</li>
))}
</ul>
</div>
);
};
export default Recipes;
What is exactly happening there?
The users have access to the fresh data almost instantly, your app is not bombarding the API with constant requests, and the content is served statically which allows search engines like Google to index your page not only using metadata, but the actual content too.
Sounds awesome, right? 🚀
Be the first to share your thoughts and start the conversation.