next
Version:
The React Framework
365 lines (276 loc) • 15.8 kB
Markdown
---
title: Caching and Revalidating (Previous Model)
nav_title: Caching (Previous Model)
description: Learn how to cache and revalidate data using fetch options, unstable_cache, and route segment configs for projects not using Cache Components.
---
> This guide assumes you are **not** using [Cache Components](/docs/app/getting-started/caching) which was introduced in version 16 under the [`cacheComponents` flag](/docs/app/api-reference/config/next-config-js/cacheComponents).
## Caching `fetch` requests
By default, [`fetch`](/docs/app/api-reference/functions/fetch) requests are not cached. You can cache individual requests by setting the `cache` option to `'force-cache'`.
```tsx filename="app/page.tsx" switcher
export default async function Page() {
const data = await fetch('https://...', { cache: 'force-cache' })
}
```
```jsx filename="app/page.jsx" switcher
export default async function Page() {
const data = await fetch('https://...', { cache: 'force-cache' })
}
```
See the [`fetch` API reference](/docs/app/api-reference/functions/fetch) to learn more.
### `unstable_cache` for non-`fetch` functions
`unstable_cache` allows you to cache the result of database queries and other async functions that don't use `fetch`. Wrap `unstable_cache` around the function:
```ts filename="app/lib/data.ts" switcher
import { unstable_cache } from 'next/cache'
import { db } from '@/lib/db'
export const getCachedUser = unstable_cache(
async (id: string) => {
return db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0])
},
['user'], // cache key prefix
{
tags: ['user'],
revalidate: 3600,
}
)
```
```js filename="app/lib/data.js" switcher
import { unstable_cache } from 'next/cache'
import { db } from '@/lib/db'
export const getCachedUser = unstable_cache(
async (id) => {
return db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0])
},
['user'], // cache key prefix
{
tags: ['user'],
revalidate: 3600,
}
)
```
The third argument accepts:
- `tags`: an array of tags for on-demand revalidation with `revalidateTag`.
- `revalidate`: the number of seconds before the cache is revalidated.
See the [`unstable_cache` API reference](/docs/app/api-reference/functions/unstable_cache) to learn more.
### Route segment config
You can configure caching behavior at the route level by exporting config options from a [Page](/docs/app/api-reference/file-conventions/page), [Layout](/docs/app/api-reference/file-conventions/layout), or [Route Handler](/docs/app/api-reference/file-conventions/route).
#### `dynamic`
Change the dynamic behavior of a layout or page to fully static or fully dynamic.
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
export const dynamic = 'auto'
// 'auto' | 'force-dynamic' | 'error' | 'force-static'
```
```jsx filename="layout.js | page.js | route.js" switcher
export const dynamic = 'auto'
// 'auto' | 'force-dynamic' | 'error' | 'force-static'
```
- **`'auto'`** (default): The default option to cache as much as possible without preventing any components from opting into dynamic behavior.
- **`'force-dynamic'`**: Force [dynamic rendering](/docs/app/glossary#dynamic-rendering), which will result in routes being rendered for each user at request time. This option is equivalent to:
- 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'`
- **`'error'`**: Force prerendering and cache the data of a layout or page by causing an error if any components use Request-time APIs or uncached data. This option is equivalent to:
- `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'`.
- **`'force-static'`**: Force prerendering and cache the data of a layout or page by forcing [`cookies`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers) and [`useSearchParams()`](/docs/app/api-reference/functions/use-search-params) to return empty values. It is possible to [`revalidate`](#route-segment-config-revalidate), [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath), or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag), in pages or layouts rendered with `force-static`.
#### `fetchCache`
<details>
<summary>This is an advanced option that should only be used if you specifically need to override the default behavior.</summary>
By default, Next.js **will cache** any `fetch()` requests that are reachable **before** any Request-time APIs are used and **will not cache** `fetch` requests that are discovered **after** Request-time APIs are used.
`fetchCache` allows you to override the default `cache` option of all `fetch` requests in a layout or page.
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
export const fetchCache = 'auto'
// 'auto' | 'default-cache' | 'only-cache'
// 'force-cache' | 'force-no-store' | 'default-no-store' | 'only-no-store'
```
```jsx filename="layout.js | page.js | route.js" switcher
export const fetchCache = 'auto'
// 'auto' | 'default-cache' | 'only-cache'
// 'force-cache' | 'force-no-store' | 'default-no-store' | 'only-no-store'
```
- **`'auto'`** (default): The default option to cache `fetch` requests before Request-time APIs with the `cache` option they provide and not cache `fetch` requests after Request-time APIs.
- **`'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'`. This means that even `fetch` requests after Request-time APIs are considered static.
- **`'only-cache'`**: Ensure all `fetch` requests opt into caching by changing the default to `cache: 'force-cache'` if no option is provided and 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'`.
- **`'default-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 Request-time APIs are considered dynamic.
- **`'only-no-store'`**: Ensure all `fetch` requests opt out of caching by changing the default to `cache: 'no-store'` if no option is provided and causing an error if any `fetch` requests use `cache: 'force-cache'`
- **`'force-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
- Any options set across each layout and page of a single route need to be compatible with each other.
- If both the `'only-cache'` and `'force-cache'` are provided, then `'force-cache'` wins. If both `'only-no-store'` and `'force-no-store'` are provided, then `'force-no-store'` wins. The force option changes the behavior across the route so a single segment with `'force-*'` would prevent any errors caused by `'only-*'`.
- The intention of the `'only-*'` and `'force-*'` options is to guarantee the whole route is either fully static or fully dynamic. This means:
- A combination of `'only-cache'` and `'only-no-store'` in a single route is not allowed.
- A combination of `'force-cache'` and `'force-no-store'` in a single route is not allowed.
- A parent cannot provide `'default-no-store'` if a child provides `'auto'` or `'*-cache'` since that could make the same fetch have different behavior.
- It is generally recommended to leave shared parent layouts as `'auto'` and customize the options where child segments diverge.
</details>
## Time-based revalidation
Use the `next.revalidate` option on `fetch` to revalidate data after a specified number of seconds:
```tsx filename="app/page.tsx" switcher
export default async function Page() {
const data = await fetch('https://...', { next: { revalidate: 3600 } })
}
```
```jsx filename="app/page.jsx" switcher
export default async function Page() {
const data = await fetch('https://...', { next: { revalidate: 3600 } })
}
```
For non-`fetch` functions, `unstable_cache` accepts a `revalidate` option in its configuration (see [example above](#unstable_cache-for-non-fetch-functions)).
### Route segment config `revalidate`
Set the default revalidation time for a layout or page. This option does not override the `revalidate` value set by individual `fetch` requests.
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
export const revalidate = false
// false | 0 | number
```
```jsx filename="layout.js | page.js | route.js" switcher
export const revalidate = false
// false | 0 | number
```
- **`false`** (default): The default heuristic to cache any `fetch` requests that set their `cache` option to `'force-cache'` or are discovered before a Request-time API is used. Semantically equivalent to `revalidate: Infinity` which effectively means the resource should be cached indefinitely. 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. Or set `revalidate` to a positive number lower than the route default to increase the revalidation frequency of a route.
- **`0`**: Ensure a layout or page is always dynamically rendered even if no Request-time APIs or uncached data fetches are discovered. This option changes the default of `fetch` requests that do not set a `cache` option to `'no-store'` but leaves `fetch` requests that opt into `'force-cache'` or use a positive `revalidate` as is.
- **`number`**: (in seconds) Set the default revalidation frequency of a layout or page to `n` seconds.
> **Good to know**:
>
> - The revalidate value needs to be statically analyzable. For example `revalidate = 600` is valid, but `revalidate = 60 * 10` is not.
> - The revalidate value is not available when using `runtime = 'edge'`.
> - In Development, Pages are _always_ rendered on-demand and are never cached. This allows you to see changes immediately without waiting for a revalidation period to pass.
#### 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. This allows you to dynamically opt-in to more frequent revalidation for certain routes based on some criteria.
## On-demand revalidation
To revalidate cached data after an event, use [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) or [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) in a [Server Action](/docs/app/getting-started/mutating-data) or [Route Handler](/docs/app/api-reference/file-conventions/route).
### Tagging cached data
Tag `fetch` requests with `next.tags` to enable on-demand cache invalidation:
```tsx filename="app/lib/data.ts" switcher
export async function getUserById(id: string) {
const data = await fetch(`https://...`, {
next: { tags: ['user'] },
})
}
```
```jsx filename="app/lib/data.js" switcher
export async function getUserById(id) {
const data = await fetch(`https://...`, {
next: { tags: ['user'] },
})
}
```
For non-`fetch` functions, `unstable_cache` also accepts a `tags` option (see [example above](#unstable_cache-for-non-fetch-functions)).
### `revalidateTag`
Invalidate cached data by tag using [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag):
```tsx filename="app/lib/actions.ts" switcher
import { revalidateTag } from 'next/cache'
export async function updateUser(id: string) {
// Mutate data
revalidateTag('user')
}
```
```jsx filename="app/lib/actions.js" switcher
import { revalidateTag } from 'next/cache'
export async function updateUser(id) {
// Mutate data
revalidateTag('user')
}
```
### `revalidatePath`
Invalidate all cached data for a specific route path using [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath):
```tsx filename="app/lib/actions.ts" switcher
import { revalidatePath } from 'next/cache'
export async function updateUser(id: string) {
// Mutate data
revalidatePath('/profile')
}
```
```jsx filename="app/lib/actions.js" switcher
import { revalidatePath } from 'next/cache'
export async function updateUser(id) {
// Mutate data
revalidatePath('/profile')
}
```
## Deduplicating requests
If you are not using `fetch` (which is [automatically memoized](/docs/app/api-reference/functions/fetch#memoization)), and instead using an ORM or database directly, you can wrap your data access with the [React `cache`](https://react.dev/reference/react/cache) function to deduplicate requests within a single render pass:
```tsx filename="app/lib/data.ts" switcher
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
export const getPost = cache(async (id: string) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
})
```
```jsx filename="app/lib/data.js" switcher
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
export const getPost = cache(async (id) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
})
```
## Preloading data
You can preload data by creating a utility function that you eagerly call above blocking requests. This lets you initiate data fetching early, so the data is already available by the time the component renders.
Combine the [`server-only` package](https://www.npmjs.com/package/server-only) with React's [`cache`](https://react.dev/reference/react/cache) to create a reusable preload utility:
```ts filename="utils/get-item.ts" switcher
import { cache } from 'react'
import 'server-only'
export const getItem = cache(async (id: string) => {
// ...
})
export const preload = (id: string) => {
void getItem(id)
}
```
```js filename="utils/get-item.js" switcher
import { cache } from 'react'
import 'server-only'
export const getItem = cache(async (id) => {
// ...
})
export const preload = (id) => {
void getItem(id)
}
```
Then call `preload()` before any blocking work so the data starts loading immediately:
```tsx filename="app/item/[id]/page.tsx" switcher
import { getItem, preload, checkIsAvailable } from '@/lib/data'
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
// Start loading item data
preload(id)
// Perform another asynchronous task
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
async function Item({ id }: { id: string }) {
const result = await getItem(id)
// ...
}
```
```jsx filename="app/item/[id]/page.js" switcher
import { getItem, preload, checkIsAvailable } from '@/lib/data'
export default async function Page({ params }) {
const { id } = await params
// Start loading item data
preload(id)
// Perform another asynchronous task
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
async function Item({ id }) {
const result = await getItem(id)
// ...
}
```