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:
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:
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.
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:
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.
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:
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:
await prisma.restaurant.findMany()
Next.js can't cache Prisma automatically.
That's where unstable_cache comes in.
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:
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:
"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:
https://example.com/restaurants
The request may follow this path:
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(oruse 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





