Streaming metadata behavior
For dynamically rendered pages, Next.js streams metadata separately, injecting it into the HTML once generateMetadata resolves, without blocking UI rendering. This improves perceived performance. Streaming metadata is disabled for bots and crawlers (e.g. Twitterbot, Slackbot, Bingbot) that expect metadata in the <head> tag, detected via User Agent header. Streaming can be customized or disabled completely with the htmlLimitedBots config option. Prerendered pages do not use streaming since metadata is resolved at build time.
Cache key composition includes deploymentId or buildId
Remote cache entries do not persist across deploys. The cache key includes the deploymentId (when configured) or the buildId, so a new build produces new keys and the previous build's entries are no longer reachable. Between builds, the function's identity hash or the shape of its return value can change, so reusing entries across deploys risks serving stale or malformed data.
Optimize cache keys to reduce unique values
Be thoughtful about which values you include in cache keys. Each unique value creates a separate cache entry, reducing cache utilization. For example, when caching products by category and price filter, cache only on category (few unique values) and don't include price filter (many unique values). Filter price in memory instead of creating cache entries for every price value. Similarly, for user-specific data, extract preferences like language that have fewer unique values, cache on that dimension, and filter or select the rest in memory.
Difference between 'use cache' in-memory limitations
'use cache' stores entries in-memory, which has inherent limitations: cache entries can be evicted to make room for new ones; memory constraints exist in your deployment environment; cache does not persist across requests or server restarts. In serverless environments, memory is not shared between instances and is typically destroyed after serving a request, leading to frequent cache misses for runtime caching. However, 'use cache' still provides value beyond server-side caching: it informs Next.js what can be prefetched and defines stale times for client-side navigation.
Use cacheTag() and revalidateTag() with remote caches
Use cacheTag() to tag remote cache entries and revalidateTag() to invalidate them on-demand. This allows you to manually revalidate specific cache entries when data changes without waiting for expiration.
revalidateTag with time parameter
The revalidateTag function can accept an optional second parameter. For example: revalidateTag('bookings-data', 'max') invalidates the cache for a specific tag.
fetch default caching behavior in App Router
The default cache option is 'auto no cache'. Next.js fetches the resource from the remote server on every request in development, but will fetch once during next build because the route will be statically prerendered. If Request-time APIs are detected on the route, Next.js will fetch the resource on every request.
fetch cache option: no-store
With cache: 'no-store', Next.js fetches the resource from the remote server on every request, even if Request-time APIs are not detected on the route.
fetch cache option: force-cache
With cache: 'force-cache', Next.js looks for a matching request in its server-side cache. Requests match on URL, method, headers, and body. If there is a fresh match, it is returned from the cache. If there is no match or a stale match, Next.js fetches from the remote server and updates the cache. Only responses with a 200 HTTP status code are stored.
fetch caching is opt-in for POST and authorization headers
Caching is opt-in. Set cache: 'force-cache' to cache any request, including POST and requests that send authorization or cookie headers. Draft Mode bypasses the cache entirely with no read or write.
fetch next.revalidate option values
The next.revalidate option sets the cache lifetime of a resource in seconds. Value false caches the resource indefinitely, equivalent to revalidate: Infinity (though HTTP cache may evict older resources). Value 0 prevents the resource from being cached. A number specifies the resource should have a cache lifetime of at most n seconds.
fetch revalidate value conflicts with route revalidation
If an individual fetch() request sets a revalidate number lower than the default revalidate of a route, the whole route revalidation interval will be decreased.
fetch revalidate value conflicts between requests
If two fetch requests with the same URL in the same route have different revalidate values, the lower value will be used.
fetch conflicting cache options are ignored
Conflicting options such as { revalidate: 3600, cache: 'no-store' } are not allowed, both will be ignored, and in development mode a warning will be printed to the terminal.
fetch next.tags option
The next.tags option sets cache tags of a resource: fetch('https://...', { next: { tags: ['collection'] } }). Data can then be revalidated on-demand using revalidateTag(). The max length for a custom tag is 256 characters and the max tag items is 128.
fetch GET requests are memoized
fetch requests using GET with the same URL and options are automatically memoized during a server render pass. If you call the same fetch in multiple Server Components, layouts, pages, generateStaticParams and generateViewport, Next.js executes it only once and shares the result. Memoization lasts only for a single render pass, while cached responses persist across requests.
opt out of fetch memoization with AbortController
To opt out of fetch memoization, pass an AbortController signal to fetch: const { signal } = new AbortController(); fetch(url, { signal })
fetch memoization does not apply in Route Handlers
Memoization does not apply in Route Handlers since they are not part of the React component tree.
fetch HMR cache behavior in development
Next.js caches fetch responses in Server Components across Hot Module Replacement (HMR) in local development for faster responses and to reduce costs for billed API calls. By default, the HMR cache applies to all fetch requests, including those with the default 'auto no cache' and cache: 'no-store' option. This means uncached requests will not show fresh data between HMR refreshes, but the cache will be cleared on navigation or full-page reloads.
fetch behavior with cache-control: no-cache header
In development mode, if the request includes the cache-control: no-cache header, options.cache, options.next.revalidate, and options.next.tags are ignored, and the fetch request is served from the source. Browsers typically include cache-control: no-cache when the cache is disabled in developer tools or during a hard refresh.
updateTag purpose and behavior
updateTag allows you to update cached data on-demand for a specific cache tag. It is designed for read-your-own-writes scenarios where a user makes a change and the UI immediately shows the updated data rather than stale data. updateTag immediately expires the cached data for the specified tag, and the next request will wait to fetch fresh data rather than serving stale content from the cache.
Assigning cache tags to data
Tags must first be assigned to cached data. This can be done in two ways: (1) Using the next.tags option with fetch for caching external API requests: fetch(url, { next: { tags: ['posts'] } }); (2) Using cacheTag from next/cache inside cached functions or components with the 'use cache' directive: import { cacheTag } from 'next/cache'; async function getData() { 'use cache'; cacheTag('posts'); // ... }
Differences between updateTag and revalidateTag
updateTag and revalidateTag serve different purposes. updateTag can only be used in Server Actions, causes the next request to wait for fresh data with no stale content served, and is designed for read-your-own-writes scenarios. revalidateTag can be used in Server Actions and Route Handlers; with profile="max" (recommended) it serves cached data while fetching fresh data in the background (stale-while-revalidate); with custom profile it can be configured to any cache life profile for advanced usage; without profile it has legacy behavior equivalent to updateTag.
When to use updateTag versus revalidateTag
Use updateTag when you are in a Server Action, need immediate cache invalidation for read-your-own-writes, and want to ensure the next request sees updated data. Use revalidateTag instead when you are in a Route Handler or other non-action context, want stale-while-revalidate semantics, or are building a webhook or API endpoint for cache invalidation.
generateMetadata streaming behavior
If the page can be prerendered and `generateMetadata` doesn't introduce dynamic behavior, the resulting metadata is included in the initial HTML. Otherwise, metadata resolved from `generateMetadata` can be streamed after sending the initial UI.
Streaming metadata behavior
Streaming metadata allows Next.js to render and send initial UI without waiting for `generateMetadata` to complete. When resolved, metadata tags are appended to the `<body>` tag. JavaScript-executing bots (like Googlebot) interpret metadata correctly from full DOM. HTML-limited bots (like facebookexternalhit) that can't execute JavaScript still have metadata block page rendering with tags in `<head>`. Next.js auto-detects HTML-limited bots via User Agent header.
Streaming metadata performance benefits
Streaming metadata improves perceived performance by reducing TTFB (Time to First Byte) and can help lower LCP (Largest Contentful Paint) time. However, overriding `htmlLimitedBots` could lead to longer response times. Streaming metadata is an advanced feature and the default should be sufficient for most cases.
cacheLife profile structure
A cacheLife profile is defined as an object with three optional properties: stale (number) - duration the client should cache a value without checking the server; revalidate (number) - frequency at which the cache should refresh on the server, with stale values possibly served while revalidating; expire (number) - maximum duration for which a value can remain stale before switching to dynamic. The expire value must be longer than the revalidate value if both are specified.
Built-in cacheLife profiles that can be overridden
The following built-in cache profiles can be overridden by defining a cacheLife profile with the same name: default, seconds, minutes, hours, days, weeks, or max.
revalidatePath relationship to cache tags
revalidatePath is a convenience layer on top of cache tags. Calling revalidatePath will call your revalidateTag function, which you can then choose if you want to tag cache keys based on the path.