Build1 publisher3 min readPublished
Next.js 14-era GET Route Handlers run on every request after an upgrade to 15 or 16
Next.js 15 switched GET Route Handlers from static to dynamic, so handlers written for 14 now run on every request. Version 16 keeps that default and adds a caching mode in which a common 14-era config line becomes an error.
The Engineer · Build desk

What happened
- Next.js 16 kept the per-request default and added a second caching model, Cache Components, enabled by setting cacheComponents: true in next.config.
- With Cache Components on, route segments that still export dynamic, revalidate or fetchCache error, including the force-dynamic line the 14 docs opened with.
- Caching a GET handler on purpose in 16 takes force-static or revalidate under the default model, or a "use cache" helper under Cache Components.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost Endpoints that used to return a response saved at build time now query the database on every request, and after an upgrade the database and response latency take that load.
- exposure Teams following a 14-era tutorial apply an opt-out fix to a framework that now needs an opt-in, so an endpoint they meant to cache ships uncached.
- constraint A codebase full of 14-era files cannot turn on Cache Components until someone removes the leftover dynamic, revalidate and fetchCache exports.
A tutorial published on dev.to starts with the handler most people write first. The `GET` function runs `SELECT id, name, price FROM products` and returns `Response.json(products)` [17]. It reads no `Request`, uses no other HTTP method, calls neither `cookies()` nor `headers()`, and sets no segment config, so none of the Next.js 14 opt-outs apply [3]. Next.js 14 evaluated it during `next build`, and the database stopped mattering until the next deploy [4]. That handler would have benchmarked beautifully. The tutorial illustrates it with a price that drops from $40 to $35 while the API keeps answering $40. The author says this is a scenario built from each version's documented default, not an incident report [1].
The change was announced. The Next.js 15 release notes from October 2024 state: "In Next.js 15, GET functions are not cached by default." [5] The `route.js` reference for 16.3.6 logs the change under v15.0.0-RC: "The default caching for GET handlers was changed from static to dynamic." [6] The default flipped once, in 15. Version 16 kept it and added Cache Components as a second caching model [7]. By the time the tutorial's author checked the docs in September 2026, per-request `GET` had been the default for about 23 months [1].
In my view the 15 default is the right one for any handler that reads data that changes. The tutorial sums it up this way: correctness improves, and load and latency change [16]. For the products endpoint, that means one query per request where there used to be none [9].
The upgrade hazard is one config line. The Next.js 14 Route Handlers page opened with `export const dynamic = "force-dynamic"`, so plenty of 14-era files carry it [10]. Under 16's default model the line is redundant for a `GET` [10]. Under Cache Components it breaks the route. The docs say route segments that still export `dynamic`, `revalidate` or `fetchCache` will error [11]. Turning a directive whose meaning has changed into an error is good design. The old line cannot sit in the file and mislead the next reader. A fresh `create-next-app` project leaves the flag unset, and its generated config is empty [8].
How you opt back into caching depends on the model. Under the default one, the tools are the `force-static` and `revalidate` options. Under Cache Components, it is a `"use cache"` helper [12]. The fix a 14-era tutorial teaches is to opt out, and the fix 16 needs is to opt in, so a copied tutorial pushes the code the wrong way [13]. Handlers with dynamic segments have a second change to absorb: `params` is now a Promise, and synchronous access is gone [15].
What to watch
- If create-next-app starts setting cacheComponents: true, leftover 14-era dynamic exports copied into new projects would error on day one.
- A codemod or build warning that flags GET handlers relying on 14's implicit build-time caching would cut the risk of an unplanned jump in database load on upgrade.