Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 109 additions & 11 deletions docs/01-app/01-getting-started/06-fetching-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -545,23 +545,27 @@ export default async function Page({ params }) {

### Reusing data with `React.cache`

Wrap a data-fetching function in [`React.cache`](https://react.dev/reference/react/cache) so multiple components in the same request share one result instead of refetching:
For data access that does not use `fetch`, such as an ORM or database query, wrap the function in [`React.cache`](https://react.dev/reference/react/cache). Multiple components can then call the function within the same request while sharing one result:

```ts filename="app/lib/user.ts" switcher
import { cache } from 'react'
import { db, eq, users } from '@/lib/db'

export const getUser = cache(async () => {
const res = await fetch('https://api.example.com/user')
return res.json()
export const getUser = cache(async (id: string) => {
return db.query.users.findFirst({
where: eq(users.id, id),
})
})
```

```js filename="app/lib/user.js" switcher
import { cache } from 'react'
import { db, eq, users } from '@/lib/db'

export const getUser = cache(async () => {
const res = await fetch('https://api.example.com/user')
return res.json()
export const getUser = cache(async (id) => {
return db.query.users.findFirst({
where: eq(users.id, id),
})
})
```

Expand All @@ -571,7 +575,12 @@ Server Components can call `getUser()` directly:
import { getUser } from '../lib/user'

export default async function DashboardPage() {
const user = await getUser() // Cached - same request, no duplicate fetch
const user = await getUser('1')

if (!user) {
return null
}

return <h1>Dashboard for {user.name}</h1>
}
```
Expand All @@ -580,11 +589,100 @@ export default async function DashboardPage() {
import { getUser } from '../lib/user'

export default async function DashboardPage() {
const user = await getUser() // Cached - same request, no duplicate fetch
const user = await getUser('1')

if (!user) {
return null
}

return <h1>Dashboard for {user.name}</h1>
}
```

Since `getUser` is wrapped with `React.cache`, multiple calls within the same request return the same memoized result, whether called directly in Server Components or resolved via context in Client Components.
Since `getUser` is wrapped with `React.cache`, calls with the same `id` within one request return the same memoized result.

> **Good to know:** [`React.cache`](https://react.dev/reference/react/cache#caveats) is scoped to the current request only. Each request gets its own memoization scope with no sharing between requests.

### Preloading data

When a component renders after other blocking work, its data request starts late even if the request inputs are already available. Preloading starts the request earlier so it can run in parallel with that work and avoid a request waterfall.

To preload data, call the data-fetching function without `await` before blocking work, then call the same function in the component that consumes the result.

The data-fetching function must deduplicate matching calls so the component can reuse the request started during preloading. Use one of the following approaches:

- For `fetch`, [identical requests are memoized automatically](/docs/app/api-reference/functions/fetch#memoization).
- For an ORM or database, wrap the data-fetching function in [`React.cache`](#reusing-data-with-reactcache).
- With Cache Components, add [`'use cache'`](/docs/app/api-reference/directives/use-cache) to the data-fetching function. If the function reads request APIs such as `cookies()` or `headers()`, use [`'use cache: private'`](/docs/app/api-reference/directives/use-cache-private).

In production, matching calls to a private Cache Function can reuse the same result within one request. This lets a component reuse a request started during preloading without storing the result in a server cache across requests.

Keep the preload function next to the component that consumes the data. This makes the dependency easier to find if you move or remove the component:

```tsx filename="app/item/[id]/item.tsx" switcher
async function getItem(id: string) {
const res = await fetch(`https://api.example.com/items/${id}`)
return res.json()
}

export const preload = (id: string) => {
void getItem(id)
}

export default async function Item({ id }: { id: string }) {
const item = await getItem(id)
return <div>{item.name}</div>
}
```

```jsx filename="app/item/[id]/item.js" switcher
async function getItem(id) {
const res = await fetch(`https://api.example.com/items/${id}`)
return res.json()
}

export const preload = (id) => {
void getItem(id)
}

export default async function Item({ id }) {
const item = await getItem(id)
return <div>{item.name}</div>
}
```

Call `preload()` before another blocking request to start loading the item earlier:

```tsx filename="app/item/[id]/page.tsx" switcher
import Item, { preload } from './item'
import { checkIsAvailable } from '@/app/lib/data'

export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params

preload(id)
const isAvailable = await checkIsAvailable(id)

return isAvailable ? <Item id={id} /> : null
}
```

```jsx filename="app/item/[id]/page.js" switcher
import Item, { preload } from './item'
import { checkIsAvailable } from '@/app/lib/data'

export default async function Page({ params }) {
const { id } = await params

preload(id)
const isAvailable = await checkIsAvailable(id)

return isAvailable ? <Item id={id} /> : null
}
```

> **Good to know**: `React.cache` is scoped to the current request only. Each request gets its own memoization scope with no sharing between requests.
The item request continues while `checkIsAvailable()` runs. If the page renders `<Item>`, the identical `fetch` call reuses the request that `preload()` started.
8 changes: 8 additions & 0 deletions docs/01-app/02-guides/migrating-to-cache-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,14 @@ export async function getUser(id) {

Like the `fetch` Data Cache, `unstable_cache` persists cached values across deployments and serverless instances, while `use cache` does not. See [`fetch` cache options](#fetch-cache-options) above for the storage details.

## `React.cache`

**Usually no change.** `React.cache` continues to deduplicate matching calls within a React render.

However, each Cache Function has an isolated React cache scope. Calls from separate Cache Functions do not share a `React.cache` result, such as when one Cache Function preloads data and another consumes it.

When a helper reads request data and matching calls need to share work across Cache Function scopes, replace the React wrapper with [`'use cache: private'`](/docs/app/api-reference/directives/use-cache-private). If the function only needs request-scoped deduplication, use `cacheLife({ stale: Infinity })` so it does not lower the route's stale time. Private Cache Function results are not stored in a server cache across production requests.

## On-demand revalidation (`revalidateTag`, `revalidatePath`, `updateTag`)

On-demand invalidation still works by tagging cached data and expiring it after an event. Tag data with [`cacheTag`](/docs/app/api-reference/functions/cacheTag) inside a `use cache` function instead of the `fetch` `next.tags` option, then choose the invalidation API by the behavior you want:
Expand Down
16 changes: 12 additions & 4 deletions docs/01-app/03-api-reference/01-directives/use-cache-private.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ related:
- app/api-reference/functions/cacheTag
---

The `'use cache: private'` directive allows functions to access runtime request APIs like `cookies()`, `headers()`, and `searchParams` within a cached scope. However, results are **never stored on the server**, they're cached only in the browser's memory and do not persist across page reloads.
The `'use cache: private'` directive allows functions to access runtime request APIs like `cookies()`, `headers()`, and `searchParams` within a cached scope. In production, matching calls within one request can reuse the same result, but Next.js does not store it in a server cache across requests.

The client router can keep the rendered output in browser memory for the [`stale` time](/docs/app/api-reference/functions/cacheLife#client-cache-behavior) configured with `cacheLife`. This client-side cache does not persist across page reloads.

Reach for `'use cache: private'` when:

- You want to cache a function that already accesses runtime data, and refactoring to [move the runtime access outside and pass values as arguments](/docs/app/getting-started/caching#working-with-runtime-apis) is not practical.
- Compliance requirements prevent storing certain data on the server, even temporarily
- You need request-specific data to be excluded from server caches that persist across production requests.

Because this directive accesses runtime data, the function executes on every server render and is excluded from running during [static shell](/docs/app/getting-started/caching#prerendering) generation.
Private Cache Functions run at request time and are excluded from [static shell](/docs/app/getting-started/caching#prerendering) generation. To start a private Cache Function before a component needs its result, see [Preloading data](/docs/app/getting-started/fetching-data#preloading-data).

It is **not** possible to configure custom cache handlers for `'use cache: private'`.

Expand Down Expand Up @@ -149,7 +151,13 @@ async function getRecommendations(productId) {
}
```
> **Good to know**: The `stale` time must be at least 30 seconds for per-link prefetching to work, and at least 5 minutes for the content to be included in the route's [App Shell](/docs/app/glossary#app-shell). See [`cacheLife` prerendering behavior](/docs/app/api-reference/functions/cacheLife#prerendering-behavior) for details.
> **Good to know:** The `stale` time must be at least 30 seconds for per-link prefetching to work, and at least 5 minutes for the content to be included in the route's [App Shell](/docs/app/glossary#app-shell). See [`cacheLife` prerendering behavior](/docs/app/api-reference/functions/cacheLife#prerendering-behavior) for details.
### Configuring the client stale time
Private Cache Functions contribute their `stale` time to the route's [Client Cache](/docs/app/glossary#client-cache). If a function only needs request-scoped deduplication, use `cacheLife({ stale: Infinity })` to keep it from lowering the route's stale time.
Next.js uses the shortest stale time from the route's cache entries, so another cache or route setting can still set a finite value. Setting `stale` to `Infinity` does not store the private result on the server across production requests. Use a finite value when the client router should revalidate personalized output after a known interval.
## Request APIs allowed in private caches
Expand Down
30 changes: 0 additions & 30 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,35 +36,6 @@ ignoredBuiltDependencies:
blockExoticSubdeps: true
minimumReleaseAge: 2880 # 48 hrs
minimumReleaseAgeExclude:
# sharp 0.35.4 and its @img/* binaries fix a security vulnerability, so they
# are exempt from the 48h release age gate.
# These exemptions should be removed on 2026-08-28
- '@img/sharp-darwin-arm64@0.35.4'
- '@img/sharp-darwin-x64@0.35.4'
- '@img/sharp-freebsd-wasm32@0.35.4'
- '@img/sharp-libvips-darwin-arm64@1.3.3'
- '@img/sharp-libvips-darwin-x64@1.3.3'
- '@img/sharp-libvips-linux-arm64@1.3.3'
- '@img/sharp-libvips-linux-arm@1.3.3'
- '@img/sharp-libvips-linux-ppc64@1.3.3'
- '@img/sharp-libvips-linux-riscv64@1.3.3'
- '@img/sharp-libvips-linux-s390x@1.3.3'
- '@img/sharp-libvips-linux-x64@1.3.3'
- '@img/sharp-libvips-linuxmusl-arm64@1.3.3'
- '@img/sharp-libvips-linuxmusl-x64@1.3.3'
- '@img/sharp-linux-arm64@0.35.4'
- '@img/sharp-linux-arm@0.35.4'
- '@img/sharp-linux-ppc64@0.35.4'
- '@img/sharp-linux-riscv64@0.35.4'
- '@img/sharp-linux-s390x@0.35.4'
- '@img/sharp-linux-x64@0.35.4'
- '@img/sharp-linuxmusl-arm64@0.35.4'
- '@img/sharp-linuxmusl-x64@0.35.4'
- '@img/sharp-wasm32@0.35.4'
- '@img/sharp-webcontainers-wasm32@0.35.4'
- '@img/sharp-win32-arm64@0.35.4'
- '@img/sharp-win32-ia32@0.35.4'
- '@img/sharp-win32-x64@0.35.4'
- '@mswjs/interceptors@0.42.0'
- '@next/*'
- '@turbo/*'
Expand All @@ -80,5 +51,4 @@ minimumReleaseAgeExclude:
- react-is
- react-server-dom-*
- scheduler
- sharp@0.35.4
- turbo
Loading