principletypescriptnextjsMajor
Next.js caching layers: request memoization, Data Cache, Full Route Cache, Router Cache
Viewed 0 times
Next.js 13+ (caching system); Next.js 15 changed fetch default to no-store
cachingdata cachefull route cacherouter cacherequest memoizationno-storeforce-dynamic
Problem
Next.js has four distinct caching layers that interact in non-obvious ways. Data fetched once in a page appears stale, or the same API is called multiple times despite memoization, because developers don't understand which cache is responsible.
Solution
Understand each cache layer and how to opt out:
// 1. Request Memoization — deduplicates identical fetch() calls within ONE request
const a = await fetch('https://api.example.com/user'); // hits network
const b = await fetch('https://api.example.com/user'); // returns memoized result
// 2. Data Cache — persists fetch() results across requests
// Opt out:
const data = await fetch(url, { cache: 'no-store' });
// Opt in with TTL:
const data2 = await fetch(url, { next: { revalidate: 60 } });
// 3. Full Route Cache — caches entire rendered HTML+RSC payload at build time
// Force dynamic rendering:
export const dynamic = 'force-dynamic';
// Force static:
export const dynamic = 'force-static';
// 4. Router Cache (client-side) — caches RSC payloads in browser during navigation
// Invalidate with:
router.refresh();
// Or server-side:
revalidatePath('/path');
revalidateTag('tag');
// Cache an ORM call (not a fetch) with unstable_cache:
import { unstable_cache } from 'next/cache';
const getUser = unstable_cache(
async (id: string) => db.user.findUnique({ where: { id } }),
['user'],
{ tags: ['users'], revalidate: 60 }
);
// 1. Request Memoization — deduplicates identical fetch() calls within ONE request
const a = await fetch('https://api.example.com/user'); // hits network
const b = await fetch('https://api.example.com/user'); // returns memoized result
// 2. Data Cache — persists fetch() results across requests
// Opt out:
const data = await fetch(url, { cache: 'no-store' });
// Opt in with TTL:
const data2 = await fetch(url, { next: { revalidate: 60 } });
// 3. Full Route Cache — caches entire rendered HTML+RSC payload at build time
// Force dynamic rendering:
export const dynamic = 'force-dynamic';
// Force static:
export const dynamic = 'force-static';
// 4. Router Cache (client-side) — caches RSC payloads in browser during navigation
// Invalidate with:
router.refresh();
// Or server-side:
revalidatePath('/path');
revalidateTag('tag');
// Cache an ORM call (not a fetch) with unstable_cache:
import { unstable_cache } from 'next/cache';
const getUser = unstable_cache(
async (id: string) => db.user.findUnique({ where: { id } }),
['user'],
{ tags: ['users'], revalidate: 60 }
);
Why
Next.js layers caches to maximize performance: memoization deduplicates per-request, Data Cache persists across requests, Full Route Cache serves pre-rendered HTML, Router Cache avoids server round-trips during client navigation.
Gotchas
- In Next.js 15, fetch() defaults to no-store (uncached) — you must explicitly opt into caching with next: { revalidate }
- The Router Cache (client) is separate from the server Data Cache — router.refresh() clears only the client cache
- cookies() and headers() calls inside a Server Component automatically opt the route into dynamic rendering
- unstable_cache() from next/cache can cache non-fetch async functions like ORM calls
Code Snippets
Caching ORM calls with unstable_cache
// Next.js 15: fetch is uncached by default
// Opt into caching explicitly:
const data = await fetch(url, { next: { revalidate: 3600 } });
// Cache ORM queries:
const cachedGetUser = unstable_cache(
(id: string) => db.user.findUnique({ where: { id } }),
['user'],
{ tags: ['users'] }
);Context
When debugging stale data or unexpected cache behavior in Next.js App Router
Revisions (0)
No revisions yet.