new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Next.js App Router · all subjects

data-fetching-caching/caching

83 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

updateTag vs revalidateTag comparison

updateTag is for Server Actions only with immediate cache expiration and read-your-own-writes use case. revalidateTag works in Server Actions and Route Handlers with stale-while-revalidate behavior and background refresh use case.

revalidatePath usage and recommendation

revalidatePath invalidates all cached data for a specific route path. Use it when you want to revalidate a route without knowing which tags are associated with it. Prefer tag-based revalidation (revalidateTag/updateTag) over path-based when possible — it is more precise and avoids over-invalidating.

revalidatePath usage example

import { revalidatePath } from 'next/cache' export async function updateUser(id: string) { // Mutate data revalidatePath('/profile') } Use revalidatePath to invalidate all cached data for a specific route path.

Caching strategy for static content with CMS

When content doesn't need time-based revalidation (e.g., data from a CMS), use cacheTag and a long cacheLife like 'max' to keep it in the static shell. Configure the content source to trigger a webhook or other notification that calls revalidateTag when the content changes. This reduces unnecessary time-based revalidation for content that hasn't changed.

What to cache with use cache

Cache data that doesn't depend on runtime data and that you are OK serving from cache for a period of time. Use 'use cache' with cacheLife to describe that behavior.

In-memory cache persistence in serverless environments

In serverless environments, in-memory cache entries may not persist across revalidations. See runtime caching considerations for details.

cacheLife profiles and durations

cacheLife accepts profile names or custom configuration objects. Profiles are: default (stale 5m, revalidate 15m, expire never), seconds (stale 30s, revalidate 1s, expire 60s), minutes (stale 5m, revalidate 1m, expire 1h), hours (stale 5m, revalidate 1h, expire 1d), days (stale 5m, revalidate 1d, expire 1w), weeks (stale 5m, revalidate 1w, expire 30d), max (stale 5m, revalidate 30d, expire 1y).

cacheLife custom configuration object

For fine-grained control, cacheLife accepts an object with stale (duration in seconds until considered stale), revalidate (duration in seconds until revalidated), and expire (duration in seconds until expired) properties.

Short-lived caches excluded from prerenders

A cache is considered 'short-lived' when it uses the seconds profile, revalidate: 0, or expire under 5 minutes. Short-lived caches are automatically excluded from prerenders and become dynamic holes instead.

cacheLife usage example

import { cacheLife } from 'next/cache' export async function getProducts() { 'use cache' cacheLife('hours') return db.query('SELECT * FROM products') } Use cacheLife inside a 'use cache' scope to set the cache lifetime.

cacheTag usage example

import { cacheTag } from 'next/cache' export async function getProducts() { 'use cache' cacheTag('products') return db.query('SELECT * FROM products') } Use cacheTag inside a 'use cache' scope to tag cached data so it can be invalidated on-demand.

revalidateTag behavior and usage

revalidateTag invalidates cache entries by tag using stale-while-revalidate semantics — stale content is served immediately while fresh content loads in the background. This is ideal for content where a slight delay in updates is acceptable, like blog posts or product catalogs. Call revalidateTag in a Server Action or Route Handler.

revalidateTag with max duration example

import { revalidateTag } from 'next/cache' export async function updateUser(id: string) { // Mutate data revalidateTag('user', 'max') } The second argument sets how long stale content can be served while fresh content generates in the background. Using 'max' gives the longest stale window.

updateTag behavior and limitations

updateTag immediately expires cached data for read-your-own-writes scenarios — the user sees their change right away instead of stale content. Unlike revalidateTag, updateTag can only be used in Server Actions.

updateTag usage example

import { updateTag } from 'next/cache' import { redirect } from 'next/navigation' export async function createPost(formData: FormData) { const post = await db.post.create({ data: { title: formData.get('title'), content: formData.get('content'), }, }) updateTag('posts') redirect(`/posts/${post.id}`) } Use updateTag in Server Actions to immediately expire cache for user-visible changes.

Route Handlers caching behavior

Route Handlers are not cached by default. Only GET methods can be opted into caching using route config options such as export const dynamic = 'force-static'. Other supported HTTP methods are not cached, even if placed alongside a cached GET method in the same file.

GET Route Handler caching with dynamic config

export const dynamic = 'force-static' export async function GET() { const res = await fetch('https://data.mongodb-api.com/...', { headers: { 'Content-Type': 'application/json', 'API-Key': process.env.DATA_API_KEY, }, }) const data = await res.json() return Response.json({ data }) }

Prerendering stops when accessing non-deterministic or runtime data

When Cache Components is enabled, GET Route Handlers are prerendered at build time if they don't access uncached or runtime data. Prerendering stops if the GET handler accesses network requests, database queries, async file system operations, request object properties (like req.url, request.headers, request.cookies, request.body), runtime APIs like cookies(), headers(), connection(), or non-deterministic operations like Math.random().

use cache in Route Handlers requires extraction to helper function

use cache cannot be used directly inside a Route Handler body; it must be extracted to a helper function. Cached responses revalidate according to cacheLife when a new request arrives.

Cached Route Handler example with use cache

import { cacheLife } from 'next/cache' export async function GET() { const products = await getProducts() return Response.json(products) } async function getProducts() { 'use cache' cacheLife('hours') return await db.query('SELECT * FROM products') }

Static prerendering example for Route Handler

export async function GET() { return Response.json({ projectName: 'Next.js', }) } This Route Handler doesn't access uncached or runtime data, so it will be prerendered at build time.

Dynamic Route Handler example with runtime API

import { headers } from 'next/headers' export async function GET() { const headersList = await headers() const userAgent = headersList.get('user-agent') return Response.json({ userAgent }) } This Route Handler accesses request-specific data via the headers() runtime API, so prerendering terminates and it runs at request time.

Metadata file types are cached by default

Special Route Handlers like sitemap.ts, opengraph-image.tsx, icon.tsx, and other metadata files are cached by default.

manifest.js is cached by default

manifest.js is a special Route Handler that is cached by default unless it uses a Request-time API or dynamic config option.

robots.js caching behavior

robots.js is a special Route Handler that is cached by default unless it uses a Request-time API or dynamic config option.

use cache directive for data-level caching

To cache an asynchronous function that fetches data, add the `use cache` directive at the top of the function body. Data-level caching is useful when the same data is used across multiple components, or when you want to cache the data independently from the UI. Example: `export async function getUsers() { 'use cache'; cacheLife('hours'); return db.query('SELECT * FROM users'); }`

use cache directive for UI-level caching

To cache an entire component, page, or layout, add the `use cache` directive at the top of the component or page body. If you add `use cache` at the top of a file, all exported functions in the file will be cached.

Cache keys include arguments and closed-over values

Arguments and any closed-over values from parent scopes automatically become part of the cache key, which means different inputs will produce separate cache entries.

Pair use cache with cacheLife

Every cache directive should be paired with a `cacheLife()` call. Without one, the implicit `default` profile applies.

use cache: private for runtime-dependent data

`use cache: private` gives a lifetime to a function that reads cookies, headers, or searchParams directly, so it can be included in a prefetch. This is an alternative to wrapping in Suspense.

Runtime caching for cached components gated behind request data

When a cached component is gated behind request data, it isn't added to the prerendered static shell. At runtime it's cached in-memory by default, which doesn't persist across serverless requests. Use `use cache: remote` for durable, shared caching.

Handling random values and timestamps in caching

Operations like `Math.random()`, `Date.now()`, or `crypto.randomUUID()` produce different values each time they execute. Cache Components requires explicit handling. `performance.now()` is meant for telemetry and Next.js doesn't treat it as a value to guard.

Cache random values to share across users

Alternatively, you can cache the result so all users see the same value until revalidation. Example: `export default async function Page() { 'use cache'; const buildId = crypto.randomUUID(); return <p>Build ID: {buildId}</p>; }`

Where cached content is stored

A cached function's output is serialized into an RSC payload at build time or runtime. Next.js can: render it to HTML and store on disk (static shell at build time, concrete page after ISR), keep it in a per-instance in-memory store (ephemeral on serverless by default), or use a durable cache handler (with use cache: remote), or send it to the browser (for client navigation or prefetch).

Prerendered HTML storage

The RSC payload is rendered to HTML and stored on disk when self-hosting, or in the platform's durable storage behind a CDN. That HTML is the static shell at build time and the concrete page after an ISR upgrade, with `revalidate` and `expire` controlling when it's rebuilt.

use cache: remote for shared durable caching

`use cache: remote` moves cached results to a durable cache handler shared across instances, a network roundtrip that pays off only at high hit rate. This is used instead of the default per-instance in-memory store.

Browser cache storage and stale window

The RSC payload is included in the RSC sent for client navigation or prefetch, where the browser keeps it fresh for its `stale` window. `use cache: private` results live only in the browser cache.

App Shell session-specific caching

An App Shell that reads cookies() or headers() is session-specific, cached per session on the client rather than in the shared server cache.

Cache key includes build id

All cache stores are scoped to a single deployment. A new deploy starts fresh, new prerenders are built, and use cache entries don't carry over, even durable remote ones, because the cache key includes the build id.

Cache Components with 'use cache' directive

Cache Components is a feature that enables component and function-level caching using the 'use cache' directive. It allows you to mix static, cached, and dynamic content within a single route by prerendering a static HTML shell that's served immediately, while dynamic content streams in when ready. Cache duration is configured with cacheLife(), cached data is tagged with cacheTag(), and on-demand invalidation is done with updateTag().

Client Cache storage and behavior

The Client Cache is an in-memory cache in the browser that stores RSC Payload for visited and prefetched routes. During client-side navigation, Next.js serves cached layouts and loading states instantly without a server request. Pages are not cached by default but are reused during browser back/forward navigation. The client cache is cleared on page refresh.

Client Cache invalidation methods

The client cache can be invalidated programmatically with revalidateTag, revalidatePath, updateTag, router.refresh, cookies.set, or cookies.delete. Client cache duration can be configured globally with staleTimes or per-route via the stale property in cacheLife (recommended).

Revalidation strategies time-based and on-demand

Revalidation is the process of updating cached data. It can be time-based using cacheLife() to set cache duration or on-demand using cacheTag() to tag data, then updateTag() to invalidate.

'use cache' directive syntax and scope

The 'use cache' directive marks a component or function as cacheable. It can be placed at the top of a file to indicate that all exports in the file are cacheable, or inline at the top of a function or component to mark that specific scope as cacheable.

force-cache option caches individual fetch requests

By default, fetch requests are not cached. To cache individual fetch requests, set the cache option to 'force-cache' in the fetch call.

unstable_cache wraps non-fetch async functions for caching

unstable_cache allows you to cache the result of database queries and other async functions that don't use fetch. It accepts three arguments: the async function to cache, an array of cache key prefixes, and a configuration object with optional tags and revalidate properties.

unstable_cache configuration options

The third argument to unstable_cache accepts: tags (array of strings for on-demand revalidation with revalidateTag), and revalidate (number of seconds before the cache is revalidated).

dynamic route segment config values

The dynamic export can be set to one of four values: 'auto' (default, caches as much as possible), 'force-dynamic' (renders for each user at request time), 'error' (forces prerendering and errors if any Request-time APIs are used), or 'force-static' (forces prerendering and returns empty values for cookies, headers, and useSearchParams).

force-dynamic is equivalent to setting cache: no-store on all fetch requests

Setting dynamic to 'force-dynamic' is equivalent to setting the option of every fetch() request to { cache: 'no-store', next: { revalidate: 0 } } and setting fetchCache to 'force-no-store'.

error dynamic mode forces prerendering with cached data

Setting dynamic to 'error' forces prerendering and causes an error if any components use Request-time APIs or uncached data. It is equivalent to setting every fetch() option to { cache: 'force-cache' } and setting fetchCache to 'only-cache'.

force-static mode with revalidation capability

Setting dynamic to 'force-static' forces cookies(), headers(), and useSearchParams() to return empty values while still allowing revalidation via revalidate(), revalidatePath(), or revalidateTag().

fetchCache advanced caching option

fetchCache allows overriding the default cache option for all fetch requests in a layout or page. Valid values are: 'auto' (default), 'default-cache', 'only-cache', 'force-cache', 'default-no-store', 'only-no-store', and 'force-no-store'.

fetchCache default behavior

By default, Next.js caches any fetch() requests that are reachable before any Request-time APIs are used and does not cache fetch requests discovered after Request-time APIs are used.

fetchCache 'default-cache' option

'default-cache' allows any cache option to be passed to fetch but if no option is provided, sets cache to 'force-cache', making even fetch requests after Request-time APIs considered static.

fetchCache 'force-cache' option

'force-cache' ensures all fetch requests opt into caching by setting the cache option of all fetch requests to 'force-cache'.

fetchCache 'default-no-store' option

'default-no-store' allows any cache option to be passed to fetch but if no option is provided, sets cache to 'no-store', making even fetch requests before Request-time APIs considered dynamic.

fetchCache 'only-no-store' option

'only-no-store' ensures 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'.

fetchCache 'force-no-store' option

'force-no-store' ensures all fetch requests opt out of caching by setting cache to 'no-store' for all fetch requests, forcing re-fetch every request even if they provide 'force-cache' option.

force options override only options in fetchCache

When both 'only-cache' and 'force-cache' are provided, 'force-cache' wins. When both 'only-no-store' and 'force-no-store' are provided, 'force-no-store' wins. Force options change behavior across the route and prevent errors from only options.

fetchCache incompatible combinations not allowed

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.

Give your agent this brain