I spent a miserable afternoon a while back debugging a production issue where a user's private financial dashboard was showing cached data from a completely different account. Why? Because Next.js 14 decided that a standard, innocent-looking fetch() call should be cached aggressively until the end of time unless you explicitly went out of your way to block it.
It was the ultimate frontend footgun.
Thankfully, the ecosystem listened. With the recent maturity and widespread production adoption of Next.js 15 and the latest 16.x patches, the Vercel team completely flipped the script. Caching is now opt-in by default.
If you haven't audited your full-stack data fetching patterns lately, the era of pulling your hair out over stale data is overβbut it means you need to adjust how you write your data layer today.
The Nightmare of "Everything is Cached"
In previous versions, Next.js tried to be clever. It assumed that if you were making a GET request, you wanted it to be as fast as possible, so it statically cached the result at build time or on the first request.
While that worked great for basic blogs, it was a disaster for real-world dynamic apps. You had to constantly litter your code with things like:
// The old boilerplate dance just to keep data fresh
export const revalidate = 0;
// or
fetch('[https://api.example.com/data](https://api.example.com/data)', { cache: 'no-store' });
If you forgot that single configuration object on an API route handler, your dynamic endpoint was suddenly serving stale data to your users, and your serverless functions were returning zombie responses.
The Modern Fix: Uncached by Default
In the current Next.js architecture, fetch requests and GET Route Handlers are uncached by default.
If you don't explicitly ask for caching, Next.js treats your request as dynamic. Your database gets queried when the user requests the page, just like traditional backend frameworks (like Node/Express, Rails, or Laravel) have done for decades.
// How we write data fetching now (Next.js 15/16 baseline)
// This hits the API live on every single request automatically.
const res = await fetch('[https://api.yourbackend.com/users/me](https://api.yourbackend.com/users/me)');
const user = await res.json();
If you actually want to cache a heavy public payload (like a product list or a public configuration), you now have to be intentional about it:
// Explicitly opting into performance optimization
const staticData = await fetch('[https://api.yourbackend.com/products](https://api.yourbackend.com/products)', {
cache: 'force-cache'
});
This simple swap dramatically lowers the cognitive load of shipping full-stack apps. You optimize for correctness first, then opt into performance later.
Watch Out for the Asynchronous Request APIs
The caching overhaul also brought another massive structural change that catches developers off guard when upgrading: request-scoped metadata APIs are now completely asynchronous.
If you are reading cookies, headers, or route params inside a Server Component, you can no longer access them synchronously.
// β This will throw an error in modern Next.js
const cookieStore = cookies();
const token = cookieStore.get('session');
// The right way
const cookieStore = await cookies();
const token = cookieStore.get('session');
Why did they do this? To make Partial Prerendering (PPR) actually work. By making these APIs return a promise, the framework can instantly serve a static HTML shell of your page while waiting for the request-dependent dynamic parts (like user sessions or headers) to resolve and stream into the client.
Over to you
Did you get bitten by the old aggressive caching model, or did you actually prefer things being fast by default? Let's talk in the comments.
Connect with Me
If you found this guide helpful, let's connect and discuss modern development workflows!
- π» GitHub: johnnylemonny
- βοΈ DEV.to: johnnylemonny
This article was created with the help of AI
Top comments (1)
How does 'uncached by default' impact server-side rendering in Next.js, and are there any edge cases to watch out for? I'd love to swap ideas on this, following your posts for more insights on optimizing performance.