The 4 Cache Layers in Next.js: The Mental Model That Finally Clicks

Programming· 6 min read

The 4 Cache Layers in Next.js: The Mental Model That Finally Clicks

I'd been building a Next.js application for three months when I realized something: I was optimizing cache completely wrong.

I had data that needed to refresh every hour. Others that should be served instantly. Some that only certain users should see. And I had no idea which of the four cache layers I should use in each case.

The official Next.js documentation is good, but it's written like a technical reference. What I needed was a simple mental model that connected everything.

Today I'm sharing exactly how it works.

Why Cache Matters

Before diving into technical details: cache is the difference between an application that takes 2 seconds to load and one that takes 200 milliseconds.

It's the difference between paying $50/month in infrastructure and $500/month.

It's the difference between a satisfied user and one who closes your site.

But here's the problem: Next.js doesn't have *one* caching system. It has *four*, and each does different things at different times.

The 4 Levels Explained (Top to Bottom)

1. Request Memoization (In Memory, During the Request)

This is the simplest but most misunderstood.

What does it do? If you call the same function twice in the same HTTP request, Next.js only executes it once.

Where does it work? Only on the server, during page rendering.

How long does it last? While that request is being processed. When it finishes, it forgets.

Real example:

```javascript // app/page.js async function getUserData(id) { console.log('Fetching user data...'); const res = await fetch(`https://api.example.com/users/${id}`); return res.json(); }

export default async function Page() { // This calls getUserData twice const user = await getUserData(1); const userAgain = await getUserData(1);

// But it only makes one API request // "Fetching user data..." appears ONCE in the logs

return <div>{user.name}</div>; } ```

Next.js uses `fetch()` with automatic memoization. If you use another library like `axios`, you don't get this benefit.

Use case: When multiple components need the same data on the same page.

---

2. Data Cache (On the Server, Between Requests)

This is where most of the magic happens.

What does it do? It stores the results of `fetch()` between HTTP requests. If someone visits your page at 10:00 and another person at 10:05, the second person gets the same cached result.

Where does it work? On the Vercel server (or your server, if you deploy elsewhere).

How long does it last? Indefinitely, until you manually invalidate it.

Example:

```javascript // app/posts/page.js async function getPosts() { const res = await fetch('https://api.example.com/posts', { next: { revalidate: 3600 } // Revalidate every hour }); return res.json(); }

export default async function PostsPage() { const posts = await getPosts(); return ( <div> {posts.map(post => ( <article key={post.id}>{post.title}</article> ))} </div> ); } ```

The first person who loads this page makes a real request to your API. The next 3600 people (during the next hour) get the cached result without touching your API.

Use case: Data that changes slowly (blog posts, product information, configuration).

---

3. Full Route Cache (Pre-rendered Static HTML)

This is the one that really speeds everything up.

What does it do? Next.js pre-renders your entire page to static HTML at build time and serves it as-is.

Where does it work? On the global CDN (if you use Vercel) or on your static server.

How long does it last? Until you redeploy your application or manually invalidate the route.

Example:

```javascript // app/about/page.js export default function AboutPage() { return ( <div> <h1>About Us</h1> <p>This page is rendered ONCE at build time.</p> </div> ); } ```

When you run `npm run build`, Next.js generates a static HTML file for this page. When someone requests it, it serves the pre-compiled HTML directly from the CDN. No Node.js server involved.

Response time: <50ms from anywhere in the world (if you use Vercel).

Use case: Static pages (About, Pricing, Landing pages). Anything that doesn't change frequently.

---

4. Router Cache (In the Browser)

This is the newest and least understood.

What does it do? When you navigate between pages in your Next.js application, the browser caches the route segments you've already visited.

Where does it work? In the user's browser memory.

How long does it last? While the user is on your application. It clears when they close the tab.

Example:

```javascript // app/layout.js 'use client';

import Link from 'next/link';

export default function Layout({ children }) { return ( <> <nav> <Link href="/">Home</Link> <Link href="/products">Products</Link> <Link href="/about">About</Link> </nav> {children} </> ); } ```

When you click "Products", Next.js loads that page. When you click "About", it loads that page. If you click "Products" again, it doesn't make another server request. It uses what it cached in the browser.

Use case: Fast navigation within your application. The experience feels like a native app.

---

The Unified Mental Model

Here's the key to understanding all of this:

``` ┌─────────────────────────────────────┐ │ User clicks on a link │ └────────────┬────────────────────────┘ │ ▼ Is it in Router Cache? (User's browser) │ ┌──────┴──────┐ │ YES │ NO ▼ ▼ Serve from Is it in Full Route Cache? browser (CDN/Static server) │ ┌────┴────┐ │ YES │ NO ▼ ▼ Serve from Is it in Data Cache? CDN (Server) │ ┌────┴────┐ │ YES │ NO ▼ ▼ Serve from Real fetch server to your API/DB ```

Practical Example: A Real Blog

I have a blog where I publish articles. Here's how I cache different things:

  • **Published articles:** Change every week. I use `revalidate: 86400` (24 hours) on Data Cache.
  • **Home page:** Shows the latest 5 articles. Full Route Cache with ISR (Incremental Static Regeneration).
  • **Contact page:** Never changes. Full Route Cache, no revalidation.
  • **User dashboard:** Personal data that changes constantly. No cache, or very short cache.

```javascript // app/blog/[slug]/page.js export async function generateStaticParams() { // Pre-render all articles at build time const posts = await fetch('https://api.example.com/posts').then(r => r.json()); return posts.map(post => ({ slug: post.slug })); }

async function getPost(slug) { const res = await fetch(`https://api.example.com/posts/${slug}`, { next: { revalidate: 86400 } // Revalidate every 24 hours }); return res.json(); }

export default async function PostPage({ params }) { const post = await getPost(params.slug); return <article>{post.content}</article>; } ```

The Practical Conclusion

You don't need to understand Next.js's internal details. You just need to know:

1. Request Memoization: Used automatically if you call `fetch()` twice. 2. Data Cache: Configure with `next: { revalidate: seconds }` in your `fetch()`. 3. Full Route Cache: Happens automatically on static pages. Use ISR for revalidation. 4. Router Cache: Happens automatically in the browser when you use `<Link>`.

Most performance problems I see come from not using these layers correctly. People doing `fetch()` without `revalidate`, or using dynamic pages when they could be static.

Next time you optimize a page, ask yourself: Which layer should this data be in?

The right answer will save you thousands in infrastructure.