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.
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.
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.
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.
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.
const res = await fetch('https://api.example.com/products', {
next: { tags: ['products'] },
})'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 |
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.
// 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'