export const fetchCache

Overrides the default cache option applied to every fetch request inside a route segment.

Signature
export const fetchCache = 'auto'
// 'auto' | 'default-cache' | 'only-cache'
// 'force-cache' | 'force-no-store' | 'default-no-store' | 'only-no-store'
  • Removed in PHP v16.0.0 (when cacheComponents is enabled)

Parameters

ParameterTypeDescription
$'auto'optionalstring literal= 'auto'Default. Caches fetch requests before Request-time APIs with the cache option they provide, and does not cache fetch requests after Request-time APIs.
$'default-cache'optionalstring literalAllows any cache option to be passed, but when none is provided sets cache: 'force-cache'. Even fetches after a Request-time API are then considered static.
$'only-cache'optionalstring literalChanges the default to cache: 'force-cache' and errors if any fetch uses cache: 'no-store'.
$'force-cache'optionalstring literalSets the cache option of every fetch request to 'force-cache', overriding what the call site asked for.
$'default-no-store'optionalstring literalAllows any cache option to be passed, but when none is provided sets cache: 'no-store'. Even fetches before a Request-time API are then considered dynamic.
$'only-no-store'optionalstring literalChanges the default to cache: 'no-store' and errors if any fetch uses cache: 'force-cache'.
$'force-no-store'optionalstring literalSets the cache option of every fetch request to 'no-store', re-fetching on every request even when the call site passed 'force-cache'.

Errors

Incompatible combinations across a single route error: 'only-cache' together with 'only-no-store', 'force-cache' together with 'force-no-store', and a parent providing 'default-no-store' while a child provides 'auto' or a '*-cache' value.

Changelog

  • PHP v16.0.0dynamic, dynamicParams, revalidate and fetchCache are removed when Cache Components (the cacheComponents flag) is enabled.

The Next.js docs file this one under an advanced disclosure for a reason: it rewrites the cache option of fetch calls you did not write, including calls inside libraries and shared data-access modules.

The three families

  • default-* only fills in a default when the call site passed nothing.
  • only-* fills in the default and errors when a call site asks for the opposite — a compile-time assertion that the whole route is one way or the other.
  • force-* overwrites the call site outright, silently.

What trips people up

Options set across the layouts and pages of a single route must be compatible with each other. Mixing 'only-cache' and 'only-no-store' in one route is not allowed, nor is mixing 'force-cache' and 'force-no-store'. When both an only- and a matching force- value appear, the force- value wins and suppresses the error the only- value would have raised.

The other surprise is inheritance: a parent layout at 'default-no-store' conflicts with a child at 'auto' or any *-cache value, because the same fetch would behave differently depending on where it ran. Leave shared parent layouts on 'auto' and set the option only where child segments genuinely diverge.

Example

Example · typescript
export const fetchCache = 'only-cache'

export default async function ReportsLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const res = await fetch('https://api.example.com/report-metadata')
  const meta: { generatedFor: string } = await res.json()

  return (
    <section>
      <header>{meta.generatedFor}</header>
      {children}
    </section>
  )
}

When to use it

  • Assert that every request in a reporting route is cached by setting fetchCache = 'only-cache' and letting the build fail on a stray no-store.
  • Force an entire admin section to skip the data cache without editing each fetch call.
  • Cache fetches that happen after a cookies() call by setting fetchCache = 'default-cache' on the segment.

Frequently asked questions

What is the difference between only-cache and force-cache?

only-cache changes the default to 'force-cache' and errors if a fetch explicitly uses cache: 'no-store', whereas force-cache silently rewrites every fetch to 'force-cache' regardless of what the call site asked for.

Why am I getting a fetchCache conflict error between my layout and my page?

Because the values across a single route must be compatible: 'only-cache' with 'only-no-store' and 'force-cache' with 'force-no-store' are not allowed together, and a parent setting 'default-no-store' conflicts with a child setting 'auto' or any *-cache value.

Do I need fetchCache if I already set dynamic on the segment?

No — dynamic = 'force-dynamic' already implies fetchCache = 'force-no-store' and dynamic = 'error' already implies fetchCache = 'only-cache', so setting both is redundant.

More examples

force-no-store overrides an explicit force-cache

The explicit cache: 'force-cache' on the fetch is discarded. This is the only fetchCache family that overrides the call site rather than supplying a default, which is what makes it useful for locking a route down and dangerous to leave in a shared layout.

Example · typescript
// app/admin/page.tsx
export const fetchCache = 'force-no-store'

export default async function AdminPage() {
  // The call site asks to be cached...
  const res = await fetch('https://api.example.com/users', {
    cache: 'force-cache',
  })

  // ...but fetchCache = 'force-no-store' rewrites it to 'no-store',
  // so this is re-fetched on every request.
  const users: { id: string; email: string }[] = await res.json()

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.email}</li>
      ))}
    </ul>
  )
}

A combination the build rejects

The intent of the only-* options is to guarantee the whole route is either fully static or fully dynamic, so 'only-cache' and 'only-no-store' cannot coexist in one route. The same restriction applies to 'force-cache' together with 'force-no-store'.

Example · typescript
// app/mixed/layout.tsx
export const fetchCache = 'only-cache'

export default function MixedLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <div>{children}</div>
}

// app/mixed/page.tsx
// Not allowed in the same route as 'only-cache' above.
export const fetchCache = 'only-no-store'

export default async function MixedPage() {
  const res = await fetch('https://api.example.com/items')
  return <pre>{JSON.stringify(await res.json())}</pre>
}

Caching fetches that happen after cookies()

default-cache changes only the default for fetches that pass no cache option, so a fetch discovered after cookies() becomes cacheable. Any fetch that passes its own cache option keeps it.

Example · typescript
// app/(dash)/summary/page.tsx
import { cookies } from 'next/headers'

export const fetchCache = 'default-cache'

export default async function SummaryPage() {
  const cookieStore = await cookies()
  const region = cookieStore.get('region')?.value ?? 'eu'

  // With 'auto' this fetch would not be cached, because it is
  // discovered after a Request-time API. 'default-cache' gives it
  // cache: 'force-cache' since no cache option was passed.
  const res = await fetch(`https://api.example.com/summary/${region}`)
  const summary: { total: number } = await res.json()

  return <p>{summary.total}</p>
}

Discussion

  • Be the first to comment on this lesson.