Skip to main content

Next.js Caching Strategies

Answer​

Next.js has multiple caching layers to optimize performance. Understanding how they work together is crucial for building fast applications.

Caching Layers​

1. Request Memoization​

// Same fetch in multiple components - only one request
async function getUser(id) {
// Memoized within same render
const res = await fetch(`/api/users/${id}`);
return res.json();
}

// Both components fetch, but only 1 actual request
async function Sidebar() {
const user = await getUser(1);
return <div>{user.name}</div>;
}

async function Header() {
const user = await getUser(1); // Deduped!
return <div>Welcome, {user.name}</div>;
}

2. Data Cache​

// Cached persistently (across requests)
const data = await fetch("https://api.example.com/data", {
cache: "force-cache", // Default - cached indefinitely
});

// No cache - always fresh
const data = await fetch("https://api.example.com/data", {
cache: "no-store",
});

// Time-based revalidation
const data = await fetch("https://api.example.com/data", {
next: { revalidate: 60 }, // Cache for 60 seconds
});

// Tag-based cache
const data = await fetch("https://api.example.com/data", {
next: { tags: ["posts"] },
});

// Revalidate by tag
import { revalidateTag } from "next/cache";
revalidateTag("posts");

3. Full Route Cache​

// Static routes are cached at build time
// app/about/page.js
export default function About() {
return <div>About Us</div>;
}
// Cached as static HTML

// Dynamic routes opt out
export const dynamic = 'force-dynamic';

// Or use dynamic functions
import { cookies, headers } from 'next/headers';
export default function Page() {
const cookieStore = cookies(); // Makes route dynamic
return <div>...</div>;
}

4. Router Cache (Client-side)​

// Prefetched routes cached in browser
<Link href="/about">About</Link>; // Prefetched, cached

// Invalidate router cache
import { useRouter } from "next/navigation";
const router = useRouter();
router.refresh(); // Invalidates router cache

Cache Configuration​

// Route Segment Config
export const dynamic = "auto"; // Default
export const dynamic = "force-dynamic"; // Always SSR
export const dynamic = "force-static"; // Always SSG
export const dynamic = "error"; // Error if dynamic

export const revalidate = 60; // ISR every 60s
export const revalidate = 0; // Always SSR
export const revalidate = false; // Cache forever

export const fetchCache = "auto";
export const fetchCache = "default-cache";
export const fetchCache = "only-cache";
export const fetchCache = "force-no-store";
export const fetchCache = "default-no-store";

Opting Out of Caching​

// 1. Using dynamic functions
import { cookies, headers } from "next/headers";

// 2. Route config
export const dynamic = "force-dynamic";

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

// 4. unstable_noStore
import { unstable_noStore as noStore } from "next/cache";

async function Component() {
noStore(); // Opts out of caching
const data = await fetchData();
}

On-Demand Revalidation​

// API Route to trigger revalidation
// app/api/revalidate/route.js
import { revalidatePath, revalidateTag } from "next/cache";

export async function POST(request) {
const { path, tag, secret } = await request.json();

if (secret !== process.env.REVALIDATION_SECRET) {
return Response.json({ error: "Invalid secret" }, { status: 401 });
}

if (path) {
revalidatePath(path);
}

if (tag) {
revalidateTag(tag);
}

return Response.json({ revalidated: true, now: Date.now() });
}

// Call from CMS webhook
// POST /api/revalidate
// { "tag": "posts", "secret": "..." }

Cache Hierarchy​

CacheDurationScopeOpt Out
Request MemoPer requestServerN/A
Data CachePersistentServerno-store, revalidate: 0
Full RouteBuild timeServerforce-dynamic
Router CacheSessionClientrouter.refresh()

Debug Caching​

// Check cache status in headers
// x-nextjs-cache: HIT | MISS | STALE

// Log fetch cache status
const res = await fetch(url);
console.log("Cache:", res.headers.get("x-nextjs-cache"));

Key Points​

  • Request Memoization: Dedupes within single render
  • Data Cache: Persistent, revalidate with time or tags
  • Full Route Cache: Static pages cached at build
  • Router Cache: Client-side navigation cache
  • Use revalidatePath / revalidateTag for on-demand updates
  • force-dynamic opts out of full route cache
  • cache: 'no-store' opts out of data cache