Caching Data

There are two ways of caching in Next.js: Segment-level and Per-Request Cache.

Segment-level Caching

Segment-level caching allows you to cache and revalidate data used in route segments.

This mechanism allows different segments of a path to control the cache lifetime of a route. Each page.tsx and layout.tsx in the route hierarchy can export a revalidate value that sets the revalidation time for the route.

app/page.tsx
export const revalidate = 60; // revalidate this page every 60 seconds

In addition to the exported revalidate value, requests made using fetch can specify a revalidate option to control the revalidation frequency of the route.

app/page.tsx
// revalidate this page every 10 seconds, since the getData's fetch
// request has `revalidate: 10`.
async function getData() {
  const res = await fetch('https://...', { next: { revalidate: 10 } });
  return res.json();
}

export default async function Page() {
  const data = await getData();
  // ...
}

If a page, layout, and fetch request all specify a revalidation frequency then the lowest value of the three will be used.

app/dashboard/layout.tsx
export const revalidate = 60;

export default function Layout() {
  // ...
}
app/dashboard/page.tsx
export const revalidate = 30;

async function getData() {
  const res = await fetch('https://...', { next: { revalidate: 10 } });
  return res.json();
}

export default async function Page() {
  const data = await getData();
  // ...
}

In this example the layout, page, and fetch request are all asking for the route to be revalidated at different times.

Since the fetch request has the lowest revalidation time, this page will use its value and be revalidated every 10 seconds.

See revalidate for other possible configuration options.

Good to know:

  • You can set fetchCache to 'only-cache' or 'force-cache' to ensure that all fetch requests opt into caching but the revalidation frequency might still be lowered by individual fetch requests. See fetchCache for more information.

Per-request Caching

Per-request caching allows you to cache and deduplicate requests on a per-request basis.

React exposes a new function, cache(), that memoizes the result of a wrapped function. The same function called with the same arguments will reuse a cached value instead of re-running the function. This cache() function was introduced in the first-class support for promises RFC.

You can use cache() to deduplicate data fetches on a per-request basis. If a function instance with the same arguments has been called before, anywhere in the server request, then we can return a cached value.

utils/getUser.tsx
import { cache } from 'react';

export const getUser = cache(async (id) => {
  const user = await db.user.findUnique({ id });
  return user;
});
app/user/[id]/layout.tsx
import { getUser } from '@utils/getUser';

export default async function UserLayout({ params: { id } }) {
  const user = await getUser(id);
  // ...
}
app/user/[id]/page.tsx
import { getUser } from '@utils/getUser';

export default async function Page({ params: { id } }) {
  const user = await getUser(id);
  // ...
}

Although the getUser() function is called twice in the example above, only one query will be made to the database. This is because getUser() is wrapped in cache(), so the second request can reuse the result from the first request.

Good to know:

  • fetch() has already been patched to include support for cache() automatically, so you don't need to wrap functions that use fetch() with cache(). See automatic request deduping for more information.
  • In this new model, we recommend fetching data directly in the component that needs it, even if you're requesting the same data in multiple components instead of prop drilling.
  • You can wrap functions you don't control such as third-party libraries with cache for the same per-request caching behavior.
  • Using cache allows shared data fetching without having to know if a parent layout was rendered during this request.
  • We recommend using the server-only package to make sure server data fetching functions are not accidentally used on the client.

Preload pattern with cache()

As a pattern, we suggest optionally exposing a preload() export in utilities or components that do data fetching.

components/User.tsx
import { getUser } from "@utils/getUser";

export const preload = (id) => {
  void getUser(id);
}
export default async function User({ id }) {
  const result = await getUser(id);
  // ...
}

By calling preload, you can eagerly start fetching data you're likely going to need.

app/user/[id]/page.tsx
import User, { preload } from "@components/User";

export default async function Page({ params: { id } }) {
  preload(id); // starting loading the user data now
  const condition = await fetchCondition();
  return condition ? <User id={id} /> : null;
}

Good to know:

  • The preload() function can have any name. It's a pattern, not an API.
  • This pattern is completely optional and something you can use to optimize on a case-by-case basis. This pattern is a further optimization on top of parallel data fetching. Now you don't have to pass promises down as props and can instead rely on the preload pattern.

Combining cache, preload, and server-only

You can combine the cache function, the preload pattern, and the server-only package to create a data fetching utility that can be used throughout your app.

utils/getUser.tsx
import { cache } from 'react';
import 'server-only';

export const preload = (id) => {
  void getUser(id);
}

export const getUser = cache(async (id) => {
  // ...
});

With this approach you can eagerly fetch data, cache responses, and guarantee that this data fetching only happens on the server.

The getUser.tsx exports can be used by layouts, pages, or components to give them control over when a user's data is fetched.