LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 3019 min read

Caching in Next.js

Understand the layers of caching Next.js applies automatically — Request Memoization, Data Cache, Full Route Cache, and Router Cache.

Why Next.js Caches Aggressively

Next.js caches by default at multiple levels so that repeated work — the same fetch call, the same rendered route — doesn't happen more often than necessary. This is a major source of its performance, but it's also the single biggest source of confusion for developers new to the framework, since data can appear "stuck" if you don't know which cache is involved.

The Four Caching Layers

CacheWhat It StoresLifetimeWhere
Request MemoizationIdentical fetch() calls during one render passA single render passServer
Data CacheResults of fetch() callsPersists across requests until revalidatedServer
Full Route CacheRendered HTML + payload for static routesPersists until the next build or revalidationServer
Router CacheVisited route segments, for instant back/forward navSession-based, in the browserClient
The One That Matters Most Day-to-Day

For most debugging, the Data Cache is the one to think about first — it's what determines whether a given fetch() call reuses a stored result or genuinely goes to the network again.

Controlling the fetch Cache

OptionBehavior
fetch(url)Cached indefinitely by default (like SSG data)
fetch(url, { cache: "no-store" })Never cached — fetched fresh on every request (SSR-like)
fetch(url, { next: { revalidate: 60 } })Cached, but eligible to regenerate after 60 seconds (ISR-like)
fetch(url, { next: { tags: ["posts"] } })Cached and taggable, for on-demand invalidation with revalidateTag()
// Always fresh
const res = await fetch(url, { cache: 'no-store' });
// Cached, refreshed at most every 5 minutes
const res2 = await fetch(url, { next: { revalidate: 300 } });
// Cached and tagged for manual invalidation
const res3 = await fetch(url, { next: { tags: ['posts'] } });

Opting Out at the Route Level

Beyond individual fetch calls, a whole route segment can opt out of static caching by exporting a dynamic config.

// app/dashboard/page.tsx
export const dynamic = 'force-dynamic'; // always render fresh, never cache the route

Common Beginner Mistakes

Assuming data is "not updating" is a bug

Very often it's the Data Cache working as designed — check whether that fetch needs no-store, a revalidate interval, or a manual revalidateTag() call after a mutation.

Setting cache: "no-store" everywhere out of caution

This throws away the performance benefit caching provides — reserve it for data that genuinely must be fresh on every request.

FAQs

Briefly, by design — it's optimized for instant back/forward navigation. router.refresh() or a revalidation call clears it when you need guaranteed-fresh data.

Not as a single global switch — caching is controlled per fetch call and per route, which is more granular but means being deliberate about where you need it off.

Summary

Next.js's caching happens at four distinct layers, and most control comes from fetch options and route-level exports. Next, you'll learn how to explicitly invalidate cached data after a mutation, with revalidation.

Next Lesson →

Revalidating Data