# The Ultimate Guide to Next.js Caching (Part 2): Cache Invalidation, SWR, React Query & Real Production Architecture

> This is **Part 2** of my Next.js Caching series. In Part 1, we covered Browser Cache, CDN, Router Cache, Full Route Cache, Data Cache, `unstable_cache`, and `use cache`.

* * *

# Previously...

We learned that a request flows through multiple cache layers:

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

But one important question remains...

> **How does the cache know when data has changed?**

That's where cache invalidation comes in.

* * *

# Why Cache Invalidation Matters

Imagine someone edits a restaurant menu.

Without invalidation:

```text
Admin

↓

Database Updated

↓

Cache Still Old ❌
```

Visitors continue seeing outdated data.

Instead we want:

```text
Admin

↓

Database Updated

↓

Invalidate Cache

↓

Fresh Data ✅
```

* * *

# `revalidateTag()`

The most common way to invalidate cached data.

When caching:

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

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

After updating data:

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

revalidateTag("restaurants");
```

Everything using that tag becomes stale and is regenerated on the next request.

Perfect for:

*   CMS updates
    
*   Admin dashboards
    
*   CRUD operations
    
*   Menu updates
    
*   Product edits
    

* * *

# `revalidatePath()`

Sometimes you only want to refresh one page.

Example:

```ts
revalidatePath("/restaurants");
```

Useful when:

*   One page changes
    
*   Blog published
    
*   Product edited
    
*   Dashboard refreshed
    

Unlike `revalidateTag`, this targets a specific route.

* * *

# `updateTag()`

`updateTag()` immediately expires cached data without waiting for the next background revalidation.

```ts
updateTag("restaurants");
```

Think of it as:

```text
revalidateTag()

↓

Refresh Soon
```

vs

```text
updateTag()

↓

Refresh Immediately
```

Use it when users should instantly see fresh content after an action.

* * *

# Time-Based Revalidation

Not everything needs manual invalidation.

You can simply let the cache expire.

Example:

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

Flow:

```text
Generate

↓

Cache

↓

5 Minutes

↓

Next Request

↓

Background Refresh
```

Great for:

*   Blogs
    
*   News
    
*   Product listings
    
*   Restaurant menus
    
*   CMS websites
    

* * *

# Which Should You Use?

| Situation | Recommendation |
| --- | --- |
| CMS update | `revalidateTag()` |
| Single page | `revalidatePath()` |
| Immediate refresh | `updateTag()` |
| Time-based updates | `revalidate` |

* * *

# Dynamic Rendering Disables Caching

Some APIs automatically make a page dynamic.

Examples:

```ts
cookies()

headers()

draftMode()

searchParams
```

If your page depends on user-specific information, Next.js cannot safely serve the same cached page to everyone.

Example:

```ts
import { cookies } from "next/headers";

const token = cookies().get("token");
```

Now the page is personalized and won't benefit from full static caching.

Use these APIs only when needed.

* * *

# Where Does SWR Fit?

Everything we've discussed so far happens **before the page reaches the browser**.

Once the page is loaded, **SWR** becomes another cache layer.

```text
Database

▲

unstable_cache

▲

Data Cache

▲

CDN

▲

Browser

▲

SWR
```

Unlike Next.js caches, SWR runs completely on the **client**.

Example:

```tsx
import useSWR from "swr";

const { data } = useSWR("/api/restaurants", fetcher);
```

Instead of fetching repeatedly:

*   SWR caches responses in memory.
    
*   Deduplicates requests.
    
*   Automatically revalidates.
    
*   Supports optimistic UI updates.
    

* * *

# When Should You Use SWR?

Good use cases:

*   Notifications
    
*   User profile
    
*   Reviews
    
*   Shopping cart
    
*   Live dashboards
    
*   Analytics
    

Avoid using SWR for:

*   Landing pages
    
*   Blogs
    
*   Marketing pages
    
*   SEO-critical pages
    

Those are better served using SSR, ISR, or SSG.

* * *

# Cache Flow with SWR

```text
Database
      ▲
