CachingData Cache
Data Cache
Cache fetch() results and database queries between requests.
Next.js 15 changed caching defaults. In Next.js 14, fetch() is cached by default. In Next.js 15+, fetch() uses no-store by default—you must explicitly opt into caching with force-cache or revalidate.
Fetch Caching
In Next.js 14, fetch() is cached by default. In Next.js 15+, you need to explicitly enable caching:
// Next.js 14: Cached automatically
// Next.js 15+: NOT cached by default - use force-cache or revalidate
const data = await fetch('https://api.example.com/data');Control Duration
// Cache forever (until redeploy)
await fetch(url, { cache: 'force-cache' });
// Cache for 1 hour
await fetch(url, { next: { revalidate: 3600 } });
// Never cache
await fetch(url, { cache: 'no-store' });Cache Tags
// Tag your fetches
const product = await fetch(`/api/products/${id}`, {
next: { tags: [`product-${id}`, 'products'] },
});// Invalidate by tag
import { revalidateTag } from 'next/cache';
revalidateTag('products');Database Queries
Use unstable_cache for non-fetch data:
import { unstable_cache } from 'next/cache';
const getProduct = unstable_cache(
async (id: string) => {
return await db.products.findUnique({ where: { id } });
},
['product'],
{ revalidate: 3600, tags: ['products'] }
);Request Memoization
Deduplicates identical calls in a single render:
const user = await getUser(); // Fetch 1
// page.tsx
const user = await getUser(); // Deduplicated!For non-fetch, use React's cache():
import { cache } from 'react';
export const getUser = cache(async () => {
return await db.users.findFirst();
});Common Mistakes
Over-Caching User Data
// ❌ Shared key for all users
const getOrders = unstable_cache(
async (userId) => db.orders.findMany({ where: { userId } }),
['orders'] // Wrong!
);
// ✅ User-scoped key
const getOrders = (userId: string) =>
unstable_cache(
async () => db.orders.findMany({ where: { userId } }),
[`orders-${userId}`],
{ revalidate: 300 }
)();Forgetting to Invalidate
// ❌ Cache shows stale data
export async function POST(request: Request) {
await db.products.create({ data });
return Response.json({ ok: true });
}
// ✅ Invalidate after mutation
export async function POST(request: Request) {
await db.products.create({ data });
revalidateTag('products');
return Response.json({ ok: true });
}Key Collisions
// ❌ Same key, different queries
const getActive = unstable_cache(fn1, ['products']);
const getAll = unstable_cache(fn2, ['products']);
// ✅ Unique keys
const getActive = unstable_cache(fn1, ['products', 'active']);
const getAll = unstable_cache(fn2, ['products', 'all']);With caching set up, see Edge vs Serverless for where to run your code.