Skip to content

Latest commit

 

History

History
382 lines (273 loc) · 17.3 KB

File metadata and controls

382 lines (273 loc) · 17.3 KB
title description version related
use cache
Learn how to use the use cache directive to cache data in your Next.js application.
experimental
title description links
Related
View related API references.
app/api-reference/next-config-js/dynamicIO
app/api-reference/next-config-js/cacheLife
app/api-reference/functions/cacheTag
app/api-reference/functions/revalidateTag
app/api-reference/functions/unstable_cache

The use cache directive designates a component, function, or file to be cached. It can be used at the top of a file to indicate that all functions in the file are cacheable, or inline at the top of a function to mark the function as cacheable. This is an experimental Next.js feature, and not a native React feature like use client or use server.

Enable support for the use cache directive with the dynamicIO flag in your next.config.ts file:

import type { NextConfig } from 'next'

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

export default nextConfig

The use cache directive will be available separately from thedynamicIO flag in the future.

Caching is a technique to improve the performance of web applications by storing the results of computations or data fetches. In Next.js you can use caching to optimize your applications rendering performance.

To explicitly cache certain asynchronous operations and achieve static behavior, you can use the use cache directive. This allows you to optimize rendering performance by caching results from async data requests, while still enabling dynamic rendering when needed.

The use cache directive is an experimental feature that aims to replace the unstable_cache function. Unlike unstable_cache, which is limited to caching JSON data and requires manual definition of revalidation periods and tags, use cache offers more flexibility. It allows you to cache a wider range of data, including anything that React Server Components (RSC) can serialize, as well as data-fetching outputs and component outputs.

Additionally, use cache automatically manages complexities by tracking both inputs and outputs, making it less likely for you to accidentally poison your cache. Since it serializes both inputs and outputs, you can avoid issues with incorrect cache retrieval.

use cache directive

The Next.js use cache directive allows you to cache entire routes, components, and the return value of functions. When you have an asynchronous function, you can mark it as cacheable by adding use cache at the top of the file or inside the function scope. This informs Next.js that the return value can be cached and reused for subsequent renders.

// File level
'use cache'

export default async function Page() {
  // ...
}

// Component level
export async function MyComponent() {
  'use cache'
  return <></>
}

// Function level
export async function getData() {
  'use cache'
  const data = await fetch('/api/data')
  return data
}

Good to know: Functions that use the use cache directive must not have any side-effects, such as modifying state, directly manipulating the DOM, or setting timers to execute code at intervals

Revalidating

By default when using the use cache directive Next.js sets a revalidation period of fifteen minutes with a near infinite expiration duration, meaning it's suitable for content that doesn't need frequent updates.

While this may be useful for content you don't expect to change often, you can use the cacheLife and cacheTag APIs for more granular caching control:

  • cacheLife: For time-based revalidation periods.
  • cacheTag: For on-demand revalidation.

Both of these APIs integrate across the client and server caching layers, meaning you can configure your caching semantics in one place and have them apply everywhere.

Basic example:

The example below shows how to use the cacheLife function at the function level to set a revalidation period of one day on the functions output:

import { unstable_cacheLife as cacheLife } from 'next/cache'

export async function MyComponent() {
  async function getData() {
    'use cache'
    cacheLife('days')
    const data = await fetch('/api/data')
    return data
  }

  return // Use the data here
}

How cache revalidation works

When a revalidation period of fifteen minutes is set, the following happens:

  1. Cache HIT: If a request is made within the 15 minute window, the cached data is served, and is a cache HIT.
  2. Stale data: If the request happens after 15 minutes the cached value is still served, but is now considered stale. Next.js will recompute a new cache entry in the background.
  3. Cache MISS: If the cache entry expires and a subsequent request is made, then Next.js will treat this as a cache MISS, and the data will be recomputed and fetched again from the source.

Time-based revalidation with cacheLife

The cacheLife function can only be used where the use cache directive is present, and allows you to define time-based revalidation periods based on cache profiles.

We recommend always adding a cache profile when using the use cache directive to explicitly define caching behavior.

Cache profiles are objects that contain the following properties:

Property Value Description Requirement
stale number Duration the client should cache a value without checking the server. Optional
revalidate number Frequency at which the cache should refresh on the server; stale values may be served while revalidating. Optional
expire number Maximum duration for which a value can remain stale before switching to dynamic fetching; must be longer than revalidate. Optional - Must be longer than revalidate

The "stale" property differs from the staleTimes setting in that it specifically controls client-side router caching. While staleTimes is a global setting that affects all instances of both dynamic and static data, the cacheLife configuration allows you to define "stale" times on a per-function or per-route basis.

Good to know: The “stale” property does not set the Cache-control: max-age header. It instead controls the client-side router cache.

Default cache profiles

Next.js provides a set of named cache profiles modeled on various timescales. If you don't specify a cache profile in the cacheLife function alongside the use cache directive, Next.js will automatically apply the “default” cache profile.

Profile Stale Revalidate Expire Description
default undefined 15 minutes INFINITE_CACHE Default profile, suitable for content that doesn't need frequent updates
seconds undefined 1 second 1 minute For rapidly changing content requiring near real-time updates
minutes 5 minutes 1 minute 1 hour For content that updates frequently within an hour
hours 5 minutes 1 hour 1 day For content that updates daily but can be slightly stale
days 5 minutes 1 day 1 week For content that updates weekly but can be a day old
weeks 5 minutes 1 week 1 month For content that updates monthly but can be a week old
max 5 minutes 1 month INFINITE_CACHE For very stable content that rarely needs updating

Basic example:

'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'

cacheLife('minutes')

The string values used to reference cache profiles don't carry inherent meaning; instead they serve as semantic labels. This allows you to better understand and manage your cached content within your codebase.

Defining reusable cache profiles

To create a reusable cache profile, choose a name that suits your use case. You can create as many custom cache profiles as needed. Each profile can be referenced by its name as a string value passed to the cacheLife function.

const nextConfig = {
  experimental: {
    dynamicIO: true,
    cacheLife: {
      biweekly: {
        stale: 60 * 60 * 24 * 14, // 14 days
        revalidate: 60 * 60 * 24, // 1 day
        expire: 86400 60 * 60 * 24 * 14, // 14 days
      },
    },
  },
}

module.exports = nextConfig

The example above caches for 14 days, checks for updates daily, and expires the cache after 14 days. You can then reference this profile throughout your application by its name:

'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'

cacheLife('biweekly')

// rest of code

Overriding the default cache profiles

While the default cache profiles provide a useful way to think about how fresh or stale any given part of cacheable output can be, you may prefer different named profiles to better align with your applications caching strategies.

You can override the default named cache profiles by creating a new configuration with the same name as the defaults.

The example below shows how to override the default “days” cache profile:

const nextConfig = {
  experimental: {
    dynamicIO: true,
    cacheLife: {
      days: {
        stale: 3600, // 1 hour
        revalidate: 900, // 15 minutes
        expire: 86400, // 1 day
      },
    },
  },
}

module.exports = nextConfig

Defining inlining cache profiles

For specific use cases, you can set a custom cache profile by passing an object to the cacheLife function:

'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'

cacheLife({
  stale: 3600, // 1 hour
  revalidate: 900, // 15 minutes
  expire: 86400, // 1 day
})

// rest of code

This inline cache profile will only be applied to the function or file it was created in. If you want to reuse the same profile throughout your application, you can add the configuration to the cacheLife property of your next.config.ts file.

Nested usage of use cache and cacheLife

When defining multiple caching behaviors, in the same route or component tree, if the inner caches specify their own cacheLife profile, the outer cache will respect the shortest cache duration among them. This applies only if the outer cache does not have its own explicit cacheLife profile defined.

