AL+

Language: RO (română) EN (English)

Email us: paul@alplustech.com

Next.js caching explained: why version 15 stopped caching by default

Next.js 15 turned off default caching for fetch, GET handlers and page navigations. Why the old default served stale data, and how to audit a Next.js 14 app.

Citiți articolul în română

On 23 May 2024 the Next.js team published the Next.js 15 release candidate and, in a section called "Caching updates", reversed one of the App Router's founding decisions. fetch requests, GET Route Handlers and client-side navigations would no longer be cached by default. The team's words were measured: "Based on your feedback, we re-evaluated our caching heuristics."

Two years on, the plainer reading holds up. Caching data by default was the wrong default. It made apps fast in demos and quietly wrong in production, and it put the cost on the people least able to see it: the developer who inherited the app, and the customer looking at yesterday's prices. A framework should make freshness the default and caching a decision someone names and owns. If you run a Next.js 14 App Router app, especially one you did not build, you should check where it serves stale data without anyone knowing.

What Next.js 14 did by default

The Next.js 14 caching documentation is honest about the design. It lists four separate caching mechanisms: Request Memoization, the Data Cache, the Full Route Cache and the client-side Router Cache. Then it states the policy: "By default, Next.js will cache as much as possible to improve performance and reduce cost. This means routes are statically rendered and data requests are cached unless you opt out."

In practice that meant:

  • Under the default static configuration, a server-side fetch without a cache option used force-cache, and the result went into the Data Cache, which "persists across deployments". Shipping a new version did not clear it. Dynamic functions such as cookies() and segment configuration could change that default; an unannotated call alone does not prove caching.
  • A route with no dynamic function (cookies(), headers(), search params) and no uncached request was statically rendered at build time. A page that read straight from a database through an ORM, without fetch, gave Next.js nothing to detect, so it rendered once and kept serving that output.
  • GET Route Handlers were cached unless they used a dynamic function or config, as the RC post recalls.
  • In the browser, the Router Cache kept visited pages for 30 seconds if they were dynamic and 5 minutes if they were static. Revalidating data in a Route Handler did not clear it, so the Router Cache "will continue to serve the previous payload until a hard refresh, or the automatic invalidation period has elapsed" (caching docs).

Each choice can be defended alone. Together they create a bug that development hides. The same documentation notes that "in development mode, fetch data is reused for Hot Module Replacement, and caching options are ignored for hard refreshes." The developer refreshes, sees fresh data and moves on. The Full Route Cache only exists after next build. The first person to meet the stale page is a user in production, and what they report is "I changed it and nothing happened", which sounds like a bug in the feature, not the cache.

What changed, and what the team said later

The RC changed three defaults. Its release notes title two of the pull requests plainly: "[Breaking] Disable automatic fetch caching" and "[Breaking] disable client router cache for page segments". Next.js 15 shipped the changes as stable on 21 October 2024, listed as a breaking change. fetch no longer defaults to force-cache. GET handlers are no longer cached. The Router Cache has a stale time of 0 for page segments, so ordinary navigation no longer reuses those page segments by default. Server-side caches and static output may still contain older data.

Three days later, Sebastian Markbåge's post Our Journey with Caching (24 October 2024) said what the release notes only implied: "the developer experience suffered due to the caching defaults and controls we provided." He named the pieces too: fetch cached by default, too little control over database access that did not use fetch, and a pile of segment configs (dynamic, fetchCache, revalidate) added as escape hatches. The experimental model he described asks you to decide, for every piece of data, whether it is cached or runs on each request. "Nothing is cached by default. No more hidden caches."

That model became Cache Components in Next.js 16 (21 October 2025), built around a "use cache" directive and enabled with a config flag. The release post sets it against "the implicit caching found in previous versions of the App Router" and calls caching with Cache Components "entirely opt-in". Two major versions in a row moved the same way. That is the framework's maintainers agreeing with the critics.

The strongest case for the old default

