Date Caching Behavior
Next.js v15 is out, and with it comes some changes to its caching behavior. The cache behavior has changed between versions 14 and v15. Now, it's very basic.
Caching Behavior in Next.js v15
The new caching behavior in v15 is that they've reverted all of the cached by default to dynamic by default. This is because since the apparatus come out, people have been complaining about its overly heavy-handed caching. This means that as you're developing, the pages look great, but then you build and start the application, and suddenly, things aren't working because you've got all this cached data lying around.
To fix this issue, you had to clear all those caches to get your app back to being dynamic. So now, with v15, you don't need to do any of that. The only thing you need to worry about is when you actually want things cached.
dynamicIO
Caching
In the next couple of segments, we're going to look at both of these things in tandem. We're going to take a look at how Next.js v15 stock out of the box does caching on particular types of data, and then how the dynamicIO
does caching on that same kind of data. We're going to take a look at multiple types of data:
- Dates
- Database access
- File access
- Fetching
We'll see how all of that is actually cached.
Let's jump into the code and explore these changes. In this video, we'll explore three directories:
dynamicIO
caching directory- Stock v15 caching directory (contains examples for out-of-the-box Next.js v15)
- A time service (used when we talk about how to cache fetches)
Stock v15 Caching Example
Let's take a look at what stock v15 looks like. This is where you'd want to get a date on the server. In this case, we want a dynamic date, that we can see it update in every reload.
Setting Up dynamicIO
-
Remove
node_modules
and.next
from thedynamicIO
caching directory (copy it from the stock v15 one). -
To enable
dynamicIO
, go intopackage.json
and change the current version of Next (which is v15.0.3) to the canary release.cd dynamicio-catching
-
Install the dependencies:
pnpm install
You'll get a warning about a React version number. To fix this, you'll need to update the React versions in
package.json
to match the versions from the console warning, and then do another install.pnpm install
-
Configure Next to run in
dynamicIO
mode. In your configuration options, bring in theexperimental
option.
const nextConfig: NextConfig = {
experimental: {
dynamicIO: true,
},
};
- Inside the folder
app
remove all the directories exceptdate-dynamic
After removing the directories, let's open our dynamicIO
app and see what happens. When we visit the same route as before, we now encounter an error.
The error indicates that we're receiving some dynamic data. The first thing we need to do is await the connection
.
import { connection } from "next/server";
export default async function DynamicDate() {
await connection();
const date = new Date();
return <div> Dynamic Date: {date.toLocaleString()}</div>;
}
Defining the Goal
At the beginning of each file, we'll define a goal. In this case, our goal is to never cache the dates. This helps us understand the purpose of caching in the current context. After making the changes, we refresh the page but we are still getting annother error.
This new error suggests that for short-lived data, we should use a suspense boundary
. There are two rules when it comes to dynamicIO
:
- If you want something dynamic, wrap it in a suspense boundary.
- If you want something cached, use the
use cache
directive.
Using Suspense and Caching in Next.js
In this example, we want to avoid caching the date component and use Suspense to handle it. First, let's create a DateDynamic
component and export it.
// dateDynamic.js
import { Suspense } from "react";
...
export default function DateDynamic() {
return (
<Suspense fallback={<div> Loading...</div>}>
<DateComponent />
</Suspense>
);
}
The Suspense
component helps us handle slow API calls or fetches by rendering the results of DateComponent
once it's completed. It's important to note that this is very fast in this example, and the real benefit comes when working with slower APIs.
Next, let's take a look at how to implement caching. We'll use the date-cached
folder for this purpose.
//Goal: Cache the date for 10 seconds
export const dynamic = "force-static";
export const revalidate = 10;
export default async function CachedDate() {
const date = new Date();
return <div> Cached Date: {date.toLocaleString()}</div>;
}
The goal here is to cache the date for 10 seconds. In Next.js v15, we would achieve this by setting the route to be static. Remember that by default, everything is dynamic in Next.js v15, so we need to explicitly tell it to cache the component.
First, we attempt to use dynamic
and revalidate
, but we'll have a compatibility issue with dynamicIO
.
// Failed attempt
dynamic(...);
revalidate(...);
To resolve this, we'll use the use cache
instead. Inside the function we want to cache, or at the top level of the module, we add use cache
.
// export const dynamic = "force-static";
// export const revalidate = 10;
export default async function CachedDate() {
"use cache";
const date = new Date();
return <div> Cached Date: {date.toLocaleString()}</div>;
}
By default, using "use cache"
without specifying a cache life will cache the function forever. To set a cache life, we need to import the cacheLife
function from next/cache
.
import { unstable_cacheLife as cacheLife } from "next/cache";
Now, we can set the cache life for our function. In this example, we'll set it to revalidate every 10 seconds.
cacheLife({
revalidate: 10,
});
With this setup, the function will be cached, and revalidation will occur every 10 seconds.
A common misconception is that caching involves automatically refreshing data at regular intervals, like a cron job. However, that's not the case here. Caching only updates when a request comes in, and if no request arrives, the cache remains unchanged.
Cache by Path Example
In this example, our goal is to cache the date but invalidate it based on the path. We want the cache to be invalidated on-demand, which means it should only happen when a user clicks a button.
import { expirePath } from "next/cache";
import CacheInvalidateButton from "./cache-invalidate-button";
export default async function CachedPathDate() {
async function invalidate() {
"use server";
await expirePath("/date-cached-path");
}
const date = new Date();
return (
<div className="flex gap-5">
<div>Cached Date: {date.toLocaleString()}</div>
<CacheInvalidateButton invalidate={invalidate} />
</div>
);
}
Instead of using the await connection
method this time, we can use the "use cache"
method to cache the entire route. This approach also works as expected.
Now, the date is cached just as we want it to be.
Date Cached Tag Example
Let's explore the last date example: date-cached-tag
. Import it and give it a try.
Take a moment to experiment with this example on your own. Good luck!