Caching in Next.js

Caching in Next.js is one of the most complex and frequently asked topics in interviews. Next.js uses four levels of caching, each operating at its own stage.

Four Cache Levels

1

Request Memoization (server, during rendering)

Deduplication of identical fetch requests within a single render pass. If multiple components request the same data, the request runs once.

2

Data Cache (server, across requests)

Caches fetch request results across user requests and deployments. Works like a CDN for data.

3

Full Route Cache (server, across requests)

Caches rendered HTML and RSC Payload of static routes at build time.

4

Router Cache (client, during session)

Caches RSC Payload of visited routes in the browser. Back navigation is instant.

Request Memoization

React automatically memoizes fetch requests with the same URL and parameters:

// Both components call the same fetch
// The request runs only once
async function Header() {
  const user = await fetch('/api/user').then(r => r.json())
  return <div>{user.name}</div>
}

async function Sidebar() {
  const user = await fetch('/api/user').then(r => r.json())
  return <div>{user.avatar}</div>
}

This only works during a single render pass on the server. For ORM queries (Prisma, Drizzle) use React.cache:

import { cache } from 'react'
import { db } from '@/lib/db'

export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } })
})

Data Cache

By default fetch in Next.js caches the result in the Data Cache:

// Cached indefinitely (until revalidation)
const res = await fetch('https://api.hackfrontend.com/docs')

// Revalidate after 5 minutes
const res = await fetch('https://api.hackfrontend.com/problems', {
  next: { revalidate: 300 }
})

// No caching
const res = await fetch('https://api.hackfrontend.com/feed', {
  cache: 'no-store'
})

Revalidation

Two ways to update the Data Cache:

Time-based: data is considered stale after N seconds

const res = await fetch(url, { next: { revalidate: 60 } })

Event-based: explicit invalidation via tags

// When fetching
const res = await fetch(url, { next: { tags: ['problems'] } })

// When updating data (in a Server Action)
import { revalidateTag } from 'next/cache'
revalidateTag('problems')

Full Route Cache

Static routes are cached entirely at build time. This cache includes HTML and RSC Payload.

A route is static if it:

  • Does not use dynamic functions (cookies(), headers(), searchParams)
  • All fetch requests are cached

A route is dynamic if it:

  • Uses cookies(), headers() or searchParams
  • Has a fetch with cache: 'no-store'
  • Sets export const dynamic = 'force-dynamic'

Router Cache

The browser caches RSC Payload of visited pages. This enables:

  • Instant back navigation to previous pages
  • Prefetching pages when hovering over Link
import Link from 'next/link'

// Next.js automatically prefetches this page
<Link href="/problems">Problems</Link>

// Prefetching can be disabled
<Link href="/problems" prefetch={false}>Problems</Link>

Interview tip:

Router Cache often causes confusion. A page may show stale data after a mutation because the client cache has not updated yet. Solution: call router.refresh() or revalidatePath() in a Server Action.

Disabling Caching

To completely disable caching for a segment:

// app/dashboard/layout.tsx
export const dynamic = 'force-dynamic'
export const revalidate = 0

Useful Resources