Fetch API & Caching

Master the extended fetch API in Next.js — how Next.js augments the native fetch with caching, revalidation, and deduplication built in.

Last reviewed: January 2025

·Next.js 13Next.js 14Next.js 15
✅
Quick Understanding: Next.js extends the native fetch API with automatic caching, deduplication, and revalidation. In Next.js 14, fetch is cached by default. In Next.js 15, fetch is NOT cached by default (breaking change). Always set explicit cache options to avoid surprises.

🔍 What Is It?

Next.js extends the native fetch API with additional options in the next namespace:

fetch(url, {
  cache: 'force-cache' | 'no-store',
  next: {
    revalidate: number,  // ISR: seconds until revalidation
    tags: string[],      // Tags for on-demand revalidation
  }
})

Caching behaviour by version:

  • →Next.js 14: fetch is cached by default (force-cache). Use cache: 'no-store' to opt out.
  • →Next.js 15: fetch is NOT cached by default. Use cache: 'force-cache' or next: { revalidate } to opt in.

Deduplication: Multiple fetch calls to the same URL within the same render pass are automatically deduplicated — the network request is made only once.

🎯 Why Do We Need It?

Without Next.js's fetch extensions, you would need to:

  • →Manually implement caching with Redis or in-memory stores
  • →Manually deduplicate requests (prevent the same API being called 3 times in one render)
  • →Manually implement ISR-style revalidation logic
  • →Manually tag cache entries for on-demand invalidation

Next.js bakes all of this into the fetch API you already know, making caching a declarative concern rather than an imperative one.

🏗️ What Happens in Real Projects?

Fetch patterns in a real CMS-driven site:

// Cached indefinitely (static)
const config = await fetch('/api/config', { cache: 'force-cache' });

// ISR: revalidate every hour
const products = await fetch('/api/products', { next: { revalidate: 3600 } });

// Always fresh (SSR)
const cart = await fetch('/api/cart', { cache: 'no-store' });

// Tagged for on-demand revalidation
const post = await fetch('/api/posts/slug', {
  next: { revalidate: 3600, tags: ['posts', 'post-slug'] }
});

⚙️ How It Works

Next.js fetch caching layers:

  1. Request deduplication — same URL + options in one render = one network request
  2. Data cache — persists across requests (ISR/SSG behaviour)
  3. Full route cache — caches the rendered HTML/RSC payload
  4. Router cache — client-side cache for navigation

These four caches work together. Understanding which cache you're controlling with each option is the key to avoiding stale data bugs.

💻 Example

Fetch Patterns for Every Scenario — The four fetch patterns you will use in every Next.js project.

ts
// lib/api.ts — centralise your fetch patterns

// 1. STATIC — cached forever, only updates on rebuild
export async function getConfig() {
  const res = await fetch('https://api.example.com/config', {
    cache: 'force-cache',
  });
  return res.json();
}

// 2. ISR — cached, revalidated every hour
export async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    next: { revalidate: 3600, tags: ['products'] },
  });
  return res.json();
}

// 3. DYNAMIC — never cached, always fresh
export async function getCartItems(sessionId: string) {
  const res = await fetch(
    `https://api.example.com/cart/${sessionId}`,
    { cache: 'no-store' }
  );
  return res.json();
}

// 4. ON-DEMAND — ISR + tagged for webhook revalidation
export async function getPost(slug: string) {
  const res = await fetch(
    `https://api.example.com/posts/${slug}`,
    { next: { revalidate: 86400, tags: ['posts', `post-${slug}`] } }
  );
  return res.json();
}

🤔 Common Confusions

❓ Does Next.js 15 cache fetch by default?

No — this is a breaking change from Next.js 14. In Next.js 15, fetch defaults to cache: "no-store" (dynamic). In Next.js 14, fetch defaults to force-cache (static). Always set explicit cache options to make your code version-agnostic and predictable.

❓ Does the extended fetch work in Client Components?

No — the Next.js fetch extensions (caching, tags, revalidate) only work in Server Components and Route Handlers. In Client Components, fetch is the standard browser fetch with no caching extensions. Use SWR or TanStack Query for client-side data fetching with caching.

❓ Can I use axios instead of fetch in Server Components?

Yes, but you lose the Next.js caching and deduplication features. Axios is a third-party library — Next.js cannot extend it. For Server Components, use the native fetch to get all the Next.js caching benefits. For Client Components, axios is fine.

🚨 Common Production Issues

Upgrading from Next.js 14 to 15 — all your cached fetch calls become dynamic (no-store) by default, causing unexpected SSR behaviour and database load spikes. Audit all fetch calls and add explicit cache options before upgrading.

Forgetting cache: "no-store" on auth endpoints — if a fetch to /api/me is cached, all users see the same user data. Always use cache: "no-store" for any user-specific endpoint.

Deduplication only works within a single render pass — it does not persist across requests. If you need cross-request caching for DB queries, use unstable_cache or React cache().

✅ Best Practices

Always set explicit cache options — never rely on defaults (they changed between versions)

Centralise fetch functions in lib/api.ts with consistent cache options

Use tags on all ISR fetch calls to enable on-demand revalidation

Use cache: "no-store" for any endpoint that returns user-specific data

Use React cache() to deduplicate DB queries across Server Components in the same request

🏛️ Architect's Note

Senior Architect's Perspective

The Next.js fetch cache is a declarative caching system — you declare the caching intent alongside the data fetch, not in a separate caching layer. This is a significant DX improvement over manually managing Redis TTLs.

However, the changing defaults between Next.js versions are a real operational risk. Establish a team convention: always explicit, never default. Every fetch call in your codebase should have an explicit cache option, documented with a comment explaining why.

💬 Frequently Asked Questions

How do I cache database queries the same way as fetch?

Use unstable_cache from next/cache. It wraps any async function with the same caching semantics as fetch: const getCachedUser = unstable_cache(async (id) => db.user.findUnique({ where: { id } }), ["user-", id], { revalidate: 3600, tags: ["users"] })

What is React cache() and when should I use it?

React cache() deduplicates function calls within a single render pass (like fetch deduplication, but for any function). Use it to wrap DB queries that might be called from multiple Server Components in the same request. Unlike unstable_cache, it does NOT persist across requests.

How do I debug what is cached and what is not?

Run next build and look at the route summary — it shows which routes are static (circle), dynamic (lambda), or ISR (clock). In development, caching is disabled by default so you see fresh data. Always test caching behaviour in production builds.

🎯 Key Takeaways

1Next.js extends native fetch with cache, revalidate, and tags options
2Next.js 15 changed the default: fetch is now no-store (dynamic) by default
3Always set explicit cache options — never rely on framework defaults
4Deduplication is automatic for same-URL fetches within one render pass
5Use unstable_cache for DB queries, React cache() for per-request deduplication
6Tags enable precise on-demand revalidation from webhooks

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Next.js references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Next.js learning path

Browse the full Next.js topic collection and learning order on SplashRide.

Next.js Interview Prep

Practice Next.js interview questions and scenario-based answers.

Frontend Engineer Career Path

A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Next.js Server Data Fetching: Async Server Components and Caching

Learn Next.js server data fetching with async Server Components, parallel and sequential requests, direct database access, caching, loading, and error handling.

Revalidation

Master Next.js revalidation — time-based, on-demand, and tag-based strategies for keeping cached content fresh without sacrificing performance.