ProNextJS
    lesson

    Date Caching Behavior

    Jack HerringtonJack Herrington

    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:

    1. dynamicIO caching directory
    2. Stock v15 caching directory (contains examples for out-of-the-box Next.js v15)
    3. 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

    1. Remove node_modules and .next from the dynamicIO caching directory (copy it from the stock v15 one).

    2. To enable dynamicIO, go into package.json and change the current version of Next (which is v15.0.3) to the canary release.

      cd dynamicio-catching
      
    3. 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
      
    4. Configure Next to run in dynamicIO mode. In your configuration options, bring in the experimental option.

    const nextConfig: NextConfig = {
    	experimental: {
    		dynamicIO: true,
    	},
    };
    
    1. Inside the folder app remove all the directories except date-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:

    1. If you want something dynamic, wrap it in a suspense boundary.
    2. 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!

    Transcript

    Next.js 15 is out and with it some changes to its caching behavior. The cache behavior has changed between 14 and 15. Now, it's very basic. The new caching behavior in 15 is that they've reverted all of the cached by default to dynamic by default. Now, I actually think this is a good thing because since the App Writer has come out, people have been complaining about its overly heavy-handed caching, which means that as you're developing, the pages look great, but then you build and you start the application, and suddenly things aren't working because you've got all this cache data lying around.

    So you need to figure out how to bust all those caches to get your app back to being dynamic. So now with 15, you don't need to do any of that. The only thing you need to worry about is when you actually want things cached. So that's just half of the equation. The other half of the equation is that they've actually come out with a new way of doing caching which is significantly easier called dynamic I.O.

    So we're actually in the next couple of segments here going to look at both of these things in tandem. We're gonna take a look at how 15 stock out of the box does caching on particular types of data, and then how the dynamic IO does caching on that same kind of data. And we're gonna take a look at multiple types of data. We're gonna take a look at dates, we're going to take a look at database access, file access, and fetching and see how all of that is actually cached. Alright, so let's jump into the code.

    So when you see this video there's actually three directories that are here. There is going to be a dynamic I o caching directory, a stock 15 caching directory. That's where all our examples are on the stock out of the box next JS 15. And then this tiny little time service that we'll use when we talk about how to cache fetches. So let's go take a look at what stock 15 looks like right now.

    So this is where you want to get a date on the server, and in this case, we want a dynamic date. So every time we hit refresh, we want to see that update. So let's try this out in dynamic IOs. So that means getting dynamic IOs set up. So let's go back over here.

    I'm gonna remove no modules and dot next. I'm gonna go to the top of the directory and I'm going to go create that dynamic I.O. Caching directory from the stock 15 one. Now in order to enable dynamic I O, I need to go into that package, Jason and change this around from the current version of next, which is 15 Oh three to the Canary release. So let's do that.

    Let's go into Dynamic I.O. And then install. Now you will get this warning about a React version number. So what I'm going to do is go in here and take these React versions and change them from their current versions to whatever came out in the console warning and then do another install. And there we go.

    Everything installs clean and fine. That's good. Okay, the next thing we need to do is configure next to be running in this dynamic IO mode. So to do that, we go over here to our options, and I'm going to bring in experimental, and the option that we're looking for is dynamic IO, and we're going to set that to true. Okay, so I'm actually going to go and also remove a lot of these.

    I'm gonna remove everything in here, except the dynamic, because that's the first one we're gonna look at. Because we're just going to take those from the original stock 15 version, and then we're going to fix them to work with dynamic IO. So all right, let's go bring up our dynamic IO app. All right, let's go take a look. That same route now gives us an error.

    So that error says that you are getting some dynamic data. So the first thing it asks us to do is await the connection. So I'm going to go down here and I'm going to go into our code to see what it looks like. So all we're doing right now is getting the date. So I'm going to do what they say to do, which is await the connection.

    To get connection, I need to import that. And you turn this into an async function. Now one more thing, right at the top of every one of these files is going to be a goal. And the goal in this case is to never cache the dates. We always know what the goal is here for our caching.

    I'm going to hit refresh again. And yeah, I get it, but we're still getting an error, but it's a different one this time. And this one tells us that for short-lived data, we want to use a suspense boundary. And this is one of the two rules when it comes to Dynamic I.O. If you want something that's dynamic, you want to wrap it in a suspense boundary.

    If you want something that's cached, then you use this use cache directive. Now what is our goal again? Well our goal is never to cache the date, so we're going to use a suspense for that. So I'm going to go and change this. So this is when I'll call now a date component.

    I'll create a new component called date dynamic and export that. Now, normally we just return that date component, but we want to wrap it in a suspense. So that's what it says to do. Let's go wrap it in a suspense. We'll import suspense, and there we go.

    We'll hit refresh, and now everything works fine. Now, the big win here of using a suspense is that should this date component take a while to do whatever it's going to do, then it's going to stream the results of DateComponent out to the client once it's completed. Now of course this is very very fast so it doesn't actually require that but Suspense is going to give us that for free and that's going to make it a lot easier to work with APIs and fetches that may potentially end up being really slow. So that's the first one. So let's go take a look at how to do caching.

    So I'm going to go over here to Stock 15 Caching, and I'm going to pull off our next example, and that is a date cached. So I'll copy that and I'll paste that into our app. So now I've got date cached. So let's take a look at the implementation on that. So our goal on this one is to cache that date, this time for 10 seconds.

    Now, with Next.js 15, what we would do is to say that we want this to be a static route. Remember, by default in 15, everything is dynamic, so we have to tell it to be cached. So in this case, that's what we're doing, telling it to be a static route, and we're telling it to revalidate if the next request coming in is more than 10 seconds older than the last cached value. So let's go try this out first. We go in there and we can see that we fail to compile because dynamic is no longer compatible with Dynamic I O.

    So we need to remove that as well as revalidate. So we can't use those. So what are we going to use instead? Well, we're going to use a use cache directive that tells Next.js 15 in Dynamic I O mode to cache this function. Whatever that function is, it could be any function that you wanted, you get to decide when you want that caching and you just put use cache in the body of that function or at the top level of the module and now bang you are a cached function.

    Awesome! So let's leave that as is and then we'll hit refresh and what we notice is if we stayed around for more than 10 seconds it wouldn't come back because useCache in this case if you don't actually add on a cache life means that you want to cache forever. So we're gonna bring in a cache life. That's an unstable function from next cache. We're gonna change that to just cache life and then we get to decide well how do we want to specify that cache.

    And then we get to tell it well what do we want for that cache life. So in this case we'll say that we want to revalidate every 10 seconds or so. And now I can see that we're cached, but if we just hold out for a couple more seconds here. And there we go. Now we've got a new value, and that's because the request came in after that 10 second threshold, and so it revalidated it.

    I think a lot of folks think that what's happening here is some kind of like cron job, that this is automatically going to refresh every 10 seconds or so. That's not what's happening here. What's happening here is that if a request comes in and it looks at the cache time and it sees that it's older then it recomputes at that time. But if no requests come in then it's not going to get re-rendered. Alright let's do two more.

    So let's cache by path. Okay so what are we talking about with this example? So we've got a page, the goal in this one is to cache the date but invalidate by path. So we're going to do an invalidation where we want to do it on demand. So you want to click a button and only at that point do we want to actually invalidate our cache.

    So let's take a look. So we got our path here, that's the actual path to that route, hit save, go back over here, and now you have an invalidate and then actually that seems to work, that's good. But we go here, we can see that we have that await connection thing or we can just use use cache. I'm just going to use cache to cache this whole route and let's take a look and that seems to work. Nice.

    Okay So now I've got a date that's cached just the way that we want it. Okay, let's take a look at our last date example and that's date cached tag. So I'll bring that in. All right, so I'm gonna give you a second to try this out on your own and I'll see you on the flip side.