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 · Guides · all subjects

caching

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

fetch caching with cache: 'force-cache' option

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

Example: revalidateTag usage

import { revalidateTag } from 'next/cache' export async function updateUser(id: string) { // Mutate data revalidateTag('user', 'max') }

Example: revalidatePath usage

import { revalidatePath } from 'next/cache' export async function updateUser(id: string) { // Mutate data revalidatePath('/profile') }

Example: React cache for request deduplication

import { cache } from 'react' import { db, posts, eq } from '@/lib/db' export const getPost = cache(async (id: string) => { const post = await db.query.posts.findFirst({ where: eq(posts.id, parseInt(id)), }) })

unstable_cache for non-fetch functions

The unstable_cache function allows caching results of database queries and other async functions that do not use fetch. It accepts a function as the first argument, a cache key prefix array as the second argument, and an options object as the third argument. The options object accepts 'tags' (an array of tags for on-demand revalidation with revalidateTag) and 'revalidate' (the number of seconds before the cache is revalidated).

Route segment config dynamic option values

The dynamic export in layout.tsx, page.tsx, or route.ts accepts these values: 'auto' (default, cache as much as possible), 'force-dynamic' (force dynamic rendering for each user at request time), 'error' (force prerendering and error if any components use Request-time APIs or uncached data), and 'force-static' (force prerendering and cache by making cookies, headers(), and useSearchParams() return empty values).

force-dynamic equivalent behaviors

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

force-static allows revalidation

When a layout or page is rendered with dynamic = 'force-static', it is possible to use revalidate(), revalidatePath(), or revalidateTag() to revalidate the page.

fetchCache option values

The fetchCache export accepts these values: 'auto' (default, cache fetch requests before Request-time APIs, don't cache after), 'default-cache' (allow any cache option, default to 'force-cache'), 'only-cache' (ensure all fetch requests use caching, error on 'no-store'), 'force-cache' (set all fetch requests to 'force-cache'), 'default-no-store' (allow any cache option, default to 'no-store'), 'only-no-store' (ensure all fetch requests disable caching, error on 'force-cache'), and 'force-no-store' (set all fetch requests to 'no-store', ignoring 'force-cache' options).

fetchCache cross-route segment behavior with force vs only options

When both 'only-cache' and 'force-cache' are provided across a route, 'force-cache' wins. When both 'only-no-store' and 'force-no-store' are provided, 'force-no-store' wins. The force option changes behavior across the route, so a single segment with 'force-*' prevents errors from 'only-*' options. 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.

fetchCache parent-child constraints

A parent cannot provide 'default-no-store' if a child provides 'auto' or '*-cache' since that could make the same fetch have different behavior. It is recommended to leave shared parent layouts as 'auto' and customize options where child segments diverge.

fetch time-based revalidation with next.revalidate

Use the next.revalidate option on fetch to revalidate data after a specified number of seconds: fetch('https://...', { next: { revalidate: 3600 } }).

Route segment config revalidate option values

The revalidate export accepts false (default, cache fetch requests that use 'force-cache' or are before Request-time APIs), 0 (always dynamically render, default uncached fetch to 'no-store'), or a number in seconds (set default revalidation frequency).

revalidate must be statically analyzable

The revalidate value must be statically analyzable. For example, revalidate = 600 is valid, but revalidate = 60 * 10 is not.

Development mode caching behavior

In Development, pages are always rendered on-demand and are never cached. This allows seeing changes immediately without waiting for a revalidation period.

Revalidation frequency across route segments

The lowest revalidate value across each layout and page of a single route determines the revalidation frequency of the entire route. This ensures child pages are revalidated as frequently as their parent layouts. Individual fetch requests can set a lower revalidate than the route's default to increase revalidation frequency of the entire route.

On-demand revalidation with revalidatePath

Use revalidatePath() in a Server Action or Route Handler to invalidate all cached data for a specific route path.

React cache function for request deduplication

When not using fetch (which is automatically memoized), wrap data access with React's cache() function to deduplicate requests within a single render pass. This is useful for ORM or direct database access.

Data preloading pattern with cache and server-only

Create a reusable preload utility by combining the server-only package with React's cache function. Define a cached data function and a preload function that calls the cached function with void to initiate data fetching early without blocking. Call preload() before blocking requests to start loading data immediately.

Example: unstable_cache with database query

import { unstable_cache } from 'next/cache' import { db } from '@/lib/db' export const getCachedUser = unstable_cache( async (id: string) => { return db .select() .from(users) .where(eq(users.id, id)) .then((res) => res[0]) }, ['user'], // cache key prefix { tags: ['user'], revalidate: 3600, } )

Example: fetch with force-cache

export default async function Page() { const data = await fetch('https://...', { cache: 'force-cache' }) }

Example: Preloading data with cache and server-only

import { cache } from 'react' import 'server-only' export const getItem = cache(async (id: string) => { // ... }) export const preload = (id: string) => { void getItem(id) }

Example: Using preload in a page component

import { getItem, preload, checkIsAvailable } from '@/lib/data' export default async function Page({ params, }: { params: Promise<{ id: string }> }) { const { id } = await params // Start loading item data preload(id) // Perform another asynchronous task const isAvailable = await checkIsAvailable() return isAvailable ? <Item id={id} /> : null } async function Item({ id }: { id: string }) { const result = await getItem(id) // ... }

Cache Components are the newer model

This guide covers the previous caching model using fetch options, unstable_cache, and route segment configs. Cache Components were introduced in version 16 under the cacheComponents flag and represent a newer approach to caching in Next.js.

Draft Mode overview and behavior

Draft Mode lets editors see how draft or in-progress content will render on a site without waiting for revalidation. While an editor is in Draft Mode, cached or pre-rendered content is bypassed and fetched from upstream sources directly. Other visitors continue to see the cached or pre-rendered version of the page. The data-fetching code does not need to change if the CMS serves draft and published content from the same URL.

What Draft Mode bypasses in Next.js

When Draft Mode is enabled for a request: fetch() calls skip the Next.js fetch cache and hit the network directly. Components and functions inside 'use cache' re-execute on every request and their results are not saved to the cache. unstable_cache reads and writes are bypassed. The page is excluded from the ISR response cache and is served with Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate. This effect applies whether the page is statically generated, served from cache, or revalidated through ISR.

draftMode() enable sets __prerender_bypass cookie

Calling draft.enable() sets a cookie named __prerender_bypass. Subsequent requests that carry this cookie skip every cache layer in Draft Mode.

Draft Mode Route Handler basic example

import { draftMode } from 'next/headers' export async function GET(request: Request) { const draft = await draftMode() draft.enable() return new Response('Draft mode is enabled') }

Draft Mode Route Handler with secret and slug validation

import { draftMode } from 'next/headers' import { redirect } from 'next/navigation' export async function GET(request: Request) { const { searchParams } = new URL(request.url) const secret = searchParams.get('secret') const slug = searchParams.get('slug') // This secret should only be known to this Route Handler and the CMS if (secret !== 'MY_SECRET_TOKEN' || !slug) { return new Response('Invalid token', { status: 401 }) } // Verify the slug exists in the CMS before enabling Draft Mode const post = await getPostBySlug(slug) if (!post) { return new Response('Invalid slug', { status: 401 }) } const draft = await draftMode() draft.enable() // Redirect to the path from the fetched post, not from searchParams, // to avoid open redirect vulnerabilities redirect(post.slug) }

Draft Mode preview banner with exit form

import { draftMode } from 'next/headers' import { redirect } from 'next/navigation' async function exitPreview() { 'use server' const draft = await draftMode() draft.disable() redirect('/') } export async function PreviewBanner() { const { isEnabled } = await draftMode() if (!isEnabled) return null return ( <aside role="status"> Preview mode is on.{' '} <form action={exitPreview}> <button type="submit">Exit preview</button> </form> </aside> ) }

Draft Mode with 'use cache' directive

You can read isEnabled inside a 'use cache' scope to render a preview indicator from a cached component. The cache bypass still applies, so the component re-executes with fresh data on every draft request. Example: async function Post({ slug }: { slug: string }) { 'use cache' const post = await fetch(`https://cms.example.com/posts/${slug}`).then((r) => r.json() ) const { isEnabled } = await draftMode() return ( <article> {isEnabled && <p role="status">Draft preview</p>} <h1>{post.title}</h1> <div>{post.content}</div> </article> ) }

draftMode().enable() and disable() cannot be called inside cache scope

draftMode().enable() and draftMode().disable() cannot be called inside a caching directive scope. Toggle Draft Mode from a Route Handler or Server Action instead.

Draft Mode with separate draft endpoint

If a CMS exposes draft content at a different URL or requires different credentials, branch the fetch on isEnabled: const { isEnabled } = await draftMode() const baseUrl = isEnabled ? 'https://cms.example.com/preview' : 'https://cms.example.com/published' const res = await fetch(`${baseUrl}/posts/${slug}`) return res.json() The cache bypass still applies to both branches; the fork only chooses where to read from.

Use POST not GET for exit preview handler

Exiting Draft Mode should use POST, not GET, because it affects future requests. This is semantically more correct. If using a GET Route Handler to exit, trigger it from a <form method="GET"> rather than a <Link>. Next.js prefetches <Link> components by default, which would clear the cookie before the editor clicks. Forms are not prefetched.

Draft Mode CMS integration URL format

The draft URL format passed to a CMS preview integration is: https://<your-site>/api/draft?secret=<token>&slug=<path> where <your-site> is the deployment domain, <token> is the secret token, and <path> is the path for the page to preview. The CMS might allow variables like &slug=/posts/{entry.fields.slug} so the path can be set dynamically based on CMS data.

Draft Mode prevents open redirect vulnerability

When enabling Draft Mode, redirect to the path from the fetched post object, not from the searchParams. This prevents open redirect vulnerabilities. Example: redirect(post.slug) instead of redirect(slug).

refreshTags() errors should be caught to avoid request failures

A cache handler must catch errors in refreshTags(): if it throws, the exception propagates as a request failure. Catching the error allows requests to continue with the last known local tag state, serving potentially stale content until connectivity is restored.

Two types of revalidation: time-based and on-demand

Time-based revalidation uses a stale-while-revalidate pattern where cached content is served immediately and a background regeneration is triggered when the content's age exceeds the cacheLife or revalidate duration. On-demand revalidation explicitly invalidates cached content by calling revalidateTag() or revalidatePath(), and the next request to that content triggers a fresh render.

Revalidation regenerates both HTML response and RSC payload

When a route is revalidated, Next.js regenerates both the HTML response and the RSC payload (React Server Components payload) from the same React component tree. Both artifacts are stored together in the same cache entry to ensure consistency, as the RSC payload is used for client-side navigations.

HTML and RSC payload must be cached together with same TTL

If a platform's cache serves HTML from one render and an RSC payload from a different render, users may see stale or mismatched content during client-side navigation. The primary mitigation is to cache HTML and RSC responses together with the same TTL and invalidation policy, and to respect the Vary header that Next.js sets.

Multi-instance revalidation is local by default

When running multiple Next.js instances behind a load balancer, revalidation events are local by default. Calling revalidateTag() on instance A only invalidates the cache on that instance. Other instances continue serving the stale content until they learn about the invalidation.

updateTags() hook for writing invalidation events to shared storage

The updateTags() hook is called when revalidateTag() is invoked. A cache handler should write the invalidation event to shared storage such as Redis or a database so other instances can discover it.

refreshTags() hook for checking shared storage for invalidation events

The refreshTags() hook is called periodically but always before starting a new request. A cache handler should check shared storage for recent invalidation events and update its local tag state accordingly.

Single instance cache handled by file-system automatically

For a single instance, the default file-system cache handles consistency automatically. Cache writes are atomic on the local filesystem, and tag state is maintained in memory. No additional configuration is needed.

Multi-instance with shared cache requires coordination setup

To reduce the window for stale content and ensure revalidation propagates across instances: store tag invalidation timestamps in a shared service like Redis, DynamoDB, or HTTP API; implement updateTags() to write to the shared service; implement refreshTags() to read from the shared service with error handling; optionally store cache entries (HTML + RSC payload) in shared storage where atomic writes reduce the mismatch window.

Revalidation system prioritizes availability over strict consistency

The revalidation system prioritizes availability over strict consistency. Content is always served, even when infrastructure guarantees cannot be fully met. Cache failures result in degraded performance (stale content, extra renders), not broken applications.

Cache write failure behavior: response served, next request fresh renders

When a cache write fails, the response is still served to the user because writes are asynchronous. The cache entry is lost, and the next request triggers a fresh render.

Cache read failure: handler should return undefined not throw

When a cache read fails, a cache handler should catch internal errors and return undefined (the cache miss signal). The route is then server-rendered fresh. A thrown error is not treated as a cache miss; it propagates as a render error, so always return undefined to signal a miss.

Pages Router on-demand ISR APIs still supported using cacheHandler

Pages Router on-demand ISR APIs such as res.revalidate() and the x-prerender-revalidate flow are still supported and use the server cache handler (cacheHandler, singular). The cacheHandlers option (plural) is for use cache directives.

Routes that can be revalidated on demand in Next.js

Most routes in Next.js can be revalidated on demand, including App Router routes and Pages Router routes that produce ISR/prerender cache entries. Pages Router routes that are automatically statically optimized (pure static output) are not revalidated on demand.

Fetch cache behavior default in app directory

By default, fetch requests in the `app` directory use `cache: 'force-cache'`, which caches the request data until manually invalidated. This is similar to `getStaticProps` behavior in the `pages` directory.

Next.js automatic caching of requests and assets

Next.js caches data requests, the rendered result of Server and Client Components, and static assets to reduce network requests to the server, database, and backend services. Caching can be opted out where appropriate.

Parallel data fetching reduces network waterfalls

Data should be fetched in parallel where appropriate to reduce network waterfalls in Next.js applications.

Verify data request caching in Next.js

Data requests should be verified to ensure they are being cached or not. Requests that do not use fetch should be cached using unstable_cache.

Extract and pass pattern for session-dependent caching

When a cached lookup depends on session data, extract the session value outside the cached function and pass it as an argument. The cookies() call stays outside the cache scope, the argument crosses the boundary, and the cached function has a deterministic signature. The cache entry is keyed on that argument, and sessions sharing the value share the cache entry.

use cache: private directive for session-scoped caching

The 'use cache: private' directive assigns a cache lifetime to a function that reads cookies, headers, or other runtime data directly. Results are cached in the browser only, scoped to that session. This pattern is used when the lookup is tied to a single session or when runtime data cannot be extracted from outside the cached function.

Next.js cache stored on local filesystem by default

Caching and revalidating pages with Incremental Static Regeneration use the same Next.js server cache. By default, this cache is stored on the local filesystem (on disk) of each Next.js server instance. This works automatically for a single self-hosted next start instance with persistent local disk. If running multiple instances, using ephemeral compute, or placing a CDN/reverse proxy in front of Next.js, you need to configure caching differently.

Automatic caching headers for immutable assets

Next.js sets the Cache-Control header of 'public, max-age=31536000, immutable' to truly immutable assets. These immutable files contain a SHA-hash in the file name and can be safely cached indefinitely. Examples include Static Image Imports. The TTL for images can be configured.

ISR cache control header

Incremental Static Regeneration (ISR) sets the Cache-Control header of 's-maxage: <revalidate in getStaticProps>, stale-while-revalidate'. The revalidation time is defined in seconds in the getStaticProps function. If revalidate is set to false, it will default to a one-year cache duration. To leverage this at the CDN layer, the CDN/reverse proxy must respect these directives and cache-key variability.

Dynamic pages cache control header

Dynamically rendered pages set a Cache-Control header of 'private, no-cache, no-store, max-age=0, must-revalidate' to prevent user-specific data from being cached. This applies to both the App Router and Pages Router and includes Draft Mode.

Give your agent this brain