Cache

Framework Next.js 13 Fetching Cache

Route Segment Config Options

The Route Segment Config Options allows you configure the behavior of a Page, Layout, or Route Handler by directly exporting the following variables:

// layout.js|page.js
export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'
export function generateStaticParams(...)

dynamic

Change the dynamic behavior of a layout or page to fully static or fully dynamic.

export const dynamic = 'auto'
// 'auto' | 'force-dynamic' | 'error' | 'force-static'
Options Description
auto (default) Cache as much as possible without preventing any components from opting into dynamic behavior
force-dynamic Disabling all caching of fetch requests and always revalidating
error Force static rendering and static data fetching of a layout or page by causing an error
force-static Force static rendering and static data fetching of a layout or page by forcing cookies(), headers() and useSearchParams() to return empty values.

dynamic = force-dynamic Equivalent

  • getServerSideProps() in the pages directory.
  • Setting the option of every fetch() request in a layout or page to { cache: 'no-store', next: { revalidate: 0 } }.
  • Setting the segment config to export const fetchCache = 'force-no-store'

dynamic = error Equivalent

  • getStaticProps() in the pages directory.
  • Setting the option of every fetch() request in a layout or page to { cache: 'force-cache' }.
  • Setting the segment config to fetchCache = 'only-cache', dynamicParams = false.
  • Note: dynamic = 'error' changes the default of dynamicParams from true to false. You can opt back into dynamically rendering pages for dynamic params not generated by generateStaticParams by manually setting dynamicParams = true.

dynamicParams

Control what happens when a dynamic segment is visited that was not generated with generateStaticParams.

export const dynamicParams = true // true | false,
Options Description
true (default) Dynamic segments not included in generateStaticParams are generated on demand.
false Dynamic segments not included in generateStaticParams will return a 404.

Notes

  • This option replaces the fallback: true | false | blocking option of getStaticPaths in the pages directory.
  • When dynamicParams = true, the segment uses Streaming Server Rendering.
  • If the dynamic = 'error' and dynamic = 'force-static' are used, it’ll change the default of dynamicParams to false.

revalidate

Set the default revalidation time for a layout or page

This option does NOT override the revalidate value set by individual fetch requests.

export const revalidate = false
// false | 'force-cache' | 0 | number
Options Description
false (default) To cache any fetch requests that set their cache option to 'force-cache' or are discovered before a dynamic function is used.
0 Ensure a layout or page is always dynamically rendered
number (in seconds) Set the default revalidation frequency of a layout or page to n seconds.

revalidate = false Equivalent

revalidate: Infinity

It is still possible for individual fetch requests to use cache: 'no-store' or revalidate: 0 to avoid being cached and make the route dynamically rendered.

revalidate = 0

  • Changes the default of fetch() requests cache option to 'no-store' if fetch() cache option NOT set.
  • fetch() requests cache options that set to 'force-cache' or use a positive revalidate will still work as is

Revalidation Frequency

  • The lowest revalidate across each layout and page of a single route will determine the revalidation frequency of the entire route. This ensures that child pages are revalidated as frequently as their parent layouts.

  • Individual fetch requests can set a lower revalidate than the route’s default revalidate to increase the revalidation frequency of the entire route.

fetchCache

fetchCache allows you to override the default cache option of all fetch requests in a layout or page.

// layout.js
export const fetchCache = 'auto'
// 'auto' | 'default-cache' | 'only-cache'
// 'force-cache' | 'force-no-store' | 'default-no-store' | 'only-no-store'
Options Description
auto (default) Cache fetch requests before dynamic functions with the cache option they provide and NOT cache fetch requests after dynamic functions.
default-cache Allow any cache option to be passed to fetch but if NO option is provided then set the cache option to 'force-cache'
only-cache Ensure all fetch requests opt into caching by changing the default to cache: 'force-cache' if no option is provided. Causing an error if any fetch requests use cache: 'no-store'.
force-cache Ensure all fetch requests opt into caching by setting the cache option of all fetch requests to 'force-cache'.
force-no-store Allow any cache option to be passed to fetch but if NO option is provided then set the cache option to 'no-store'. This means that even fetch requests before dynamic functions are considered dynamic.
default-no-store Ensure all fetch requests opt out of caching by changing the default to cache: 'no-store' if no option is provided. Causing an error if any fetch requests use cache: 'force-cache'
only-no-store Ensure all fetch requests opt out of caching by setting the cache option of all fetch requests to 'no-store'. This forces all fetch requests to be re-fetched every request even if they provide a 'force-cache' option.

Cross-route segment behavior

page1.js page2.js Winner
only-cache force-cache force-cache
only-no-store force-no-store force-no-store

The force option changes the behavior across the route so a single segment with 'force-*' would prevent any errors caused by 'only-*'.

page1.js page2.js Not allowed
only-cache only-no-store X
force-cache force-no-store X
default-no-store auto or *-cache X

The intention of the 'only-*' and force-*' options is to guarantee the whole route is either fully static or fully dynamic.

runtime

// layout.js
export const runtime = 'nodejs'
// 'experimental-edge' | 'nodejs'

preferredRegion

If a preferredRegion is NOT specified, it’ll inherit the option of the nearest parent layout.

The root layout defaults to auto.

// layout.js
export const preferredRegion = 'auto'
// 'auto' | 'home' | 'edge' | 'string'
Options Description
auto (default) Either the specified home region on your platform or the Edge if no waterfall requests are detected.
home The specified home region on your platform.
edge The Edge Network, if one is available on your platform.

generateStaticParams

To define the list of route segment parameters that will be statically generated at build time instead of on-demand at request time.

Segment-level Caching

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

Route Segment Cache Options

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

Fetch function specify a revalidate option

// 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();
  // ...
}

Different revalidate setting priority

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

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();
  // ...
}

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

Per-request Caching

React exposes a new function, cache(), that memoizes the result of a wrapped function

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

export const getUser = cache(async (id: string) => {
  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 UserLayout({
  params: { id },
}: {
  params: { id: string };
}) {
  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.

GraphQL and cache()

Only GET requests are cache on the fetch() function. But you can use cache() to cache the POST requests.

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

export const getUser = cache(async (id: string) => {
  const res = await fetch('/graphql', { method: 'POST', body: '...' })
  // ...
});

Preload pattern with cache()

The preload() function can have any name. It’s a pattern, NOT an API.

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

export const preload = (id: string) => {
  // void evaluates the given expression and returns undefined
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
  void getUser(id);
}
export default async function User({ id }: { id: string }) {
  const result = await getUser(id);
  // ...
}
// app/user/[id]/page.tsx
import User, { preload } from '@components/User';

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

Using On-Demand Revalidation

https://<your-site.com>/api/revalidate?secret=<token>

Add the secret as an Environment Variable to your application.

MY_SECRET_TOKEN=<token>

Create the revalidation API Route:

// pages/api/revalidate.js
export default async function handler(req, res) {
  // Check for secret to confirm this is a valid request
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' });
  }

  try {
    // This should be the actual path not a rewritten path
    // e.g. for "/blog/[slug]" this should be "/blog/post-1"
    await res.revalidate('/path-to-revalidate');
    return res.json({ revalidated: true });
  } catch (err) {
    // If there was an error, Next.js will continue
    // to show the last successfully generated page
    return res.status(500).send('Error revalidating');
  }
}

Testing On-Demand Revalidation During Development

next build
next start

Reference