Curriculum

The `use cache` Directive Deep Dive in Next.js

The Next.js Conf announced a very exciting update - a new caching mechanism.

It was announced that there is now a mechanism to control the caching of functions, components as well as entire routes. It is called as use cache .

Now, this is a very exciting update and it is a game changer. But that leaves questions like where do we use useCallback, useMemo and also different data fetching methods like SSG (Static Site Generation), SSR (Server Side Rendering) and ISR (Incremental Static Regeneration) and what about the unstable_cache directive?

This blog post will answer all those questions.

If I have to summarize the use cache directive in one sentence, I would say,

What is use cache?

use cache is a directive like use client or use server. It is used to control the caching of not just functions but also components and routes.

This new mechanism is a successor to the experimental unstable_cache and offers more flexibility and power. With unstable_cache, you could only cache JSON data but with use cache, you can cache any serializable data, including components and routes.

We talked about what serialization is and how it works already in a previous lesson in the course .

So, anything RSC (React Server Components) friendly can be cached as long as it is serializable. Why? Since RSC also has the same criteria that you can only pass serializable values to props. Take a look at the React Server Components Serializable Props here .

What are some key features of use cache?

  1. Versatile Caching: Unlike unstable_cache which could only cache JSON data, use cache can cache any serializable data, including components and routes.

  2. Multiple Caching Levels: You can apply caching at different levels:

    • Function level
    • Component level
    • Route level (entire file)
  3. Automatic Caching: When you add the use cache directive to a file, everything inside that file becomes cacheable, similar to how use client works where adding use client directive to a file makes everything inside that file a client component.

How do we use use cache?

To implement use cache, you need to:

  1. Use the canary version of Next.js
  2. Enable the experimental flag in your next.config.ts file

Here's a basic example of how to enable use cache experimental feature:

next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  experimental: {
    dynamicIO: true,
  },
};

export default nextConfig;

Let's take a look at a demo for how it works.

Demo 🔥

In the below demo, you can see how the caching works at different levels. We have a RootLayout plus a Navbar, Sidebar, Post, Comments and a Footer as its children. You can control the caching of each of these components individually, thanks to use cache directive.

By default, all components are uncached. You can use the 'use cache' toggle to toggle the caching of the components.

But if you want to cache the entire layout, you can do that by adding the use cache directive at the top of your layout file or in this demo, you can toggle the main "use cache" switch of the RootLayout component.

After you press the invalidate cache button, you will see that the cached layout will be revalidated and the new data will be fetched from the server.

There are several Cache Profiles you can choose from to control the cache duration. Next.js has offered several options like seconds, minutes, hours, days, weeks, months, years. You can simply pick one of them from the dropdown and reference the definition of those in the legend.

If you don't specify a cache profile, the default cache duration is 15 minutes and it never expires.

Caching Components Demo

Legend:

Cached Content
Fresh Data
Manual Invalidation
Locked (Inherited Cache)

Cache Profiles:

default: Revalidates every 15 minutes, never expires
seconds: Revalidates every second, expires after 1 minute
minutes: Stale after 5 minutes, revalidates every minute, expires after 1 hour
hours: Stale after 5 minutes, revalidates every hour, expires after 1 day
days: Stale after 5 minutes, revalidates every day, expires after 1 week
weeks: Stale after 5 minutes, revalidates every week, expires after 1 month
max: Stale after 5 minutes, revalidates every month, never expires

Root

Root content

Navbar

Navbar content

Sidebar

Sidebar content

Post

Post content

Comments

Comments content

Footer

Footer content

Controlling Cache Behavior

You get granular control caching controls with use cache directive. As shown in the demo above, you can control the cache duration, cache tag and cache profile.

Cache Tag

You can also tag cached items for selective revalidation which means you can invalidate or revalidate specific cached items after a specific amount of time.

Here's an example where we are caching the fetchProducts function and tagging it with product-details cache tag. The first time, fetchProducts is called, it fetches the products from the API and caches the result with the product-details cache tag.

The next time, fetchProducts is called, it will return the cached result if the cache is still valid.

app/data/fetch-products.ts
import { unstable_cacheTag as cacheTag } from 'next/cache';

export async function fetchProducts() {
  'use cache';
  cacheTag('product-details');

  const response = await fetch(
    'https://api.mockapi.io/products'
  );
  return response.json();
}

Cache Life

You can control the cache duration by using the unstable_cacheLife function from the next/cache module. Cache Life is the amount of time the cache will be valid for. It is the life of the cache!

Here's an example where we are caching the fetchProducts function and setting the cache life to 1 hour. By selecting hours, the cache will be invalidated after 1 hour.

I'd encourage you to try it out in the demo above.

app/data/fetch-products.ts
import {
  unstable_cacheLife as cacheLife,
  unstable_cacheTag as cacheTag,
} from 'next/cache';

export async function fetchProducts() {
  'use cache';

  cacheLife('hours');
  cacheTag('product-details');

  const response = await fetch('https://api.mockapi.io/products');
  return response.json();
}

Purging and Revalidating Cache

To purge or revalidate cached data, you can use server actions revalidateTag function.

Just like you can purge the cache of a component using the revalidateTag function when the cache is tagged by adding { next: { tags: ['a', 'b', 'c'] } } as a parameter to fetch. You then purge the cache of a route using the revalidateTag function.

In the case of use cache, you use the cacheTag function to tag the cache and still use the revalidateTag function to purge the cache.

