NextjsOpting Out of Caching

Opting Out of Caching

Next.js caches aggressively by default, at three different layers: the Data Cache (individual fetch() results), the Full Route Cache (rendered output of static routes), and the client-side Router Cache (prefetched/visited segments in the browser). Most of the time this is exactly what you want. But some routes genuinely need fresh data on every single request — a dashboard, a stock ticker, an admin panel — and Next.js gives you a specific opt-out for each layer.

Warning
Opting out of caching is not free. Every layer you disable moves work back onto the server (or the network) for every single request. Treat these as targeted tools for routes that truly need it, not defaults to reach for everywhere.
Opting out at the Data Cache layer

By default, fetch() requests inside a Server Component are cached indefinitely. Pass cache: 'no-store' to skip the Data Cache for that specific request:

TSX
async function getLiveStats() {
  const res = await fetch('https://api.example.com/stats', {
    cache: 'no-store',
  })
  return res.json()
}

export default async function Dashboard() {
  const stats = await getLiveStats()
  return <StatsPanel data={stats} />
}

This is the most surgical option — it only affects the one fetch() call, leaving every other request on the page free to be cached normally.

Opting out at the route level

Sometimes an entire route needs to be dynamically rendered on every request — for example if it reads cookies() to render per-user content, or you simply don't want Next.js to attempt static optimization. The route segment config does this:

TSX
// app/dashboard/page.tsx
export const dynamic = 'force-dynamic'

export default async function DashboardPage() {
  // Every fetch() here is re-run on every request,
  // and the route is excluded from the Full Route Cache.
  const data = await getLiveStats()
  return <StatsPanel data={data} />
}
Note
force-dynamic affects the whole route, including every fetch() inside it and any nested Server Components it renders — it is a bigger hammer than cache: 'no-store' on a single call.
Opting out on the client

The Router Cache lives in the browser and is unaffected by anything you set on the server. To force the current view to discard its cached segments and refetch, call router.refresh() from a Client Component:

TSX
'use client'

import { useRouter } from 'next/navigation'

export default function RefreshButton() {
  const router = useRouter()
  return <button onClick={() => router.refresh()}>Refresh data</button>
}
Which technique addresses which layer

Technique

Layer it affects

Scope

fetch(url, { cache: 'no-store' })

Data Cache

A single fetch() call

export const dynamic = 'force-dynamic'

Full Route Cache + Data Cache

The entire route

router.refresh()

Client-Side Router Cache

The current view, client-side only

A middle ground: time-based revalidation
Full opt-out isn't the only option. If data just needs to be reasonably fresh rather than perfectly live, export const revalidate on a route or a fetch(url, { next: { revalidate: 60 } }) call lets Next.js keep serving cached output while regenerating it in the background at most once per interval — you get most of the performance of caching without stale data lingering indefinitely.
  • Use no-store / force-dynamic for data that must never be stale, even for a second.

  • Use time-based revalidate for data that can tolerate being a little behind.

  • Use on-demand revalidatePath() / revalidateTag() when a mutation should immediately invalidate specific cached content.

Tip
Before reaching for force-dynamic, check whether the actual requirement is "this needs to be live" or just "this needs to reflect the latest write." The second case is usually better solved with revalidateTag() triggered from the mutation itself, keeping the rest of the route statically cacheable.