unstable_cache
      ▲
Data Cache
      ▲
Full Route Cache
      ▲
Cloudflare CDN
      ▲
Browser
      ▲
SWR
```

SWR is the **last cache**, not the first.

Think of it as the cache your React application uses after everything else has finished.

* * *

# SWR vs React Query

Both libraries solve the same problem: **client-side server state management.**

| Feature | SWR | React Query (TanStack Query) |
| --- | --- | --- |
| Learning Curve | ⭐ Easy | ⭐⭐⭐ Medium |
| Bundle Size | Small | Larger |
| Caching | ✅ | ✅ |
| Background Revalidation | ✅ | ✅ |
| Optimistic Updates | Basic | Excellent |
| Infinite Queries | Basic | Excellent |
| Pagination | Basic | Excellent |
| DevTools | Limited | Excellent |

### My Recommendation

Use **SWR** if:

*   You're already using Next.js App Router
    
*   You only need simple client-side fetching
    
*   You want minimal setup
    

Choose **React Query** if:

*   Your app has complex CRUD operations
    
*   Heavy mutations
    
*   Infinite scrolling
    
*   Offline support
    
*   Complex cache management
    

* * *

# How I Cache a Production Next.js App

This is the caching architecture I use in production.

```text
                 User
                   │
        Browser Cache
                   │
        Cloudflare DNS
                   │
      Cloudflare CDN (Images)
                   │
             Vercel Edge
                   │
             Next.js App
                   │
      ┌────────────┴─────────────┐
      │                          │
 Static / ISR Pages        Dynamic Pages
      │                          │
Full Route Cache        unstable_cache
      │                          │
      │                    Prisma ORM
      │                          │
      └────────────┬─────────────┘
                   │
             Neon PostgreSQL

Images

User
 │
Cloudflare CDN
 │
Cloudflare R2
```

## Why this architecture?

### Images

Images are served directly from:

```plaintext
Cloudflare CDN
        ↓
Cloudflare R2
```

Benefits:

*   Near-zero latency
    
*   Infinite free egress (R2)
    
*   No Next.js server execution
    
*   Reduced Vercel bandwidth usage
    

* * *

### Restaurant List

Restaurant pages use:

*   ISR
    
*   Full Route Cache
    
*   `unstable_cache`
    

Flow:

```text
User

↓

Full Route Cache

↓

Data Cache

↓

unstable_cache

↓

Prisma

↓

Neon
```

If the data is cached, Prisma never runs.

* * *

### Admin Updates

Suppose an admin edits a menu.

```text
Admin

↓

Update Database

↓

revalidateTag("restaurants")

↓

Cache Cleared

↓

Next Request

↓

Fresh Data
```

Only affected pages regenerate.

Everything else stays cached.

* * *

### User Dashboard

For authenticated pages, I avoid Full Route Cache.

Instead:

```plaintext
SSR

+

SWR
```

Reason:

Every user sees different data.

Caching the entire page wouldn't make sense.

* * *

# Which Cache Should I Use?

```text
Need SEO?

├── No
│      │
│      └── CSR + SWR
│
└── Yes
       │
       ├── Never Changes
       │      │
       │      └── SSG
       │
       ├── Changes Every Few Minutes
       │      │
       │      └── ISR
       │
       ├── User Specific?
       │      │
       │      └── SSR
       │
       └── Expensive Database Query?
              │
              └── unstable_cache / use cache
