NextjsThe Extended fetch() API

The Extended fetch() API

Next.js ships a modified version of the native fetch() Web API. It looks and behaves like the fetch you already know, but the App Router's server-side rendering engine automatically extends it with caching and revalidation semantics — configured through an extra options object you pass as the second argument.

Note
This behavior is specific to Next.js. If you fetch from a Client Component, or from anywhere outside the Next.js server runtime, you get the plain browser fetch with none of the extra caching options described below.
cache: 'force-cache' — cache aggressively

force-cache tells Next.js to check the Data Cache first and reuse a stored response if one exists, only hitting the network if there is no cached entry yet. In the App Router, this is actually the default for fetch requests in statically rendered routes, so you rarely need to write it explicitly — but being explicit can make intent clearer.

TSX
const res = await fetch('https://api.example.com/plans', {
  cache: 'force-cache',
})
cache: 'no-store' — never cache

no-store tells Next.js to skip the Data Cache entirely and fetch fresh data on every single request. Using this option on a fetch call inside a route also makes that route render dynamically at request time, because Next.js can no longer produce one static HTML result ahead of time.

TSX
const res = await fetch('https://api.example.com/live-price', {
  cache: 'no-store',
})
next: { revalidate: N } — time-based ISR

Passing a revalidate number inside a next object caches the response, but only for up to N seconds. After that window elapses, the next request triggers a background regeneration while still serving the (briefly) stale cached response — the same stale-while-revalidate behavior covered on the ISR page.

TSX
const res = await fetch('https://api.example.com/articles', {
  next: { revalidate: 60 }, // regenerate at most once every 60 seconds
})
next: { tags: [...] } — tag-based invalidation

Attaching one or more string tags to a fetch call lets you invalidate that cached data on demand from anywhere in your app, by name, using revalidateTag — without needing to know the specific path the data was fetched on.

TSX
const res = await fetch('https://api.example.com/products', {
  next: { tags: ['products'] },
})

TSX
'use server'

import { revalidateTag } from 'next/cache'

export async function updateProduct() {
  await saveProductChanges()

  // Invalidates every fetch anywhere in the app tagged 'products'
  revalidateTag('products')
}
Summary

Option

Behavior

Rendering effect

Typical use case

cache: 'force-cache'

Reuse cached response if available (default for static routes)

Static

Content that rarely changes

cache: 'no-store'

Always fetch fresh, never cache

Dynamic

Live/user-specific data

next: { revalidate: N }

Cache for N seconds, then regenerate in the background

Static with ISR

Blog posts, product listings

next: { tags: [...] }

Cache indefinitely until explicitly invalidated by tag

Static, invalidated on demand

Data mutated via Server Actions

Tip
revalidate and tags are not mutually exclusive — you can combine next: { revalidate: 3600, tags: ['products'] } to get both a time-based safety net and a precise on-demand invalidation hook.
Route segment config vs. per-fetch config

The same cache and revalidate concepts can also be set once for an entire route segment by exporting a revalidate or dynamic constant from a page or layout file, instead of repeating options on every fetch call.

TSX
// Applies to every fetch in this route segment that doesn't set its own option
export const revalidate = 3600
export const dynamic = 'force-dynamic' // or 'force-static', 'auto'
Warning
If a route segment config and a per-fetch option disagree, the more restrictive (more dynamic) setting generally wins. Avoid mixing them carelessly — it makes caching behavior hard to reason about.