Caching Configurations

1. cacheHandler & cacheMaxMemorySize

The cacheHandler option allows you to configure a custom cache implementation for Next.js, enabling you to persist cached pages and data to durable storage or share the cache across multiple containers or instances of your Next.js application.

By default, Next.js uses an in-memory cache that doesn't persist between deployments or share across instances. A custom cache handler is particularly useful for:

To configure a custom cache handler:

module.exports = { cacheHandler: require.resolve('./cache-handler.js'), cacheMaxMemorySize: 0, // disable default in-memory caching }

Your custom cache handler must implement the following methods:

Example Redis-based cache handler:

const Redis = require('ioredis'); const redis = new Redis(process.env.REDIS_URL); class CustomCacheHandler { async get(key) { const value = await redis.get(key); return value ? JSON.parse(value) : null; } async set(key, data, ctx) { await redis.setex(key, 3600, JSON.stringify(data)); if (ctx?.tags) { // Store reverse mapping for tag-based invalidation for (const tag of ctx.tags) { await redis.sadd(`tag:${tag}`, key); } } } async revalidateTag(tag) { const keys = await redis.smembers(`tag:${tag}`); if (keys.length > 0) { await redis.del(...keys); await redis.del(`tag:${tag}`); } } resetRequestCache() { // Clear any request-scoped cache } } module.exports = CustomCacheHandler;

2. cacheLife

The cacheLife function is used in conjunction with the use cache directive to define how long cached data should remain valid. This is part of Next.js's experimental caching system that works with dynamicIO and useCache flags.

When you use the use cache directive, you can specify cache lifetime policies:

import { cacheLife } from 'next/cache'; export default async function ProductPage({ params }) { 'use cache'; cacheLife('max'); const product = await fetchProduct(params.id); return <div>{product.name}</div>; }

Available cache life policies include:

For dynamic content with specific cache requirements:

async function getWeatherData() { 'use cache'; cacheLife('1 hour'); return fetch('/api/weather').then(r => r.json()); }

This is particularly useful for:



3. dynamicIO

The dynamicIO flag is an experimental feature that changes how Next.js handles data fetching in the App Router. When enabled, data fetching operations are excluded from pre-renders unless explicitly cached with use cache.

This is useful when your application requires fresh data at runtime rather than serving pre-rendered content:

import type { NextConfig } from 'next' const nextConfig: NextConfig = { experimental: { dynamicIO: true, }, } export default nextConfig

With dynamicIO enabled, consider this component:

// Without 'use cache' - always fetches fresh data at runtime export default async function UserDashboard() { const user = await getCurrentUser(); // Always fresh const notifications = await getNotifications(); // Always fresh return ( <div> <h1>Welcome, {user.name}</h1> <NotificationList notifications={notifications} /> </div> ); } // With 'use cache' - can be pre-rendered async function getStaticContent() { 'use cache'; return await fetchStaticContent(); }

This approach is beneficial for:

Note that while dynamicIO ensures fresh data, it may introduce additional latency compared to pre-rendered content.



4. expireTime

The expireTime option configures a custom stale-while-revalidate expire time for CDNs in the Cache-Control header for ISR (Incremental Static Regeneration) enabled pages.

This setting helps CDNs understand how long they can serve stale content while revalidating in the background:

module.exports = { // one hour in seconds expireTime: 3600, }

The expire time works with your page's revalidate setting. For example, if you have:

// pages/blog/[slug].js export async function getStaticProps({ params }) { const post = await fetchBlogPost(params.slug); return { props: { post }, revalidate: 900, // 15 minutes }; }

With expireTime: 3600 (1 hour), Next.js generates:

Cache-Control: s-maxage=900, stale-while-revalidate=2700

This means:

This is particularly useful for:



5. generateEtags

The generateEtags option controls whether Next.js automatically generates ETags for HTML pages. ETags are HTTP response headers that help with browser caching by allowing the browser to check if content has changed.

By default, Next.js generates ETags for every page. You might want to disable this depending on your caching strategy:

module.exports = { generateEtags: false, }

Reasons to disable ETag generation:

Example of ETag behavior:

// With generateEtags: true (default) // Response headers include: // ETag: "1234567890abcdef" // Browser on subsequent request sends: // If-None-Match: "1234567890abcdef" // If content unchanged, server responds with 304 Not Modified

For applications with:

You might prefer to handle cache validation manually rather than relying on automatic ETag generation.



6. onDemandEntries

The onDemandEntries option controls how Next.js manages built pages in memory during development. This affects development server performance and memory usage.

Configure the development page buffer settings:

module.exports = { onDemandEntries: { // period (in ms) where the server will keep pages in the buffer maxInactiveAge: 25 * 1000, // 25 seconds // number of pages that should be kept simultaneously without being disposed pagesBufferLength: 2, }, }

These settings control:

For different development scenarios:

// For large applications with many pages (reduce memory usage) module.exports = { onDemandEntries: { maxInactiveAge: 10 * 1000, // 10 seconds pagesBufferLength: 1, // Keep only 1 page }, } // For small teams frequently switching between pages (increase performance) module.exports = { onDemandEntries: { maxInactiveAge: 60 * 1000, // 1 minute pagesBufferLength: 5, // Keep 5 pages }, }

This is particularly useful when:



7. serverComponentsHmrCache

The serverComponentsHmrCache is an experimental feature that caches fetch responses in Server Components across Hot Module Replacement (HMR) refreshes during local development.

This reduces API calls and speeds up development by caching responses between code changes:

import type { NextConfig } from 'next' const nextConfig: NextConfig = { experimental: { serverComponentsHmrCache: false, // defaults to true }, } export default nextConfig

When enabled (default), this affects Server Components like:

// This fetch will be cached across HMR refreshes export default async function BlogPost({ slug }) { const post = await fetch(`/api/posts/${slug}`, { cache: 'no-store' // Even no-store requests are cached in HMR }).then(r => r.json()); return <article>{post.content}</article>; }

Benefits:

Important considerations:

For better observability, combine with logging:

const nextConfig: NextConfig = { experimental: { serverComponentsHmrCache: true, }, logging: { fetches: { fullUrl: true, }, }, }

8. staleTimes

The staleTimes experimental feature enables caching of page segments in the client-side router cache, controlling how long different types of content remain cached on the client.

Configure stale times for different content types:

/** @type {import('next').NextConfig} */ const nextConfig = { experimental: { staleTimes: { dynamic: 30, // 30 seconds for dynamic content static: 180, // 3 minutes for static content }, }, } module.exports = nextConfig

The two properties control different scenarios:

Example usage with Next.js routing:

import Link from 'next/link'; export default function Navigation() { return ( <nav> {/* This link benefits from static stale time (180s) */} <Link href="/about" prefetch={true}> About </Link> {/* This link uses dynamic stale time (30s) */} <Link href="/dashboard"> Dashboard </Link> </nav> ); }

Benefits for different content types:

// Static marketing pages - longer cache time staleTimes: { static: 300, // 5 minutes } // Dynamic user dashboards - shorter cache time staleTimes: { dynamic: 15, // 15 seconds } // Balanced approach for mixed content staleTimes: { dynamic: 30, // 30 seconds static: 180, // 3 minutes }

Important notes:



9. useCache

The useCache flag is an experimental feature that enables the use cache directive to be used independently of dynamicIO. This gives you granular control over caching at the component and function level.

Enable the useCache flag:

import type { NextConfig } from 'next' const nextConfig: NextConfig = { experimental: { useCache: true, }, } export default nextConfig

Once enabled, you can use caching directives in your components:

import { cacheTag, cacheLife } from 'next/cache'; // Cache an entire page component export default async function ProductCatalog() { 'use cache'; cacheLife('1 hour'); cacheTag('products', 'catalog'); const products = await fetchProducts(); return ( <div> {products.map(product => ( <ProductCard key={product.id} product={product} /> ))} </div> ); } // Cache a specific function async function getExpensiveData(id) { 'use cache'; cacheLife('30 minutes'); cacheTag('expensive-computation'); return await performExpensiveComputation(id); }

Available cache functions:

Practical caching strategies:

// Short-lived user-specific data async function getUserPreferences(userId) { 'use cache'; cacheLife('5 minutes'); cacheTag(`user-${userId}`, 'preferences'); return await fetchUserPreferences(userId); } // Long-lived static content async function getSiteConfiguration() { 'use cache'; cacheLife('24 hours'); cacheTag('site-config'); return await fetchSiteConfig(); } // Cache with manual invalidation async function getBlogPost(slug) { 'use cache'; cacheLife('max'); // Cache until manually invalidated cacheTag('blog-posts', `post-${slug}`); return await fetchBlogPost(slug); }

This approach enables: