mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
docs: improve clarity in cache components and server/client docs (#88946)
- Applying learnings from a recipe - React.cache usage under a use cache scope
This commit is contained in:
@@ -412,6 +412,191 @@ Your Server Component will now be able to directly render your provider, and all
|
||||
|
||||
> **Good to know**: You should render providers as deep as possible in the tree – notice how `ThemeProvider` only wraps `{children}` instead of the entire `<html>` document. This makes it easier for Next.js to optimize the static parts of your Server Components.
|
||||
|
||||
### Sharing data with context and React.cache
|
||||
|
||||
You can share fetched data across both Server and Client Components by combining [`React.cache`](https://react.dev/reference/react/cache) with context providers.
|
||||
|
||||
Create a cached function that fetches data:
|
||||
|
||||
```ts filename="app/lib/user.ts" switcher
|
||||
import { cache } from 'react'
|
||||
|
||||
export const getUser = cache(async () => {
|
||||
const res = await fetch('https://api.example.com/user')
|
||||
return res.json()
|
||||
})
|
||||
```
|
||||
|
||||
```js filename="app/lib/user.js" switcher
|
||||
import { cache } from 'react'
|
||||
|
||||
export const getUser = cache(async () => {
|
||||
const res = await fetch('https://api.example.com/user')
|
||||
return res.json()
|
||||
})
|
||||
```
|
||||
|
||||
Create a context provider that stores the promise:
|
||||
|
||||
```tsx filename="app/user-provider.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { createContext } from 'react'
|
||||
|
||||
type User = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const UserContext = createContext<Promise<User> | null>(null)
|
||||
|
||||
export default function UserProvider({
|
||||
children,
|
||||
userPromise,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
userPromise: Promise<User>
|
||||
}) {
|
||||
return <UserContext value={userPromise}>{children}</UserContext>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/user-provider.js" switcher
|
||||
'use client'
|
||||
|
||||
import { createContext } from 'react'
|
||||
|
||||
export const UserContext = createContext(null)
|
||||
|
||||
export default function UserProvider({ children, userPromise }) {
|
||||
return <UserContext value={userPromise}>{children}</UserContext>
|
||||
}
|
||||
```
|
||||
|
||||
In a layout, pass the promise to the provider without awaiting:
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import UserProvider from './user-provider'
|
||||
import { getUser } from './lib/user'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const userPromise = getUser() // Don't await
|
||||
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<UserProvider userPromise={userPromise}>{children}</UserProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import UserProvider from './user-provider'
|
||||
import { getUser } from './lib/user'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
const userPromise = getUser() // Don't await
|
||||
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<UserProvider userPromise={userPromise}>{children}</UserProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Client Components use [`use()`](https://react.dev/reference/react/use) to resolve the promise from context, wrapped in `<Suspense>` for fallback UI:
|
||||
|
||||
```tsx filename="app/ui/profile.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { use, useContext } from 'react'
|
||||
import { UserContext } from '../user-provider'
|
||||
|
||||
export function Profile() {
|
||||
const userPromise = useContext(UserContext)
|
||||
if (!userPromise) {
|
||||
throw new Error('useContext must be used within a UserProvider')
|
||||
}
|
||||
const user = use(userPromise)
|
||||
return <p>Welcome, {user.name}</p>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/profile.js" switcher
|
||||
'use client'
|
||||
|
||||
import { use, useContext } from 'react'
|
||||
import { UserContext } from '../user-provider'
|
||||
|
||||
export function Profile() {
|
||||
const userPromise = useContext(UserContext)
|
||||
if (!userPromise) {
|
||||
throw new Error('useContext must be used within a UserProvider')
|
||||
}
|
||||
const user = use(userPromise)
|
||||
return <p>Welcome, {user.name}</p>
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { Profile } from './ui/profile'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading profile...</div>}>
|
||||
<Profile />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { Profile } from './ui/profile'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading profile...</div>}>
|
||||
<Profile />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Server Components can also call `getUser()` directly:
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import { getUser } from '../lib/user'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const user = await getUser() // Cached - same request, no duplicate fetch
|
||||
return <h1>Dashboard for {user.name}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import { getUser } from '../lib/user'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const user = await getUser() // Cached - same request, no duplicate fetch
|
||||
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.
|
||||
|
||||
> **Good to know**: `React.cache` is scoped to the current request only. Each request gets its own memoization scope with no sharing between requests.
|
||||
|
||||
### Third-party components
|
||||
|
||||
When using a third-party component that relies on client-only features, you can wrap it in a Client Component to ensure it works as expected.
|
||||
|
||||
@@ -194,6 +194,10 @@ Use [`connection()`](/docs/app/api-reference/functions/connection) if you need t
|
||||
|
||||
> **Good to know**: Runtime data cannot be cached with `use cache` because it requires request context. Components that access runtime APIs must always be wrapped in `<Suspense>`. However, you can extract values from runtime data and pass them as arguments to cached functions. See the [with runtime data](#with-runtime-data) section for an example.
|
||||
|
||||
One approach for reading runtime data like cookies without blocking the static shell is to pass a promise to a client context provider. See [Sharing data with context and React.cache](/docs/app/getting-started/server-and-client-components#sharing-data-with-context-and-reactcache) for an example.
|
||||
|
||||
> **Good to know:** `React.cache` operates in an isolated scope inside `use cache` boundaries. See [React.cache isolation](/docs/app/api-reference/directives/use-cache#reactcache-isolation) for more information.
|
||||
|
||||
### Non-deterministic operations
|
||||
|
||||
Operations like `Math.random()`, `Date.now()`, or `crypto.randomUUID()` produce different values each time they execute. To ensure these run at request time (generating unique values per request), Cache Components requires you to explicitly signal this intent by calling these operations after dynamic or runtime data access.
|
||||
|
||||
@@ -189,6 +189,10 @@ async function CachedForm({ action }: { action: () => Promise<void> }) {
|
||||
|
||||
## Constraints
|
||||
|
||||
Cached functions execute in an isolated environment. The following constraints ensure cache behavior remains predictable and secure.
|
||||
|
||||
### Runtime APIs
|
||||
|
||||
Cached functions and components **cannot** directly access runtime APIs like `cookies()`, `headers()`, or `searchParams`. Instead, read these values outside the cached scope and pass them as arguments.
|
||||
|
||||
### Runtime caching considerations
|
||||
@@ -206,6 +210,34 @@ If the default in-memory cache isn't enough, consider **[`use cache: remote`](/d
|
||||
|
||||
Very rarely, for compliance requirements or when you can't refactor your code to pass runtime data as arguments to a `use cache` scope, you might need [`use cache: private`](/docs/app/api-reference/directives/use-cache-private).
|
||||
|
||||
### React.cache isolation
|
||||
|
||||
[`React.cache`](https://react.dev/reference/react/cache) operates in an isolated scope inside `use cache` boundaries. Values stored via `React.cache` outside a `use cache` function are not visible inside it.
|
||||
|
||||
This means you cannot use `React.cache` to pass data into a `use cache` scope:
|
||||
|
||||
```tsx
|
||||
import { cache } from 'react'
|
||||
|
||||
const store = cache(() => ({ current: null as string | null }))
|
||||
|
||||
function Parent() {
|
||||
const shared = store()
|
||||
shared.current = 'value from parent'
|
||||
return <Child />
|
||||
}
|
||||
|
||||
async function Child() {
|
||||
'use cache'
|
||||
const shared = store()
|
||||
// shared.current is null, not 'value from parent'
|
||||
// use cache has its own isolated React.cache scope
|
||||
return <div>{shared.current}</div>
|
||||
}
|
||||
```
|
||||
|
||||
This isolation ensures cached functions have predictable, self-contained behavior. To pass data into a `use cache` scope, use function arguments instead.
|
||||
|
||||
## `use cache` at runtime
|
||||
|
||||
On the **server**, cache entries are stored in-memory and respect the `revalidate` and `expire` times from your `cacheLife` configuration. You can customize the cache storage by configuring [`cacheHandlers`](/docs/app/api-reference/config/next-config-js/cacheHandlers) in your `next.config.js` file.
|
||||
|
||||
Reference in New Issue
Block a user