Spots

Next.js Route Handlers: GET Stopped Caching in 15 — How to Cache in 16

Picture a Next.js 14 app whose GET /api/products handler reads prices from the database. A price drops from $40 to $35, and the API keeps answering $40: Next.js 14 ran the handler once at build time and serves that saved response. Upgrade the same file to Next.js 15 and the stale price is gone, and so is the cache. Every request now runs the query. Same code, opposite behaviour.

That's a scenario, not an incident report, but

That's a scenario, not an incident report, but each half follows from its version's documented default. Next.js Route Handlers switched GET from static to dynamic in version 15, and 16 added a second caching model on top. This episode covers what changed, what it means for code and tutorials from the 14 era, and how to cache a Route Handler on purpose today. By the end of this article you'll be able to:

Explain what changed for GET Route Handlers between

Explain what changed for GET Route Handlers between Next.js 14 and 15, and why a handler migrated from 14 now runs on every request

Cache a GET handler on purpose in Next.js

Cache a GET handler on purpose in Next.js 16: force-static and revalidate under the default model, a "use cache" helper under cacheComponents Predict when a GET handler is prerendered at build time under cacheComponents, and what stops it Read dynamic segments correctly now that params is a Promise and synchronous access is gone Stream a response, and decide when a Route Handler is the right tool instead of a Server Action or a page

You've built at least one App Router route

You've built at least one App Router route and used fetch inside a Server Component. No Pages Router experience is needed.

This is written against Next.js 16.3 (npm latest

This is written against Next.js 16.3 (npm latest is 16.3.6; docs verified September 2026). Next.js 16 has two caching models: the default one, and Cache Components, enabled with cacheComponents: true in next.config. A fresh create-next-app project doesn't set the flag (its generated config is empty), so this article covers both, plus the Next.js 14 behaviour you'll still meet in older code and tutorials. The problem: Next.js Route Handlers changed their default in 15 Here's the handler from the scenario, written the way most people write their first one:

On Next.js 14, this is the $40 bug

On Next.js 14, this is the $40 bug. The Next.js 14 docs say so directly: "Route Handlers are cached by default when using the GET method with the Response object." The ways out were reading the Request object, using another HTTP method, calling cookies() or headers(), or setting a segment config option. This handler does none of those, so Next.js 14 evaluated it during next build, and the database stopped mattering until the next deploy.

On Next.js 15, the default flipped. From the

On Next.js 15, the default flipped. From the Next.js 15 release notes (October 2024): "In Next 14, Route Handlers that used the GET HTTP method were cached by default unless they used a dynamic function or dynamic config option. In Next.js 15, GET functions are not cached by default." The route.js reference for 16.3.6 records the same change in its version history: "The default caching for GET handlers was changed from static to dynamic" (v15.0.0-RC).

So on Next.js 16, under the default model

So on Next.js 16, under the default model, that handler runs on every request: correct prices, and one query per request where there used to be none. What the change means for code migrated from 14

Handlers that were quietly static now run per

Handlers that were quietly static now run per request. Correctness improves; load and latency change. If an endpoint should be cached, in 15+ you have to say so.

News

Next.js Route Handlers: GET Stopped Caching in 15 — How to Cache in 16

Picture a Next.js 14 app whose GET /api/products handler reads prices from the database.

@spots #dev
Source: Dev.to
See more like this