# The Ultimate Guide to Next.js Caching (Part 1): Browser Cache, CDN, Data Cache & unstable_cache

When someone opens your website, does Next.js immediately query your database?

**Not always.**

A request may pass through **multiple cache layers** before your database is even touched.

The more requests served from cache, the:

*   ⚡ Faster your app
    
*   💰 Lower your server costs
    
*   📈 Better your scalability
    

Think of caching as:

> **The fastest database query is the one that never runs.**

* * *

# The Complete Cache Flow

A typical Next.js request looks something like this:

```text
Browser
    │
Browser Cache
    │
Cloudflare / Vercel CDN
    │
Next.js Router Cache
    │
Full Route Cache
    │
Data Cache
    │
unstable_cache / use cache
    │
Database
```

Let's understand what each layer does.

* * *

# 1\. Browser Cache

This is the **first cache** checked.

If your browser already has the file, **no network request is made**.

Typical cached assets:

*   Images
    
*   CSS
    
*   JavaScript
    
*   Fonts
    

Controlled using:

```http
Cache-Control
ETag
Last-Modified
```

✅ Best for static assets.

* * *

# 2\. CDN Cache (Cloudflare / Vercel)

If the browser doesn't have the file, the request reaches the CDN.

```text
User
   │
Cloudflare POP
   │
Origin Server
```

If cached:

✅ No server execution

✅ No database query

Cloudflare and Vercel both cache assets close to users around the world, reducing latency significantly.

* * *

## Cloudflare vs Vercel CDN

| Cloudflare | Vercel |
| --- | --- |
| Great for images & static assets | Built into Next.js deployments |
| Works with any hosting | Optimized for App Router |
| Custom cache rules | Automatic Next.js caching |

In my projects, I use:

*   **Cloudflare R2 + CDN** → Images
    
*   **Vercel** → Next.js pages & functions
    

* * *

# 3\. Router Cache

Router Cache exists **inside the browser**.

Example:

```text
Home

↓

Restaurants

↓

Restaurant Details
```

When navigating between pages, Next.js doesn't always fetch everything again.

Instead, it reuses the **React Server Component payload**, making navigation feel almost instant.

Think of it as **navigation cache**, not page cache.

* * *

# 4\. Full Route Cache

This caches the **entire rendered page**.

```text
Request

↓

Cached HTML

↓

Response
```

No React rendering.

No database.

No server execution.

Works with:

*   Static Pages (SSG)
    
*   ISR
    

Perfect for:

*   Blogs
    
*   Landing pages
    
*   Documentation
    
*   Marketing websites
    

* * *

# 5\. Data Cache

Suppose the page itself isn't cached.

Next.js then checks whether the **fetched data** is cached.

Example:

```ts
await fetch(API_URL, {
  next: {
    revalidate: 60
  }
})
```

The API response is cached for **60 seconds**.

Notice:

❌ The page isn't cached.

✅ Only the fetched data is.

Best for:

*   REST APIs
    
*   Headless CMS
    
*   External APIs
    

* * *

# 6\. `unstable_cache`

What if you're **not** using `fetch()`?

For example:

```ts
await prisma.restaurant.findMany()
```

Next.js can't cache Prisma automatically.

That's where `unstable_cache` comes in.

```ts
import { unstable_cache } from "next/cache";

export const getRestaurants = unstable_cache(
  async () => prisma.restaurant.findMany(),
  ["restaurants"],
  {
    tags: ["restaurants"],
    revalidate: 3600
  }
);
```

Now Prisma isn't executed on every request.

Instead:

```text
Request

↓

Cache

↓

Database (only when needed)
```

Perfect for:

*   Prisma
    
*   Drizzle
    
*   MongoDB
    
*   Redis
    
*   Any expensive database query
    

* * *

# 7\. `use cache` (The Future)

Next.js is gradually replacing `unstable_cache` with the simpler `use cache` directive.

Example:

```ts
"use cache";

async function getRestaurants() {
  return prisma.restaurant.findMany();
}
```

Same goal.

Cleaner syntax.

If you're starting a new project, keep an eye on `use cache` as it becomes the preferred API.

* * *

# Quick Comparison

| Cache | What It Caches |
| --- | --- |
| Browser Cache | CSS, JS, Images, Fonts |
| CDN | Static assets & responses |
| Router Cache | Navigation between pages |
| Full Route Cache | Entire rendered page |
| Data Cache | `fetch()` responses |
| `unstable_cache` | Database functions & expensive operations |

* * *

# Real Example

Imagine someone opens:

```text
https://example.com/restaurants
```

The request may follow this path:

```text
Browser Cache
        │
Miss
        │
Cloudflare CDN
        │
Miss
        │
Full Route Cache
        │
Miss
        │
Data Cache
        │
Miss
        │
unstable_cache
        │
Hit ✅
        │
Return Restaurant List
```

Notice something?

The database was **never queried.**

That's exactly what good caching should achieve.

* * *

# Common Mistakes

❌ Wrapping `fetch()` inside `unstable_cache`

❌ Using `no-store` everywhere

❌ Forgetting cache invalidation

❌ Caching frequently changing data for too long

❌ Assuming every cache works the same

Each cache has a different responsibility.

* * *

# Final Thoughts

Next.js doesn't have **one cache**.

It has **multiple cache layers**, each solving a different problem.

A typical production app uses several of them together:

*   Browser Cache for assets
    
*   CDN for global delivery
    
*   Router Cache for navigation
    
*   Full Route Cache for static pages
    
*   Data Cache for `fetch()`
    
*   `unstable_cache` (or `use cache`) for database queries
    

Understanding where each cache fits is the first step toward building fast, scalable applications.

In **Part 2**, we'll explore:

*   `revalidateTag()`
    
*   `revalidatePath()`
    
*   `updateTag()`
    
*   SWR
    
*   React Query
    
*   Dynamic Rendering
    
*   Real production caching architecture
    
*   Common pitfalls
    

Happy caching! 🚀

* * *

## Useful Resources

*   Next.js Caching: https://nextjs.org/docs/app/guides/caching
    
*   unstable\_cache: https://nextjs.org/docs/app/api-reference/functions/unstable\_cache
    
*   React Server Components: https://react.dev/reference/rsc/server-components
    

* * *
