ProNextJS
    Loading
    lesson

    Cache-busting with Tags

    Jack HerringtonJack Herrington

    There are different ways to fetch data from a database and cache that data in Next.js. To make things interesting, we'll simulate database interactions and observe how caching behaves with dynamic routes.

    We'll start by creating a new db-time route and page at app/db-time/page.tsx. This page will fetch the current time from our simulated database, but before we build the page we'll create the database itself in a file db-time.ts:

    // inside app/db-time/db-time.ts
    
    export async function getDBTime() {
      return { 
        time: new Date().toLocaleTimeString();
      };
    }
    

    Then for the page, we'll import the getDBTime function and render it:

    // inside app/db-time/page.tsx
    
    import { getDBTime } from "./db-time";
    
    export default async function DBTime() {
      const { time } = await getDBTime();
    
      console.log(`Render /db-time ${new Date().toLocaleTimeString()}`);
    
      return (
        <div>
          <h1 className="text-2xl">Time From DB</h1>
          <p className="text-xl">{time}</p>
        </div>
      );
    }
    

    We can see that the time route is dynamic, but our db-time route is static because it hasn't detected anything dynamic in the route:

    route is dynamic

    If we visit the /db-time route, we'll see that the time is static and doesn't change on each refresh.

    Forcing Dynamic Rendering

    As we've seen, we could use the force-dynamic option to make the route dynamic, but this would make the entire page dynamic.

    In order to be more surgical and specific with our caching, we can use Next.js's unstable_cache, which is a persistent cache that goes between requests:

    // inside app/db-time/db-time.ts
    
    import { unstable_cache } from "next/cache";
    
    export async function getDBTimeReal() {
      return { time: new Date().toLocaleTimeString() };
    }
    
    export const getDBTime = unstable_cache(getDBTimeReal);
    

    Now the db-time route is dynamic:

    the db-time route

    However, the cache is still active between requests, even though the route is dynamic.

    Adding a Revalidation Button

    Similar to before, we'll add a button to revalidate the cache:

    // inside app/db-time/RerenderDBTimeButton.tsx
    "use client";
    import { Button } from "@/components/ui/button";
    
    export default function RevalidateDBTimeButton({
      onRevalidate,
    }: {
      onRevalidate: () => Promise<void>;
    }) {
      return (
        <Button onClick={async () => await onRevalidate()} className="mt-4">
          Revalidate DB Time
        </Button>
      );
    }
    

    Back in the page.tsx file, we'll being in the button and use the revalidateTag function to revalidate the cache:

    import { revalidateTag } from "next/cache";
    import { getDBTime } from "./db-time";
    import RevalidateDBTimeButton from "./RevalidateDBTimeButton";
    
    export const dynamic = "force-dynamic";
    
    export default async function DBTime() {
      const { time } = await getDBTimeReal();
    
      console.log(`Render /db-time ${new Date().toLocaleTimeString()}`);
    
      async function onRevalidate() {
        "use server";
        revalidateTag("db-time");
      }
    
      return (
        <div>
          <h1 className="text-2xl">Time From DB</h1>
          <p className="text-xl">{time}</p>
          <RevalidateDBTimeButton onRevalidate={onRevalidate} />
        </div>
      );
    }
    

    Then back in the db-time.ts file, we'll add the tag to the call to unstable_cache:

    // inside app/db-time/db-time.ts
    
    export const getDBTime = unstable_cache(getDBTimeReal, ["db-time"], {
      tags: ["db-time"],
    });
    

    Clicking this button will trigger the server action, which invalidates the 'db-time' tag, causing Next.js to re-fetch data on the next request.

    As we've seen, using revalidatePath is also an option, but using tags allows for more granular control over which cache to invalidate.

    Transcript