-
Notifications
You must be signed in to change notification settings - Fork 27.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Docs: Improve caching story #73437
Open
delbaoliveira
wants to merge
34
commits into
canary
Choose a base branch
from
docs-getting-started-caching-and-revalidating
base: canary
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+368
−32
Open
Docs: Improve caching story #73437
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
f2e3ee1
Clean up
delbaoliveira 9143654
Create 07-caching-and-revalidating.mdx
delbaoliveira 3b820a3
Add `caching` section (wip)
delbaoliveira b72324b
Add API reference description
delbaoliveira a8e6cfe
Clean up
delbaoliveira 8f1c93a
Add caching examples
delbaoliveira 4bebd04
Merge branch 'canary' into docs-getting-started-caching-and-revalidating
delbaoliveira 7254afb
Add revalidation section
delbaoliveira 23581d5
Add time-based revalidation examples
delbaoliveira 9bf78d8
Merge branch 'canary' into docs-getting-started-caching-and-revalidating
delbaoliveira 3a4a981
Clean up
delbaoliveira 1fbc5fd
Add on-demand revalidation example
delbaoliveira d79be66
Clean up
delbaoliveira 5d0eaa0
Remove mentions of expireTag and expirePath
delbaoliveira 5481a23
Clean up
delbaoliveira 2237db5
Merge branch 'canary' into docs-getting-started-caching-and-revalidating
delbaoliveira 8c78cc0
Update caching description
delbaoliveira fb5e279
Use api.vercel.app
delbaoliveira b6cf0a1
Update docs/01-app/01-getting-started/07-caching-and-revalidating.mdx
delbaoliveira eebe58c
Clarify that functions within a file are cached separately
delbaoliveira 6d9cd97
Update docs/01-app/01-getting-started/07-caching-and-revalidating.mdx
delbaoliveira b690c1e
Merge branch 'docs-getting-started-caching-and-revalidating' of https…
delbaoliveira f5a8342
Clean up
delbaoliveira b33b5ed
Merge branch 'canary' into docs-getting-started-caching-and-revalidating
delbaoliveira 89db02d
clean up
delbaoliveira 03b1ad2
Merge branch 'canary' into docs-getting-started-caching-and-revalidating
delbaoliveira 1b8a681
Merge branch 'canary' into docs-getting-started-caching-and-revalidating
delbaoliveira 4111b1a
Merge branch 'canary' into docs-getting-started-caching-and-revalidating
delbaoliveira 2a9effe
Merge branch 'canary' into docs-getting-started-caching-and-revalidating
delbaoliveira c839e79
Add "How `use cache` works" section
delbaoliveira 8d9e017
Move revalidation section up
delbaoliveira 66559c9
Remove repetition
delbaoliveira 03e4bc7
Clean up
delbaoliveira 4b2892e
grammar
delbaoliveira File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
299 changes: 299 additions & 0 deletions
299
docs/01-app/01-getting-started/07-caching-and-revalidating.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,299 @@ | ||
--- | ||
title: How to cache and revalidate components and functions | ||
nav_title: Caching and Revalidating | ||
description: Learn how to cache and revalidate components and functions in your Next.js application. | ||
related: | ||
title: API Reference | ||
description: Learn more about the features mentioned in this page by reading the API Reference. | ||
links: | ||
- app/api-reference/config/next-config-js/dynamicIO | ||
- app/api-reference/directives/use-cache | ||
- app/api-reference/functions/cacheLife | ||
- app/api-reference/functions/cacheTag | ||
- app/api-reference/functions/revalidateTag | ||
- app/api-reference/functions/revalidatePath | ||
--- | ||
|
||
> **Warning:** The content below assumes the [`dynamicIO` config option](/docs/app/api-reference/config/next-config-js/dynamicIO) is enabled in your application. This option was introduced in Next.js 15 canary. | ||
|
||
## Caching | ||
|
||
In Next.js, you can cache the output of a [component](#components) or [function](#functions) to reduce the need to re-execute work for every user request. | ||
|
||
Caching is useful for data that doesn't change often or is shared across users, and functions that are computationally intensive, as you can reuse the result and reduce the time it takes to respond to requests. | ||
|
||
To mark a function or component as cacheable, you can use the [`'use cache'`](/docs/app/api-reference/directives/use-cache) directive. | ||
|
||
### Components | ||
|
||
You can add `'use cache'` to an **asynchronous** [Server Component](https://react.dev/reference/rsc/server-components) to cache the component's render output: | ||
|
||
```tsx filename="app/blog/page.tsx" highlight={4} switcher | ||
import { getPosts } from '@/app/lib/data' | ||
|
||
export default async function Page() { | ||
'use cache' | ||
const posts = await getPosts() | ||
|
||
return ( | ||
<ul> | ||
{posts.map((post) => ( | ||
<li key={post.id}>{post.title}</li> | ||
))} | ||
</ul> | ||
) | ||
} | ||
``` | ||
|
||
```js filename="app/blog/page.js" highlight={4} switcher | ||
import { getPosts } from '@/app/lib/data' | ||
|
||
export default async function Page() { | ||
'use cache' | ||
const posts = await getPosts() | ||
return ( | ||
<ul> | ||
{posts.map((post) => ( | ||
<li key={post.id}>{post.title}</li> | ||
))} | ||
</ul> | ||
) | ||
} | ||
``` | ||
|
||
Alternatively, you can add the `'use cache'` directive at the top of the file to mark all components in the file as cacheable. Each component will be cached separately, with its own cache key. | ||
|
||
```tsx filename="app/blog/page.tsx" highlight={1} switcher | ||
'use cache' | ||
|
||
import { getPosts } from '@/app/lib/data' | ||
|
||
export default async function Page() { | ||
const posts = await getPosts() | ||
|
||
return <Posts posts={posts} /> | ||
} | ||
|
||
async function Posts({ posts }) { | ||
return ( | ||
<ul> | ||
{posts.map((post) => ( | ||
<li key={post.id}>{post.title}</li> | ||
))} | ||
</ul> | ||
) | ||
} | ||
``` | ||
|
||
```js filename="app/blog/page.js" highlight={1} switcher | ||
'use cache' | ||
|
||
import { getPosts } from '@/app/lib/data' | ||
|
||
export default async function Page() { | ||
const posts = await getPosts() | ||
|
||
return <Posts posts={posts} /> | ||
} | ||
|
||
async function Posts({ posts }) { | ||
return ( | ||
<ul> | ||
{posts.map((post) => ( | ||
<li key={post.id}>{post.title}</li> | ||
))} | ||
</ul> | ||
) | ||
} | ||
``` | ||
|
||
### Functions | ||
|
||
You can cache the return value of an **asynchronous** function, including data requests, by adding the `'use cache'` directive inside the function body: | ||
|
||
```ts filename="app/lib/data.ts" switcher | ||
export async function getPosts(slug: string) { | ||
'use cache' | ||
const data = await fetch(`https://api.vercel.app/posts/${slug}`) | ||
return data.json() | ||
} | ||
``` | ||
|
||
```js filename="app/lib/data.js" switcher | ||
export async function getPosts(slug) { | ||
'use cache' | ||
const data = await fetch(`https://api.vercel.app/posts/${slug}`) | ||
return data.json() | ||
} | ||
``` | ||
|
||
You can then call the function throughout your application code and the same cache entry will be reused, as long as the arguments to the function (e.g. `slug`) are the same. If the arguments are different, a new cache key will be created for the new arguments. | ||
|
||
Alternatively, you can also cache all functions within a file by adding the `'use cache'` directive at the top of the file. Each function will be cached separately. | ||
|
||
```ts filename="app/lib/data.ts" switcher | ||
'use cache' | ||
|
||
export async function getPosts() { | ||
const data = await fetch(`https://api.vercel.app/posts`) | ||
return data.json() | ||
} | ||
|
||
export async function getPostBySlug(slug: string) { | ||
const data = await fetch(`https://api.vercel.app/posts/${slug}`) | ||
return data.json() | ||
} | ||
``` | ||
|
||
```js filename="app/lib/data.js" switcher | ||
'use cache' | ||
|
||
export async function getPosts() { | ||
const data = await fetch(`https://api.vercel.app/posts/`) | ||
return data.json() | ||
} | ||
|
||
export async function getPostBySlug(slug: string) { | ||
const data = await fetch(`https://api.vercel.app/posts/${slug}`) | ||
return data.json() | ||
} | ||
``` | ||
|
||
## Revalidating | ||
|
||
Revalidation allows you to update cached content without having to rebuild your entire application. It's useful for content that _sometimes_ changes, but would benefit from being cached to improve your application's performance. | ||
|
||
In Next.js, there are two types of revalidation: | ||
|
||
- [Time-based](#time-based-revalidation): Based on a time interval (e.g. every hour). | ||
- [On-demand](#on-demand-revalidation): Triggered by a specific event (e.g. a CMS webhook). | ||
|
||
## Time-based Revalidation | ||
|
||
You can use the [`cacheLife` function](/docs/app/api-reference/functions/cacheLife) to define a time interval for how long a cached value should remain stale before it's revalidated. | ||
|
||
`cacheLife` comes with [default cache profiles](/docs/app/api-reference/functions/cacheLife#default-cache-profiles) such as `'hour'`, `'day'`, and `'week'`. These profiles can be [customized](/docs/app/api-reference/functions/cacheLife#custom-cache-profiles), if needed. | ||
|
||
To use `cacheLife`, import it from `next/cache` and nest it within the **scope** of the `'use cache'` directive and the function. For example, to revalidate the blog page after one hour: | ||
|
||
```tsx filename="app/blog/page.tsx" highlight={3,6} switcher | ||
'use cache' | ||
|
||
import { cacheLife } from 'next/cache' | ||
|
||
export default async function Page() { | ||
cacheLife('hour') | ||
return <Posts posts={posts} /> | ||
} | ||
``` | ||
|
||
```jsx filename="app/blog/page.js" highlight={3,6} switcher | ||
'use cache' | ||
|
||
import { cacheLife } from 'next/cache' | ||
|
||
export default async function Page() { | ||
cacheLife('hour') | ||
return <Posts posts={posts} /> | ||
} | ||
``` | ||
|
||
delbaoliveira marked this conversation as resolved.
Show resolved
Hide resolved
|
||
## On-demand Revalidation | ||
|
||
You can use the [`cacheTag` function](/docs/app/api-reference/functions/cacheTag) to define a tag for a cache entry. The entry can then be revalidated using the [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) function in another part of your application, such as a [Server Action](https://react.dev/reference/rsc/server-functions) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). | ||
|
||
For example, to update the list of blog posts once a new post is created, add the `cacheTag` function to the component that renders the list of posts: | ||
|
||
```tsx filename="app/ui/posts.tsx" switcher | ||
'use cache' | ||
|
||
import { cacheTag } from 'next/cache' | ||
|
||
export function Posts({ posts }) { | ||
cacheTag('blog-posts') | ||
return ( | ||
<ul> | ||
{posts.map((post) => ( | ||
<li key={post.id}>{post.title}</li> | ||
))} | ||
</ul> | ||
) | ||
} | ||
``` | ||
|
||
```jsx filename="app/ui/posts.js" switcher | ||
'use cache' | ||
|
||
import { cacheTag } from 'next/cache' | ||
|
||
export function Posts({ posts }) { | ||
cacheTag('blog-posts') | ||
return ( | ||
<ul> | ||
{posts.map((post) => ( | ||
<li key={post.id}>{post.title}</li> | ||
))} | ||
</ul> | ||
) | ||
} | ||
``` | ||
|
||
Then, in the Server Action to create a new post, call `revalidateTag` using the same tag to purge the cache entry: | ||
|
||
```tsx filename="app/actions.ts" switcher | ||
'use server' | ||
|
||
import { revalidateTag } from 'next/cache' | ||
|
||
export async function createPost() { | ||
// Mutate data | ||
// ... | ||
|
||
// Purge cache | ||
revalidateTag('blog-posts') | ||
} | ||
``` | ||
|
||
```jsx filename="app/actions.js" switcher | ||
'use server' | ||
|
||
import { revalidateTag } from 'next/cache' | ||
|
||
export async function createPost() { | ||
// Mutate data | ||
// ... | ||
|
||
// Purge cache | ||
revalidateTag('blog-posts') | ||
} | ||
``` | ||
|
||
Alternatively, you can call the [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) function to purge the whole `/blog` route (in which case you don't need `cacheTag`): | ||
|
||
```tsx filename="app/actions.ts" switcher | ||
'use server' | ||
|
||
import { revalidatePath } from 'next/cache' | ||
|
||
export async function createPost() { | ||
// Mutate data | ||
// ... | ||
|
||
// Purge cache | ||
revalidatePath('/blog') | ||
} | ||
``` | ||
|
||
```tsx filename="app/actions.ts" switcher | ||
'use server' | ||
|
||
import { revalidatePath } from 'next/cache' | ||
|
||
export async function createPost() { | ||
// Mutate data | ||
// ... | ||
|
||
// Purge cache | ||
expirePath('/blog') | ||
} | ||
``` |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe again: There will be two separate entries into the cache. One for
getPosts
and another forgetPostsBySlug
, where the cache will be invalidated if theslug
argument changes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the invalidated part, wouldn't it be two separate entries? A cache key is the function's unique id + the arguments?
https://vercel.slack.com/archives/C06QRHBQ742/p1730766893478919?thread_ts=1730765761.544979&cid=C06QRHBQ742