ProNextJS
    Loading
    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