Caching

Next.js caching has changed significantly across versions. Understanding which version you’re on determines what defaults apply and which APIs are available.

The Big Picture

Versionfetch defaultCache APIKey feature
v14force-cache (aggressive)revalidate optionsEverything cached by default
v15no-store (no caching)Same revalidate optionsBreaking change - opt into caching
v16no-store"use cache" directiveCache Components, compiler-driven

Next.js 15: The Caching Reset

Next.js 15 flipped the default. fetch() no longer caches by default. This was the biggest breaking change in the v15 release.

Before (v14) - cached by default

// v14: This fetch is cached indefinitely by default
const data = await fetch("https://api.example.com/posts");

After (v15+) - no caching by default

// v15+: This fetch runs every request (no caching)
const data = await fetch("https://api.example.com/posts");

// Explicitly opt into caching
const data = await fetch("https://api.example.com/posts", {
  next: { revalidate: 3600 }, // cache for 1 hour
});

// Or cache indefinitely
const data = await fetch("https://api.example.com/posts", {
  cache: "force-cache",
});

Gotcha: If you’re upgrading from v14 to v15+, your app will make many more network requests than before. Audit your fetch calls and add explicit caching where needed.

Fetch Caching Options

// No caching (default in v15+)
fetch(url);
fetch(url, { cache: "no-store" });

// Cache until manually revalidated
fetch(url, { cache: "force-cache" });

// Cache with time-based revalidation
fetch(url, { next: { revalidate: 60 } }); // revalidate every 60 seconds

// Cache with tag-based revalidation
fetch(url, { next: { tags: ["posts"] } });

Revalidation

// Time-based: set on the fetch or at the segment level
export const revalidate = 3600; // revalidate this page every hour

// On-demand: trigger from a Server Action or Route Handler
import { revalidateTag, revalidatePath } from "next/cache";

async function publishPost() {
  "use server";
  await db.post.create({ ... });
  revalidateTag("posts");       // revalidate all fetches tagged "posts"
  revalidatePath("/blog");      // revalidate a specific path
}

Next.js 16: “use cache” Directive

Next.js 16 introduces a new caching model built on the "use cache" directive. Instead of configuring caching per-fetch, you mark entire components, pages, or functions as cacheable.

"use cache";

export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const product = await db.product.findUnique({ where: { id } });

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>
    </div>
  );
}

The compiler analyzes the function’s inputs and generates cache keys automatically. No manual key management.

Granular Caching

// Cache an entire page
"use cache";
export default async function Page() { ... }

// Cache a specific function
async function getProduct(id: string) {
  "use cache";
  return db.product.findUnique({ where: { id } });
}

// Cache a component
async function ProductCard({ id }: { id: string }) {
  "use cache";
  const product = await getProduct(id);
  return <div>{product.name}</div>;
}

cacheLife Profiles

Control how long cached values live:

import { cacheLife } from "next/cache";

async function getProducts() {
  "use cache";
  cacheLife("hours"); // built-in profile

  return db.product.findMany();
}

Built-in profiles:

ProfileStaleRevalidateExpire
"default"undefined15 minundefined
"seconds"01 sec60 sec
"minutes"5 min1 min1 hour
"hours"5 min1 hour1 day
"days"5 min1 day1 week
"weeks"5 min1 week1 month
"max"5 min1 monthindefinite

Custom profiles in next.config.ts:

// next.config.ts
const nextConfig = {
  cacheLife: {
    catalog: {
      stale: 300,      // 5 min client cache
      revalidate: 900,  // 15 min server revalidate
      expire: 86400,    // 24 hour max age
    },
  },
};
export default nextConfig;

Cache Hierarchy

Requests flow through multiple cache layers:

Client Request
  -> Router Cache (client-side, in-memory)
  -> Full Route Cache (server-side, rendered pages)
  -> Data Cache (server-side, fetch results)
  -> Data Source (origin)
LayerWhereWhatDuration
Router CacheClientPrefetched route segmentsSession (resets on refresh)
Full Route CacheServerPre-rendered HTML + RSC payloadUntil revalidated
Data CacheServerfetch responsesUntil revalidated

Tip: When debugging caching issues, work backwards: is it the Router Cache (client)? The Full Route Cache (server)? Or the Data Cache (fetch)? Each has different invalidation strategies.

Opting Out of Caching

// Per-fetch
fetch(url, { cache: "no-store" });

// Per-page/layout (force dynamic rendering)
export const dynamic = "force-dynamic";

// Using dynamic functions (automatically opts out)
import { cookies, headers } from "next/headers";
const cookieStore = await cookies(); // makes the route dynamic

Next: Server Components | Cache Components