```

* * *

# Common Caching Mistakes

### ❌ Wrapping `fetch()` inside `unstable_cache`

Next.js already caches `fetch()`.

Adding another cache usually isn't needed.

* * *

### ❌ Using `no-store` Everywhere

Many developers disable caching while debugging and forget to remove it.

This forces every request to hit your server and database.

* * *

### ❌ Forgetting Cache Invalidation

Updating the database doesn't automatically refresh cached pages.

Use:

*   `revalidateTag()`
    
*   `revalidatePath()`
    
*   `updateTag()`
    

when content changes.

* * *

### ❌ Very Short Revalidation Times

Example:

```ts
revalidate: 5
```

Refreshing every five seconds often defeats the purpose of caching.

Choose a TTL based on how frequently your data actually changes.

* * *

### ❌ Caching User-Specific Data

Never cache content that depends on:

*   Cookies
    
*   Authentication
    
*   User preferences
    

Each user should receive their own response.

* * *

# Performance Tips

✔ Cache expensive database queries.

✔ Use `unstable_cache` (or `use cache`) for Prisma and ORM calls.

✔ Cache images at the CDN.

✔ Prefer tag invalidation over tiny TTL values.

✔ Let Cloudflare and Vercel serve as much content as possible.

✔ Measure before optimizing.

* * *

# Quick Cheat Sheet

| Cache | Stores | Best For |
| --- | --- | --- |
| Browser Cache | CSS, JS, Images | Static assets |
| Cloudflare CDN | Static assets | Global delivery |
| Router Cache | Navigation | Instant page transitions |
| Full Route Cache | HTML | SSG & ISR pages |
| Data Cache | `fetch()` responses | APIs & CMS |
| `unstable_cache` / `use cache` | Database queries | Prisma, Drizzle, SQL |
| SWR | Client state | Dashboards & live data |
| React Query | Complex client state | Enterprise apps |

* * *

# Final Thoughts

One thing I've learned while building production applications is this:

> **The fastest query isn't the optimized query—it's the one that never runs.**

Next.js gives us multiple caching layers, each solving a different problem.

A well-optimized application usually combines several of them:

*   Browser Cache
    
*   Cloudflare CDN
    
*   Vercel Edge Cache
    
*   Full Route Cache
    
*   Data Cache
    
*   `unstable_cache` / `use cache`
    
*   SWR (or React Query)
    

The goal isn't to cache everything.

It's to cache **the right thing at the right layer**.

Once you start thinking in cache layers instead of individual queries, building fast and scalable applications becomes much easier.

Happy building! 🚀

* * *

# Useful Resources

📚 Next.js Caching

https://nextjs.org/docs/app/guides/caching

📚 `unstable_cache`

https://nextjs.org/docs/app/api-reference/functions/unstable\_cache

📚 `use cache`

https://nextjs.org/docs/app/api-reference/directives/use-cache

📚 SWR

https://swr.vercel.app

📚 TanStack Query

https://tanstack.com/query/latest

* * *

## Suggested SEO Titles

*   The Ultimate Guide to Next.js Caching
    
*   Next.js Caching Explained with Real Production Examples
    
*   Browser Cache vs Data Cache vs `unstable_cache`
    
*   Mastering Next.js Cache Invalidation
    
*   How I Cache Production Next.js Apps
    

* * *

## Meta Description

Learn how modern Next.js caching works with Browser Cache, Cloudflare CDN, Vercel, Full Route Cache, Data Cache, `unstable_cache`, `use cache`, SWR, React Query, and production-ready cache invalidation strategies.

* * *

## Suggested Tags

`nextjs` `react` `typescript` `performance` `webdev` `caching` `cloudflare` `vercel` `prisma` `softwareengineering`

* * *

## Cover Image Prompt

**"Premium minimalist white-themed 16:9 tech illustration showing the complete Next.js caching pipeline. Visualize Browser Cache → Cloudflare CDN → Vercel Edge → Router Cache → Full Route Cache → Data Cache → unstable\_cache/use cache → Prisma → PostgreSQL, with glowing blue and purple gradients, modern isometric layers, elegant typography reading 'The Ultimate Guide to Next.js Caching – Part 2', clean Apple-inspired design, subtle glassmorphism, futuristic developer aesthetic, digital art, no clutter."**
