The biggest behavioral change in v15
In Next.js 14, fetch() in Server Components was cached by default. Developers were burned by stale data they didn't know they had. Next.js 15+ flipped the default: fetch is uncached unless you opt in.
Opt-in caching options
| Strategy | How to set it |
|---|---|
| Cache forever (until revalidate) | fetch(url, { cache: 'force-cache' }) |
| ISR — revalidate every N seconds | fetch(url, { next: { revalidate: 60 } }) |
| Tag for on-demand invalidation | fetch(url, { next: { tags: ['posts'] } }) |
| No cache (default) | fetch(url) or explicit { cache: 'no-store' } |
| Route-level default | export const revalidate = 60 |
What the migration looks like
If your v14 app feels slow after upgrading, find the routes that don't actually need fresh data on every render and add cache: 'force-cache' or next: { revalidate }. Those were getting free caching before; they aren't now.
Write the freshness requirement next to each request in product language before choosing an option. A catalog description may tolerate an hour of age; account balance may not. Translate that decision into force-cache, revalidate, or no-store instead of restoring the old default everywhere.
The framework default is a safe starting point, not your product policy. Caching everything may make an upgrade benchmark look better while serving incorrect data. Caching nothing spends compute on content that changes once a day. Call a timestamped endpoint under each cache mode in a production build and record consecutive responses. Audit routes upgraded from v14 and require every nontrivial cache choice to have a reason reviewers can repeat.