So, revalidateTag is a function that can be used to purge the cache of a component or a route.

Here's an example where we are purging the product-details cache tag.

app/data/server-action.ts
'use server';
import { revalidateTag } from 'next/cache'; 

export async function purgeProductDetailsCache() {
  revalidateTag('product-details');
}

Caching Components

You can also cache components as long as their props are serializable (i.e. React Server Components friendly). A cache key is allocated based on the serialized props.

Here's an example:

app/components/ExpensiveComponent.tsx
import Image from 'next/image';
import { Dog } from '@/types';
import DogCard from '@/components/dog-card';

// Simulate an API call with caching
export async function fetchDogs(): Promise<Dog[]> {
  const urls = ['https://http.dog/100.json', 'https://http.dog/200.json', 'https://http.dog/401.json'];
  const responses = await Promise.all(urls.map(url => fetch(url)));
  const data = await Promise.all(responses.map(response => response.json()));

  console.log('Hi Fetching Dogs');
  return data;
}

export default async function DogList() {
  'use cache';
  const dogs = await fetchDogs();

  return (
    <div className="max-w-2xl mx-auto p-6 bg-pink-200 shadow-md rounded-lg">
      <h1 className="text-3xl font-bold text-left mb-6">Dogs</h1>
      <ul className="grid grid-cols-3 gap-4">
        {dogs.map((dog: Dog) => <DogCard key={dog.id} dog={dog} />)}
      </ul>
    </div>
  );
}

Route-level Caching

To cache an entire route, add the use cache directive at the top of your page file. This makes the entire file cacheable but also makes all the components inside the file cacheable i.e. it's children components are also cached.

In the below example, the entire page.tsx file is cached and so are the DogList and ProductsList components as they are children of the page.tsx file.

app/page.tsx
'use cache';

import DogList from '@/components/dog-list';
import ProductsList from '@/components/products-list';
import { Suspense } from 'react';

export default async function Page() {
  return (
    <div className="p-12">
      <DogList />
      <Suspense fallback={<div>Loading...</div>}>
        <ProductsList />
      </Suspense>
    </div>
  );
}

Considerations

  • use cache is still an experimental feature, so keep an eye on the official Next.js documentation for updates.
  • When caching at the route level, be mindful of client interactivity and re-rendering needs.
  • The default cache duration is 15 minutes if not specified.
  • Be cautious when caching dynamic content or frequently changing data.

By leveraging use cache, you can significantly improve the performance of your Next.js applications by reducing unnecessary computations and network requests. However, it's crucial to implement caching thoughtfully to ensure data freshness and correctness in your application.

I also want to clarify that use cache is not a replacement for use server or use client. It is a new directive that allows you to control the caching of different parts of your application.

What about useCallback, useMemo and other performance optimizations?

Well, remember, with React compiler, we are going to use useCallback and useMemo less as React Compiler will optimize the code for us. Try checking out this video to learn more about how React Compiler works and how it will optimize the code for us.

How is use cache different from React Server Components and from data fetching methods like SSG, SSR and ISR?

React Server Components helped avoid the network roundtrip for data fetching as we can delegate the data fetching to the server. With data fetching methods like SSG, SSR and ISR, we can cache by default at build time or revalidate at runtime or refetch at runtime respectively. But these capabilities were mainly provided to the fetch function. This meant that a lot of apps that were not using fetch suffered.

So where does use cache fit in?

Well, the world of Next.js is heading towards Partial Pre-rendering as the future. With PPR, we can have a static shell while dynamic parts of the apps are rendered on the client. This allows Next.js to leverage the benefits of both worlds - the static shell can be cached while the dynamic parts are rendered on the client. How does the dynamic parts get rendered? Well, our good old friend Suspense boundary comes to the rescue!

use cache directive is the key to unlocking the power of PPR.

You can add use cache at different levels - function, component and route level depending on your use case but add a Suspense boundary around the dynamic parts of the apps. This unlocks a lot of power as a developer to control the caching of different parts of the apps without having to depend on the fetch function.

Now, you can still use the 3 data fetching methods like SSG (Static Site Generation), SSR (Server Side Rendering) and ISR (Incremental Static Regeneration) but you can also use use cache directive to cache the data at different levels. It gives you more control as a developer to cache the data at different levels. Which is why we get cacheTag and cacheLife functions to control the cache duration and cache tag.

If you think about it, a lot of our apps are dynamic in nature. We won't just have static content and not every app is a blog or a documentation site or a E-commerce site. use cache unlocks the power to do so.

Tip of the iceberg!

In this lesson, we talked about the use cache directive and how it works. But there is a lot more to Caching in Next.js that we haven't covered.

I've an entire section on Next.js Caching filled with full of interactive demos and exercises that goes way deeper into the topic.

Now, if you liked this post, you'll love the course. It's called The Modern Full Stack Next.js Course .

The Modern Full Stack Next.js Course

It's meant for folks who are looking to ship more real world applications with Next.js. Folks that are looking to know when to use what caching method, how to control caching at different levels and more.

If you'd like to improve the skills needed to land a great job as a Next.js developer or grow your career as a Next.js developer, you should check it out.

You can learn more here 👇

I hope you found this post helpful. 💖

The Course has launched in Early Access! 🎉

The Modern Full Stack Next.js Course has launched in Early Access with over 50% off!
Feedback