stackademic

The leading education platform for anyone with an interest in software development.

Caching

Control the Data Cache and Full Route Cache with revalidation and tags

Overview

The App Router layers several caches: the Request Memoization cache dedupes fetch calls per render, the Data Cache persists fetch results across requests, and the Full Route Cache stores rendered routes. Understanding when each applies — and how to invalidate them with time-based or on-demand revalidation — is key to serving fresh data without sacrificing performance.

Syntax / Usage

Configure caching per fetch, or invalidate on demand with revalidatePath and revalidateTag.

// Time-based revalidation (ISR): refresh at most every 60s
const res = await fetch('https://api.example.com/feed', {
  next: { revalidate: 60 },
})

// Tag a request so it can be invalidated by name
const products = await fetch('https://api.example.com/products', {
  next: { tags: ['products'] },
})
'use server'
import { revalidateTag } from 'next/cache'

export async function updateProduct(id: string, data: ProductInput) {
  await db.product.update({ where: { id }, data })
  revalidateTag('products') // purge all fetches tagged 'products'
}

Examples

Cache an expensive non-fetch computation with unstable_cache:

import { unstable_cache } from 'next/cache'

const getTopAuthors = unstable_cache(
  async () => db.author.findMany({ orderBy: { sales: 'desc' }, take: 10 }),
  ['top-authors'],
  { revalidate: 3600, tags: ['authors'] }
)

Force a route to be dynamic, disabling the Full Route Cache:

// app/dashboard/page.tsx
export const dynamic = 'force-dynamic'
// or opt a specific fetch out: fetch(url, { cache: 'no-store' })

Common Mistakes

  • Expecting revalidatePath to update data across every deployment region instantly
  • Mixing cache: 'no-store' with next: { revalidate } on the same fetch
  • Forgetting that database queries are not cached in the Data Cache unless wrapped
  • Assuming a page is dynamic when a cached fetch silently makes it static
  • Over-tagging or forgetting to call revalidateTag after a mutation, serving stale data

See Also

nextjs-data-fetching nextjs-route-handlers nextjs-server-actions