The old default deserves a fair hearing. Most pages on most sites are read far more often than they change. Caching by default gives every team the fast first load of a static site without asking them to understand caching, and it keeps database load and hosting bills down. An uncached default moves that cost onto every request, and teams that never think about caching will pay for it in latency and compute. Markbåge's post admits the trade-off: moving data fetching to the server meant "sacrificing the great initial loading performance of Jamstack".

That argument is right about the cost and wrong about who should carry the risk. A slow, correct page shows up in monitoring and gets fixed by someone adding a cache on purpose. A fast, wrong page shows up as a customer complaint, a support ticket or an order placed at an old price, and someone has to work backwards through four cache layers to find it. Stale data is a correctness bug. Defaults should fail in the direction you can see.

Where the conclusion does not hold

The reversal did not make Next.js 15 "uncached", and teams that upgraded expecting that were surprised. The Next.js 15 fetch reference describes the default as "auto no cache": Next.js "fetches the resource from the remote server on every request in development, but will fetch once during next build because the route will be statically prerendered" (fetch docs, v15). A route with no dynamic APIs is still built once and served as is. The failure that development hides is still there for static routes, just narrower. The RC post also listed what stayed cached: shared layouts are not refetched on navigation, back and forward navigation restores from cache, and loading.js is kept for five minutes.

The thesis also matters less for some apps. A marketing site, documentation or a blog that rebuilds on every content change was well served by the old default; static rendering is the point there. The argument is about apps whose data changes between deployments: orders, stock, bookings, account pages, admin screens.

How to audit a Next.js 14 app you inherited

Most Next.js 14 apps still running were built under the old defaults, often by someone who has since left. If you are taking over one of them, these checks help expose stale-data risks before users do.

  1. Reproduce production, not development. Run next build and next start locally against real-shaped data. Change a record and reload. next dev will not show the problem.
  2. List every server-side fetch without an explicit cache or next.revalidate option. Treat these as audit candidates, not proven cached reads. The v14 reference defaults to force-cache unless a dynamic function such as cookies() has been used; effective dynamic and fetchCache settings can override that. Check call order, inherited configuration and request context before classifying a read.
  3. Find pages that read from a database or SDK directly and use no dynamic function. With default static settings these can render at build time; check effective segment configuration. Decide for each whether that is intended.
  4. Check every GET Route Handler for a dynamic function or export const dynamic.
  5. Follow every mutation. Where a mutation affects cached reads, identify the relevant revalidatePath or revalidateTag call and any client refresh needed. Remember that revalidating from a Route Handler leaves the browser's Router Cache stale for 30 seconds on dynamic pages and five minutes on static ones.

The fix is to make each decision visible in the code. In Next.js 14 the options look like this:

For a mixed orders/catalogue route, keep the segment in its default dynamic = 'auto' mode and inspect inherited fetchCache settings. Give orders an explicit cache: 'no-store'; this opts the route out of the Full Route Cache. Give the catalogue an explicit cache: 'force-cache' with next.revalidate: 300 and a catalogue tag. The v14 caching guide permits cached and uncached reads in a dynamically rendered route. Do not add dynamic = 'force-dynamic': it disables the Data Cache too and defeats that catalogue policy. This is a configuration explanation checked against the versioned documentation, not an executed sample; verify the actual route in production mode before relying on it.

Whether to upgrade comes after the audit. Upgrading to 15 or 16 changes the defaults but does not tell you which of the old cached reads the business relied on. Upgrading without the audit swaps one set of surprises for another.

The principle outside Next.js

The lesson applies to any cache a business depends on: a CDN in front of an API, a Redis layer, a materialised report, a mobile app's offline store. Each one should have a name, an owner, a stated lifetime and a known event that clears it. A cache nobody chose is a cache nobody will think of when a customer says the numbers are wrong. Next.js took two major versions to make that the default. An app you run can meet that standard sooner, and regular maintenance is where that work belongs.

Sources