Understanding Data Caching and Revalidation in Next.js App Router
Grace Collins
Solutions Engineer · Leapcell

Introduction
In the fast-paced world of web development, delivering a snappy and responsive user experience is paramount. One of the most effective ways to achieve this is through intelligent data caching. However, this powerful optimization can also introduce unexpected behaviors, leading developers to exclaim, "Why is my fetch requestcached?" This is a particularly common point of confusion when working with modern frameworks like Next.js, especially with the introduction of its App Router. Understanding how Next.js handles data caching and revalidation is crucial for building performant and predictable applications, and that's precisely what we'll explore in this article. We'll peel back the layers to reveal the sophisticated strategies Next.js employs to manage your data, ensuring you're always in control of what your users see.
Core Concepts
Before we dive into the intricacies of Next.js's caching mechanisms, let's define some fundamental terms that will be central to our discussion:
- Caching: The process of storing copies of data or files in a temporary storage location so that future requests for that data can be served faster.
 - Revalidation: The process of checking the cached data for freshness and, if necessary, fetching new data to update the cache.
 - Static Rendering (SSG - Static Site Generation): Pages are pre-rendered at build time, resulting in static HTML files served directly from a CDN. These pages are inherently cached until a new build.
 - Dynamic Rendering (SSR - Server-Side Rendering): Pages are rendered on the server at request time. This allows for dynamic data fetching, but the server still might cache the results of internal 
fetchcalls. - Server Component: A new paradigm in React (and Next.js App Router) for rendering components directly on the server, potentially reducing client-side JavaScript and improving initial page load. These components can directly 
fetchdata. - Data Cache: A storage mechanism (often in memory or on disk) used by Next.js to store the results of 
fetchrequests made on the server. - Full Route Cache: A cache that stores the complete HTML payload of a rendered route, leveraging the Data Cache underneath.
 
Next.js App Router's Data Caching and Revalidation Strategy
The Next.js App Router introduces a highly sophisticated and opinionated caching system. At its core, it intelligently caches data requests made within Server Components and other server-side contexts. This caching is not just a simple key-value store; it's deeply integrated with the rendering lifecycle and built on top of the web's fetch API.
By default, fetch requests within Server Components are assumed to be cacheable. If a fetch request is made with the default cache: 'force-cache' option (which is the default when not specified), Next.js will store the response in its Data Cache. Subsequent identical fetch requests (same URL, same options) will then be served from this cache, significantly speeding up data retrieval.
Let's illustrate with an example:
// app/page.tsx async function getPosts() { const res = await fetch('https://jsonplaceholder.typicode.com/posts'); // The 'cache: "force-cache"' option is implicit here if (!res.ok) { throw new Error('Failed to fetch data'); } return res.json(); } export default async function Page() { const posts = await getPosts(); return ( <div> <h1>Posts</h1> <ul> {posts.map((post: any) => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ); }
In this scenario, getPosts() will fetch data from the API. The first time this route is rendered, Next.js will make the actual API call and cache the response. If the user navigates to this page again within a short period, or if the server component re-renders (e.g., due to a different user, but the data is shared), Next.js will serve the data directly from its Data Cache without making another network request to jsonplaceholder.typicode.com.
When does the cache get invalidated or revalidated?
This is where the flexibility of Next.js shines. There are several ways to manage revalidation:
- 
Time-based Revalidation (
next.revalidate): You can tell Next.js to revalidate the cache for a specificfetchrequest after a certain amount of time using thenext.revalidateoption.async function getPosts() { const res = await fetch('https://jsonplaceholder.typicode.com/posts', { next: { revalidate: 60 }, // Revalidate every 60 seconds }); if (!res.ok) { throw new Error('Failed to fetch data'); } return res.json(); }With
revalidate: 60, after 60 seconds of the data being cached, the next request will trigger a background revalidation. The stale data will be served immediately, and Next.js will quietly fetch the new data in the background to update the cache for subsequent requests. - 
Explicit Cache Invalidation (
cache: 'no-store'): If you absolutely do not want afetchrequest to be cached, you can explicitly opt out:async function getLiveStockPrices() { const res = await fetch('https://api.example.com/stock-prices', { cache: 'no-store', // Always fetch fresh data }); if (!res.ok) { throw new Error('Failed to fetch data'); } return res.json(); }This is ideal for highly dynamic data that changes frequently and where showing stale data is unacceptable (e.g., real-time stock quotes, user-specific shopping cart contents).
 - 
Opting out of Caching for a Route Segment: You can configure an entire route segment to be dynamic, meaning all
fetchrequests within that segment will default tono-storeand the route itself will be rendered dynamically. This is done by exporting adynamicoption from alayout.tsxorpage.tsxfile:// app/dashboard/page.tsx or app/dashboard/layout.tsx export const dynamic = 'force-dynamic'; // This will make all fetches in this segment behave as 'no-store'Other options include
'auto'(default),'error', and'force-static'. Settingdynamic = 'force-dynamic'is a powerful way to ensure freshness across a whole part of your application. - 
On-Demand Revalidation (
revalidatePath,revalidateTag): For data that changes based on external events (e.g., a CMS update, an e-commerce order), you can trigger revalidation programmatically.revalidatePath(path: string): Revalidates the data cache for a specific path.revalidateTag(tag: string): Revalidates allfetchrequests that have been tagged with a specific string.
To use
revalidateTag, you must tag your fetch requests:// app/products/page.tsx async function getProducts() { const res = await fetch('https://api.example.com/products', { next: { tags: ['products'] }, // Tag this fetch request }); if (!res.ok) { throw new Error('Failed to fetch products'); } return res.json(); } // api/revalidate-products.ts (an API route for triggering revalidation) import { NextRequest, NextResponse } from 'next/server'; import { revalidateTag } from 'next/cache'; export async function GET(request: NextRequest) { const tag = request.nextUrl.searchParams.get('tag'); // e.g., ?tag=products if (tag) { revalidateTag(tag); return NextResponse.json({ revalidated: true, now: Date.now() }); } return NextResponse.json({ revalidated: false, message: 'Missing tag' }); }This allows you to build webhooks or admin interfaces to programmatically clear specific cache entries, providing immediate updates without rebuilding your entire application.
 
Understanding Cache Invalidation Triggers
It's important to note the triggers for Next.js's data cache invalidation:
- Deployment: A new deployment of your Next.js application will clear all data caches.
 next.revalidateexceeding its time: As discussed, this leads to background revalidation.- On-demand revalidation: 
revalidatePathorrevalidateTagare explicitly called. - Development Mode: In development mode (
npm run dev), Next.js generally performs less aggressive caching to ensure you always see the latest changes immediately. You might not observe the same caching behavior as in production. 
Conclusion
The "why is my fetch cached" conundrum in Next.js App Router is a testament to its powerful, yet sometimes invisible, optimization strategies. By understanding the default caching behavior of fetch within Server Components, and mastering the tools for revalidation such as next.revalidate, cache: 'no-store', export const dynamic, and on-demand revalidation functions, developers can precisely control data freshness and deliver highly performant user experiences. Effectively leveraging Next.js's data caching and revalidation mechanisms is key to building fast, dynamic, and reliable web applications.

