From 1076c4745318e439062d3464a76881d2b967f886 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 1 Sep 2026 03:45:41 -0400 Subject: [PATCH 1/2] chore: Remove expired sharp release age exclusions (#98120) - PR #97931 temporarily exempted sharp@0.35.4 and its @img/* binaries to ship same day. - Its now past the 48-hour minimum release age. - This PR removes those temporary exemptions. --- pnpm-workspace.yaml | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 90d951a4fd91..33e83211d500 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -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/*' @@ -80,5 +51,4 @@ minimumReleaseAgeExclude: - react-is - react-server-dom-* - scheduler - - sharp@0.35.4 - turbo From 1a9245c7d0941c235feb66003705e040f9a299ad Mon Sep 17 00:00:00 2001 From: Aurora Scharff <66901228+aurorascharff@users.noreply.github.com> Date: Tue, 1 Sep 2026 13:03:19 +0200 Subject: [PATCH 2/2] docs: document preloading with Cache Components (#97864) ## Summary - add the preload pattern to the current App Router data fetching guide - distinguish automatic fetch memoization, React cache for non-fetch data, and Cache Functions - clarify request-scoped private caching and when stale: Infinity should not constrain the route stale time The previous-model caching guide remains unchanged. ## Verification - Prettier on both edited docs - alex on both edited docs - git diff --check - Not run: full framework build because this is a documentation-only change --- .../01-getting-started/06-fetching-data.mdx | 120 ++++++++++++++++-- .../migrating-to-cache-components.mdx | 8 ++ .../01-directives/use-cache-private.mdx | 16 ++- 3 files changed, 129 insertions(+), 15 deletions(-) diff --git a/docs/01-app/01-getting-started/06-fetching-data.mdx b/docs/01-app/01-getting-started/06-fetching-data.mdx index cde45400c6f8..e4687c488b1b 100644 --- a/docs/01-app/01-getting-started/06-fetching-data.mdx +++ b/docs/01-app/01-getting-started/06-fetching-data.mdx @@ -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), + }) }) ``` @@ -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

Dashboard for {user.name}

} ``` @@ -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

Dashboard for {user.name}

} ``` -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
{item.name}
+} +``` + +```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
{item.name}
+} +``` + +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 ? : 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 ? : 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 ``, the identical `fetch` call reuses the request that `preload()` started. diff --git a/docs/01-app/02-guides/migrating-to-cache-components.mdx b/docs/01-app/02-guides/migrating-to-cache-components.mdx index b1a94b3babfe..a6bdd848f4a5 100644 --- a/docs/01-app/02-guides/migrating-to-cache-components.mdx +++ b/docs/01-app/02-guides/migrating-to-cache-components.mdx @@ -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: diff --git a/docs/01-app/03-api-reference/01-directives/use-cache-private.mdx b/docs/01-app/03-api-reference/01-directives/use-cache-private.mdx index 26a7ec16475a..807dd8a3cf04 100644 --- a/docs/01-app/03-api-reference/01-directives/use-cache-private.mdx +++ b/docs/01-app/03-api-reference/01-directives/use-cache-private.mdx @@ -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'`. @@ -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