RSC Caching Strategies — Optimizing Data Fetching Performance
In this tutorial, you will learn about RSC Caching Strategies. We cover key concepts, practical examples, and best practices to help you master this topic.
Caching in React Server Components reduces server load and improves response times by storing fetched data and rendered output across requests.
What You'll Learn
You will understand the Next.js caching layers (Data Cache, Full Route Cache, Router Cache), how to configure each, and patterns for cache invalidation.
Why It Matters
Proper caching reduces database load, improves page load times, and lowers server costs. Incorrect caching serves stale data or misses optimization opportunities.
Real-World Use
DodaZIP caches file metadata queries with a 30-second revalidation, ensuring the file browser stays fresh without hitting the database on every request.
flowchart TD
A[Incoming Request] --> B{Full Route Cache}
B -->|Hit| C[Serve Cached HTML]
B -->|Miss| D{Data Cache}
D -->|Hit| E[Render from cached data]
D -->|Miss| F[Fetch fresh data]
F --> G[Cache data]
E --> H[Render HTML]
H --> I[Cache rendered page]
I --> C
style B fill:#1e293b,color:#fff
style D fill:#1e293b,color:#fff
style C fill:#0f172a,color:#fff
The Data Cache
The Data Cache stores the result of fetch requests. It is enabled by default for fetch calls.
// Cached indefinitely (until revalidated)
async function StaticPage() {
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache'
});
return <Content data={await data.json()} />;
}
// Revalidated every 60 seconds
async function SemiDynamicPage() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }
});
return <Content data={await data.json()} />;
}
// Never cached
async function DynamicPage() {
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
return <Content data={await data.json()} />;
}
Expected output: StaticPage serves cached data indefinitely. SemiDynamicPage serves cached data but re-fetches every 60 seconds. DynamicPage always fetches fresh data.
Revalidation with revalidateTag
Tag-based revalidation lets you invalidate specific cache entries.
// Fetch with a tag
async function ProductPage({ params }) {
const product = await fetch(`https://api.example.com/products/${params.id}`, {
next: { tags: [`product-${params.id}`, 'products'] }
});
return <Product product={await product.json()} />;
}
// Server Action that revalidates by tag
async function updateProduct(formData) {
'use server';
const id = formData.get('id');
await db.products.update(id, formData);
revalidateTag(`product-${id}`);
revalidateTag('products');
return { success: true };
}
Expected output: When a product is updated via the Server Action, the specific product cache entry and the general products list cache are both invalidated. The next request fetches fresh data.
Time-Based Revalidation
Use revalidatePath for page-level revalidation and revalidateTag for data-level revalidation.
async function createPost(formData) {
'use server';
await db.posts.create(formData);
// Option 1: Revalidate a specific path
revalidatePath('/posts');
revalidatePath('/');
// Option 2: Revalidate by tag (if fetch uses tags)
revalidateTag('posts');
return { success: true };
}
Expected output: After creating a post, both the /posts listing page and the home page revalidate. The next visit shows the new post.
Full Route Cache
Next.js caches rendered HTML of static routes at build time. Dynamic routes are not cached unless configured.
// next.config.js
module.exports = {
// Dynamic routes that should be statically cached
experimental: {
serverComponentsExternalPackages: ['mongoose'],
},
};
// Force static generation for a page
export const dynamic = 'force-static';
export default async function AboutPage() {
const content = await db.content.findBySlug('about');
return <Article content={content} />;
}
// Opt out of caching for dynamic data
export const dynamic = 'force-dynamic';
export default async function UserDashboard() {
const user = await getCurrentUser();
return <Dashboard user={user} />;
}
Expected output: Static pages are pre-rendered and cached at build time. Dynamic pages render on every request. force-static makes a page static even if it uses dynamic functions.
Request Memoization
React automatically deduplicates fetch requests within the same render pass.
// Both components fetch the same data — React deduplicates
async function Header() {
const user = await getCurrentUser(); // First call
return <header>Welcome {user.name}</header>;
}
async function Sidebar() {
const user = await getCurrentUser(); // Same call — deduplicated
return <aside>User menu for {user.name}</aside>;
}
export default async function Page() {
return (
<div>
<Header />
<Sidebar />
</div>
);
}
Expected output: Even though two components call getCurrentUser, React deduplicates the request. The database is queried once and the result is shared within the same render pass.
Common Mistakes
Over-caching with force-cache: Not all data should be cached indefinitely. Dynamic data needs no-store or short revalidation periods.
Forgetting to revalidate after mutations: After creating, updating, or deleting data, always call revalidatePath or revalidateTag to update the cache.
Using revalidatePath without specifying the exact path: If the path pattern is wrong, revalidation does not work. Use the exact route path.
Not using tags for granular cache control: Path-based revalidation invalidates everything on that page. Tag-based revalidation targets specific data.
Assuming the Router Cache is the same as the Data Cache: The Router Cache stores rendered pages in the browser during navigation. It is separate from the server-side Data Cache.
Practice Questions
- What are the three caching layers in Next.js?
Data Cache (fetch results), Full Route Cache (rendered HTML), and Router Cache (client-side navigation cache).
- How do you opt out of caching for a specific fetch?
Use
cache: 'no-store'in the fetch options. This bypasses the Data Cache and fetches fresh data on every request.
- What is the difference between revalidatePath and revalidateTag?
revalidatePath invalidates the cache for a specific route. revalidateTag invalidates all cached data with a specific tag.
- How does request memoization work?
React deduplicates identical fetch requests within the same render pass. Multiple components fetching the same URL only trigger one request.
- When would you use force-dynamic?
For pages with user-specific data, real-time updates, or data that changes faster than the minimum revalidation period.
Challenge
Set up a caching Strategy for a blog with: static archive pages (force-cache, revalidate weekly), recent posts (revalidate every 60s), and individual post pages (revalidate on tag when post is updated via Server Action).
Frequently Asked Questions
Mini Project
Build a product catalog with three caching tiers: product listing (static, revalidated daily), product details (revalidated on update via tag), and inventory counts (no cache, always fresh). Include Server Actions that revalidate the appropriate cache entries.
What's Next
Learn about RSC Routing to understand how routing interacts with Server Components.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro