Skip to content

@tank/nextjs-caching-guide

1.0.0

Description

Deep guide to Next.js App Router caching layers, revalidation strategies, use cache directive, fetch cache options, static vs dynamic rendering, ISR, cache debugging, and version-specific behavior changes.

Triggered by

next.js cachingrevalidatePathrevalidateTagISRstale datause cache
Download
Verified
tank install @tank/nextjs-caching-guide

Next.js Caching Guide

Core Philosophy

  1. Understand the stack, not just the API — Next.js has four distinct cache layers that interact. Fixing stale data requires knowing which layer is stale, not blindly adding force-dynamic everywhere.
  2. Static by default, dynamic by intent — Start with static rendering and opt into dynamic behavior only for request-specific data. Every dynamic opt-in has a performance cost.
  3. Invalidation is the hard problem — Caching data is easy. Knowing when to invalidate is where bugs live. Prefer tag-based invalidation over path-based — tags follow data, paths follow UI.
  4. Version-aware decisions matter — Next.js 14, 15, and 16 each changed caching defaults significantly. Know which version runs in production before debugging.
  5. Cache debugging is a first-class skill — Use response headers, verbose logging, and the static route indicator to verify cache behavior rather than guessing.

Quick-Start: Common Problems

"I updated data but the page still shows old content"

  1. Identify which cache layer is stale — check the decision tree below
  2. After mutations in Server Actions, call revalidateTag('tag') or revalidatePath('/path')
  3. On the client after a Server Action, call router.refresh() to clear Router Cache
  4. Verify with NEXT_PRIVATE_DEBUG_CACHE=1 that revalidation fires -> See references/four-cache-layers.md and references/revalidation-strategies.md

"Everything is dynamic and slow"

  1. Check if force-dynamic or cache: 'no-store' is set at a layout level — it cascades to all children
  2. Move dynamic data reads to leaf components, keep parent routes static
  3. Use revalidate with a time interval instead of disabling caching entirely
  4. For user-specific data alongside cached content, use Partial Prerendering or composition patterns -> See references/static-dynamic-rendering.md

"Which caching model — fetch options or use cache?"

Next.js VersionModelKey API
14.xfetch cache defaults to cachedfetch({ next: { revalidate, tags } })
15.xfetch cache defaults to uncachedfetch({ cache: 'force-cache' }), unstable_cache
16.x+Cache Components'use cache' directive, cacheLife, cacheTag

-> See references/use-cache-directive.md and references/fetch-cache-options.md

"How do I cache database queries (not fetch)?"

  1. Next.js 16+: Add 'use cache' to the async function wrapping the query
  2. Next.js 15: Wrap with unstable_cache(fn, keys, { tags, revalidate })
  3. Deduplication only: Wrap with React.cache(fn) for same-render dedup without persistence -> See references/use-cache-directive.md

Decision Trees

"My page is stale" — Which Cache Layer?

SymptomLikely LayerFix
Stale after deployFull Route Cache (build-time HTML)revalidatePath or redeploy
Stale after mutation (server)Data CacherevalidateTag in Server Action
Stale on client navigationRouter Cacherouter.refresh() after mutation
Duplicate fetches in same renderRequest MemoizationWorking as intended (dedup)
Stale on hard refresh but fresh on soft navCDN/edge cacheCheck Cache-Control headers

revalidatePath vs revalidateTag

SignalUse
Data is shared across routes (products, posts)revalidateTag — follows the data
Single specific page needs refreshingrevalidatePath — targets one route
CMS publishes content used in many pagesrevalidateTag — one call, all routes
User profile page after profile editrevalidatePath('/profile')
Unclear which routes use this datarevalidateTag — decoupled from routing

Static vs Dynamic Rendering

SignalRenderingConfig
Content same for all usersStaticDefault (no config needed)
Reads cookies, headers, searchParamsDynamicAutomatic (detected)
Personalized but cacheable per-userDynamic + short revalidaterevalidate: 60
Real-time data, never cacheDynamiccache: 'no-store' or force-dynamic
Mostly static with one dynamic sectionPartial PrerenderingSuspense boundary around dynamic part

Reference Index

FileContents
references/four-cache-layers.mdRequest Memoization, Data Cache, Full Route Cache, Router Cache — how each works, duration, interaction
references/revalidation-strategies.mdTime-based revalidation, on-demand with revalidateTag/revalidatePath, ISR patterns, webhook-triggered invalidation
references/use-cache-directive.mdCache Components (Next.js 16+), use cache syntax, cacheLife profiles, cacheTag, updateTag, serialization, interleaving patterns
references/fetch-cache-options.mdfetch() cache option, next.revalidate, next.tags, unstable_cache, route segment config (dynamic, fetchCache, revalidate)
references/static-dynamic-rendering.mdStatic vs dynamic rendering triggers, Partial Prerendering, generateStaticParams, streaming + caching interaction
references/cache-debugging.mdNEXT_PRIVATE_DEBUG_CACHE, response headers, static route indicator, build output analysis, production verification
references/version-migration.mdNext.js 14 vs 15 vs 16 caching defaults, migration paths, staleTimes config, breaking changes
references/self-hosting-cdn.mdCache-Control headers, CDN integration, expireTime config, custom cache handlers, edge caching patterns

Command Palette

Search skills, docs, and navigate Tank