Next.js extends the browser's native fetch() function with extra options for controlling caching and revalidation. This lesson focuses specifically on that extended API and its most important options.
fetch() with Next.js-Specific Options
Calling fetch() inside a Server Component works exactly like the standard Web API you already know, but Next.js augments it with a second, Next.js-specific options object (nested under a next key) that controls how the result is cached and when it should be considered stale.
By default, fetch() requests in Next.js are cached indefinitely at build/request time unless you explicitly opt out — this is one of the most surprising behaviors for newcomers coming from plain React, where every fetch() call is always a fresh network request.
cache: "force-cache" caches the response and reuses it for subsequent identical requests.
cache: "no-store" disables caching entirely for that specific request.
next.revalidate sets a time (in seconds) after which the cached result becomes stale and refetches.
next.tags assigns cache tags you can later invalidate on demand with revalidateTag().
fetch() Caching Cheat Sheet
Which option to reach for based on how fresh your data needs to be.
Goal
Option to Use
Cache indefinitely, rarely-changing data
Default fetch() behavior, or cache: "force-cache"
Always fresh, per-request data
cache: "no-store"
Refresh automatically every N seconds
next: { revalidate: N }
Refresh only when explicitly told to
next: { tags: [...] } + revalidateTag()
Force a route to always be dynamic
export const dynamic = "force-dynamic";
Choosing Between revalidate and no-store
no-store is the right choice for data that must always be current on every request — like a live stock price or a personalized dashboard. revalidate is better for content that changes occasionally, like a blog post or product listing, where serving slightly stale data for a short window is an acceptable trade-off for much better performance.
On-Demand Revalidation with Tags
Tagging a fetch call lets you invalidate its cached result on demand — for example, immediately after a Server Action updates the underlying data — rather than waiting for a fixed time interval to pass.
// Tagging a fetch call
const res = await fetch("https://api.example.com/posts", {
next: { tags: ["posts"] },
});
// Elsewhere, after a mutation:
import { revalidateTag } from "next/cache";
revalidateTag("posts");
Any cached fetch() result tagged "posts" is invalidated immediately, without waiting for its revalidate timer.
Common Mistakes
Being surprised that fetch() returns stale data, without realizing Next.js caches it by default in many cases.
Using no-store everywhere out of caution, losing the performance benefits of caching for content that rarely changes.
Forgetting next.tags and revalidateTag() must use the exact same tag string to match.
Not knowing export const dynamic = "force-dynamic" exists when a whole route needs to opt out of caching entirely.
Key Takeaways
Next.js extends fetch() with a next options object for caching and revalidation.
next.revalidate sets a time-based staleness window for Incremental Static Regeneration.
next.tags combined with revalidateTag() enables on-demand cache invalidation.
Understanding these defaults is essential — unexpected caching is a very common source of confusion.
Pro Tip
When a page seems to be showing outdated data during development, check its fetch() calls for missing cache options before assuming there's a bug elsewhere — Next.js's fetch caching is powerful but easy to overlook when you're used to plain browser fetch() behavior.
You now understand fetch() caching options. Next, learn about static rendering — the rendering strategy that pairs naturally with cached fetches.