Three months ago, I pushed a "fully static" page to a UAE-based e-commerce client using Next.js 13’s App Router. It showed outdated price data for 10 minutes until I realized I’d misunderstood how React Cache handles background revalidation. That’s when I rolled up my sleeves and started digging into real-world caching patterns for App Router — not just the theoretical ones in docs.
App Router Caching Mechanics: Not What I Expected
At first glance, force-cache and no-store seem straightforward. But in production, things get messy. Here’s what I learned:
- •Static generation ≠ global cache. Pages with
generateStaticParamsmight still fetch fresh data if not properly cached. - •Server Components are lazy. A
use(fetch(...))call inside a component might skip cached data if placed incorrectly. - •Headers trump everything. Vercel Edge caches respect
Cache-Controlheaders way more aggressively than your code-levelcacheoptions.
For a logistics client in Dubai, I accidentally cached Arabic/English language switches for all users because I used cookie() inside a cached component. Took me a full day to isolate that issue.
Server Component Data Fetching: The Real Game-Changer
Next.js 13.5 introduced better Streaming Server Components, which I’ve found play well with edge caching. Example:
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 300 } // 5 minutes
});
return res.json();
} But here’s the kicker: revalidate works differently in SSG vs SSR. One of my Tawasul Limo pages showed stale limo availability data until I realized I needed revalidatePath after every booking.
CDNs Add Another Layer of Chaos
I once deployed a food delivery app where Redis cached 80% of menu requests successfully — until we started adding location-based promotions. The CDN started serving cached versions to the wrong regions. Fixed it by:
- Adding
Vary: x-vercel-id, accept-languageheaders - Including region codes in Redis keys (e.g.,
menu:AE:Dubai) - Setting
Cache-Control: s-maxage=60for edge,max-age=10for browsers
Without those Vary headers, Arabic content would sometimes show up to English-only users in Abu Dhabi. Not good.
Common Pitfalls I’ve Debugged
- •Over-fragmented caching: When you split data into 20+ cached pieces, one stale chunk breaks the whole page.
- •Third-party APIs ignoring headers: Some services just won’t send
Cache-Control— force your own withfetch(..., { next: { revalidate: 60 } }). - •Turborepo conflicts: Shared components might cache differently in monorepos. We had a design system token cache persist across deployments for Reach Home Properties.
Frequently Asked Questions
How to cache dynamic routes in Next.js app router?
Use params in server components with fetch(..., { next: { revalidate: 60 } }). For route groups like /[region]/products/[id], include both param values in your cache key.
Does revalidatePath work in App Router?
Yes, but only if you’re using incremental static regeneration (ISR). For dynamic routes like /ar/properties/123, pass the full path: revalidatePath('/ar/properties/123', 'page').
What’s the fastest cache invalidation method in production?
Vercel’s native purge API if you’re on Edge. For self-hosted setups, I use Redis keys with expiration + fetch’s revalidate flag. DAS Holding’s corporate site uses a hybrid approach that clears regional caches in <2 seconds.
How to bypass cache in development?
Add no-cache headers in next.config.js:
async headers() {
return [{ source: '/api/:path*', headers: [{ key: 'Cache-Control', value: 'no-cache' }] }]
} Want to avoid caching gotchas in your Next.js projects? Let’s talk — I’ve probably already debugged that exact issue for a UAE client. Book a free consultation or Get in touch.