Decision hierarchy for cache boundaries:

  1. Next.js will use the shortest cache profile found within the whole use cache boundary, excluding inner use cachedirectives.
  2. If no cache profile exists then the shortest profile times from all inner use cache calls applies to this use cache. If there are no inner use cache's then the default is used
  3. Inner caches at two levels deep, do not affect the outer cache since they have already provided their duration to their parent.

For example, if you add the use cache directive to your page, without specifying a cache profile, the default cache profile will be applied implicitly (cacheLife(”default”)). If a component imported into the page also uses the use cache directive with its own cache profile, the outer and inner cache profiles are compared, and shortest duration set in the profiles will be applied.

// Parent component
import { unstable_cacheLife as cacheLife } from 'next/cache'

export async function ParentComponent() {
  'use cache'
  cacheLife('days')

  return (
    <div>
      <ChildComponent />
    </div>
  )
}

// Child component
import { unstable_cacheLife as cacheLife } from 'next/cache'

export async function ChildComponent() {
  'use cache'
  cacheLife('hours')

  // This component's cache will respect the shorter 'hours' profile
}

Revalidate on-demand with cacheTag

A cacheTag is used in combination with revalidateTag to purge cache data, on-demand. The cacheTag function takes a single string value, or a string array.

In the below example the getData function uses the “weeks” cache profile, and defines a cacheTag on the functions cached output:

import {
  unstable_cacheTag as cacheTag,
  unstable_cacheLife as cacheLife,
} from 'next/cache'

export async function getData() {
  'use cache'
  cacheLife('weeks')
  cacheTag('my-data')

  const data = await fetch('/api/data')
  return data
}

You can then purge the cache on-demand using revalidateTag in another function, for examples, a route handler or Server Action:

'use server'

import { revalidateTag } from 'next/cache'

export default async function submit() {
  await addPost()
  revalidateTag('my-data')
}

See the revalidateTag docs for more information on purging cached data on-demand.

Examples

Caching entire routes with use cache

The placement of Suspense boundaries in your application determines how dynamic your components can be. Components inside a Suspense boundary are allowed to be dynamic, but this doesn't mean they automatically are. If you cache everything or your content is static, Next.js will still generate a static application. Using Suspense indicates that dynamic behavior is allowed within the boundary.

To ensure your route remains static, avoid using Suspense boundaries. If you must use them, you can maintain a static page by adding the use cache directive to both the layout and page components as they are treated as separate entry points in your application.

This is recommended for applications that previously used the export const dynamic = "force-cache" option, and will ensure the entire route is prerendered.

"use cache"
import { unstable_cacheLife as cacheLife} from 'next/cache'
cacheLife('minutes')

export default Layout({children}: {children: ReactNode}) {
  return <div>{children}</div>
}

And in your page.tsx file you can add the use cache directive to the top of the file, and define a cache profile:

"use cache"
import { unstable_cacheLife as cacheLife} from 'next/cache'
cacheLife('minutes')

async function Users() {
  const users = await fetch('/api/users');
  // loop through users
}

export default Page() {
  return (
    <main>
      <Users/>
    </main>
  )
}

Caching component output with use cache

You can use use cache at the component level to cache any fetches or computations performed within that component. When you reuse the component throughout your application it can share the same cache entry as long as the props maintain the same structure.

The props are serialized and form part of the cache key. If you use the same component in multiple places in your application, the cache entry will be reused as long as the serialized props produce the same value in each instance.

import { unstable_cacheLife as cacheLife } from 'next/cache'

interface BookingsProps {
  type: string
}

export async function Bookings({ type = 'massage' }: BookingsProps) {
  'use cache'
  cacheLife('minutes')

  async function getBookingsData() {
    const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
    return data
  }
  return //...
}

Caching function output with use cache

Since you can add use cache to any asynchronous function you aren't limited to caching components or routes only. You might want to cache a network request or database query or compute something that is very slow. By adding use cache to a function containing this type of work it becomes cacheable, and when reused, will share the same cache entry.

import { unstable_cacheLife as cacheLife } from 'next/cache'

export async function getData() {
  'use cache'
  cacheLife('minutes')

  const data = await fetch('/api/data')
  return data
}