Caching
Understand the four caching layers in Next.js -- request memoization, data cache, full route cache, and router cache.
Search across all documentation pages
Understand the four caching layers in Next.js -- request memoization, data cache, full route cache, and router cache.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Quick-reference recipe card -- copy-paste ready.
// Layer 1: Request Memoization (automatic for fetch, manual for others)
// Same fetch URL is deduplicated within a single render pass
const data = await fetch("https://api.example.com/user/1"); // called in 3 components, only 1 request
// Manual memoization for non-fetch calls
import { cache } from "react";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
// Layer 2: Data Cache (persists across requests)
await fetch(url, { next: { revalidate: 3600 } }); // cached 1 hour
await fetch(url, { cache: "no-store" }); // skip data cache
await fetch(url, { next: { tags: ["users"] } }); // tag for invalidation
// Layer 3: Full Route Cache (static pages at build time)
// Automatic for routes with no dynamic data
// Layer 4: Router Cache (client-side, in-memory)
// Automatic for <Link> prefetching and back/forward navigationWhen to reach for this: You need to understand why your data is stale, why a page is not updating, or how to optimize performance by leveraging the right cache layer.
// lib/data.ts -- demonstrating all caching layers
import { cache } from "react";
import { db } from "@/lib/db";
// ---- Layer 1: Request Memoization ----
// This function can be called from multiple Server Components
// in the same render pass -- only one DB query runs
export const getCurrentUser = cache(async () => {
const session = await getSession();
if (!session) return null;
return db.user.findUnique({ where: { id: session.userId } });
});
// ---- Layer 2: Data Cache ----
export async function getProducts() {
const res = await fetch("https://api.example.com/products", {
next: { revalidate: 600, tags: ["products"] },
});
return res.json() as Promise<Product[]>;
}
// No caching -- always fresh
export async function getCartItems(userId: string) {
const res = await fetch(`https://api.example.com/cart/${userId}`, {
cache: "no-store",
});
return res.json() as Promise<CartItem[]>;
}// app/layout.tsx -- getCurrentUser called here...
import { getCurrentUser } from "@/lib/data";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const user = await getCurrentUser(); // Request 1 (actual DB call)
return (
<html lang="en">
<body>
<nav>
<span>{user?.name ?? "Guest"}</span>
</nav>
{children}
</body>
</html>
);
}// app/dashboard/page.tsx -- ...and also called here (deduplicated)
import { getCurrentUser } from "@/lib/data";
export default async function DashboardPage() {
const user = await getCurrentUser(); // Request 2 (memoized -- no DB call)
return <h1>Welcome back, {user?.name}</h1>;
}What this demonstrates:
React.cache deduplicating a database call across two Server Components in the same rendernext.revalidate placing fetch results into the Data Cachecache: "no-store" bypassing the Data Cache for user-specific datagetCurrentUser() without triggering two queriesfetch is called with the same URL and options, React returns the memoized result. For non-fetch calls (database, ORM), wrap the function with React.cache(). Memoization is cleared after the render completes.next.revalidate for time-based expiry and next.tags for on-demand invalidation via revalidateTag().cookies(), headers(), searchParams, or cache: "no-store") are excluded.<Link>, the next page may already be cached. The router cache has different lifetimes: 30 seconds for dynamic pages, 5 minutes for static pages (these are defaults and configurable in Next.js 15+).Opting out of all caching for a route:
// app/real-time/page.tsx
export const dynamic = "force-dynamic"; // skips Full Route Cache
export const fetchCache = "force-no-store"; // skips Data Cache for all fetchesUsing unstable_cache for non-fetch data:
import { unstable_cache } from "next/cache";
const getCachedPosts = unstable_cache(
async () => db.post.findMany(),
["all-posts"], // cache key parts
{
tags: ["posts"],
revalidate: 3600,
}
);Controlling Router Cache behavior:
// next.config.ts
import type { NextConfig } from "next";
const config: NextConfig = {
experimental: {
staleTimes: {
dynamic: 0, // don't cache dynamic pages on the client
static: 300, // cache static pages for 5 minutes
},
},
};
export default config;// React.cache preserves the function signature
import { cache } from "react";
const getUser = cache(
async (id: string): Promise<User | null> => {
return db.user.findUnique({ where: { id } });
}
);
// getUser: (id: string) => Promise<User | null>
// Segment config types
export const dynamic: "auto" | "force-dynamic" | "force-static" | "error" = "auto";
export const revalidate: number | false = 60;
export const fetchCache: "auto" | "default-cache" | "only-cache" |
"force-cache" | "force-no-store" | "default-no-store" | "only-no-store" = "auto";Confusing request memoization with the Data Cache -- Request memoization is per-render and ephemeral; the Data Cache persists across requests. Fix: Use React.cache() for per-request deduplication and next.revalidate / next.tags for persistent caching.
Router Cache serving stale pages -- After a mutation, the client-side Router Cache may still serve the old page during back/forward navigation. Fix: Call router.refresh() in the Client Component after a Server Action, or configure staleTimes to reduce client-side cache duration.
cache: "force-cache" is not always the default -- In Next.js 15+, the default is cache: "auto", which may vary depending on context (dynamic functions, route config). Fix: Be explicit about caching behavior by setting cache or next.revalidate on every fetch.
unstable_cache key collisions -- If two unstable_cache calls share the same key parts but return different data shapes, you get corrupted cache. Fix: Use unique, descriptive key arrays.
Middleware and Edge Runtime do not share caches -- The Data Cache is per-deployment-region. Middleware running at the edge may not see the same cache as a Node.js server function. Fix: Be aware of cache boundaries in multi-region deployments.
| Alternative | Use When | Don't Use When |
|---|---|---|
React.cache() | You need per-request deduplication for non-fetch calls | You need data to persist across requests |
unstable_cache | You need Data Cache semantics for database or ORM calls | Plain fetch with next.revalidate works |
| Redis or Memcached | You need a shared cache across multiple server instances | The built-in Next.js cache is sufficient |
SWR stale-while-revalidate | You want client-side caching with automatic revalidation | Server-side caching covers your needs |
cache: "no-store" everywhere | Data must always be fresh (real-time dashboards) | You can tolerate stale data for better performance |
import { cache } from "react";
import { db } from "@/lib/db";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});React.cache() for per-request deduplicationcache: "auto", not force-cache as in Next.js 14cache or next.revalidate on every fetchstaleTimes in next.config.tsexport const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";force-dynamic skips the Full Route Cacheforce-no-store skips the Data Cache for all fetches in the routerouter.refresh() in the Client Component or reduce staleTimes in configunstable_cache callimport { cache } from "react";
const getUser = cache(
async (id: string): Promise<User | null> => {
return db.user.findUnique({ where: { id } });
}
);
// getUser: (id: string) => Promise<User | null>React.cache() provides per-request deduplication only; data does not persist across requestsunstable_cache provides Data Cache semantics (persists across requests, supports tags and revalidation)unstable_cache for database or ORM calls that need cross-request caching// next.config.ts
const config: NextConfig = {
experimental: {
staleTimes: {
dynamic: 0, // don't cache dynamic pages on the client
static: 300, // cache static pages for 5 minutes
},
},
};export const fetchCache:
| "auto"
| "default-cache"
| "only-cache"
| "force-cache"
| "force-no-store"
| "default-no-store"
| "only-no-store" = "auto";Reviewed by Chris St. John·Last updated Jul 7, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