Skip to main content
Chapter 3 Data Fetching and Server Components

Caching Strategies in Next.js 15

24 min read Lesson 11 / 28

Understanding the Next.js 15 Caching Model

Next.js 15 made caching explicit by default. Understanding the four caching layers is essential for building apps that are both fast and up-to-date.

The Four Caching Layers

  1. Request Memoization — Deduplicates identical fetch() calls within a single render pass
  2. Data Cache — Persists fetch results across requests (the cache option controls this)
  3. Full Route Cache — Caches the rendered HTML of static routes on the server
  4. Router Cache — Client-side cache of visited route segments

Controlling the Data Cache

// No cache — always fetch fresh (Next.js 15 default)
const data = await fetch('/api/posts');
const data = await fetch('/api/posts', { cache: 'no-store' });

// Cache indefinitely (until manually invalidated)
const data = await fetch('/api/posts', { cache: 'force-cache' });

// Cache with time-based revalidation (in seconds)
const data = await fetch('/api/posts', {
    next: { revalidate: 60 }, // Revalidate every 60 seconds
});

// Cache with tag-based revalidation
const data = await fetch('/api/posts', {
    next: { tags: ['posts'] },
});

Route-Level Cache Control

Set caching for an entire route segment:

// app/blog/page.tsx

// Opt the entire route into dynamic rendering (no caching)
export const dynamic = 'force-dynamic';

// Or set a revalidation interval for the whole page
export const revalidate = 3600; // seconds

On-Demand Revalidation

Purge cached data when it changes using revalidatePath and revalidateTag:

// app/actions/posts.ts
'use server';

import { revalidatePath, revalidateTag } from 'next/cache';

export async function publishPost(postId: string) {
    await db.post.update({
        where: { id: postId },
        data: { published: true },
    });

    // Clear the blog page cache
    revalidatePath('/blog');

    // Clear all fetches tagged with 'posts'
    revalidateTag('posts');
}

Request Memoization in Practice

// Both components call getUser() but the fetch happens only once per request
// Next.js deduplicates identical fetch calls automatically

async function Header() {
    const user = await getUser(); // Fetch #1
    return <nav>{user.name}</nav>;
}

async function Sidebar() {
    const user = await getUser(); // Deduped — no extra network request
    return <aside>{user.email}</aside>;
}

This lets you fetch data exactly where you need it without worrying about over-fetching.