Next.js and the cost of magic
Next made hard things easy by making simple things opaque. Caching you did not ask for, a router that changed under your feet, and a framework whose best behaviour lives on one host — a field report from years of shipping it.
I have built more products on Next.js than on anything else, and I would still reach for it in plenty of situations. That is exactly why its costs are worth naming precisely. Not the Twitter version — "Next bad, framework heavy" — but the specific bills that arrive months later, itemised, when the honeymoon is over and the app is load-bearing.
They all trace back to the same trade. Next made hard things easy by taking decisions away from you. That is what magic is. And every decision it took away is one you eventually need back.
The cache you never asked for
For two major versions, fetch() inside Next did not behave like fetch(). Responses were cached by default — aggressively, invisibly, at several layers with different names. Teams shipped dashboards that showed yesterday's data and spent days learning the difference between the Data Cache, the Full Route Cache, the Router Cache and the browser's own, each with its own invalidation story.
Then Next 15 flipped the default. Uncached, like the platform. The flip was the right call — and it was also an admission, and a migration, and a re-audit of every data read in every app that had learned the old rules. When a framework overrides a web primitive and later un-overrides it, both moves are breaking changes, and both were yours to absorb.
The lesson is not "caching is hard", although it is. The lesson is that implicit caching is a loan. You ship faster today because something cached for you; you pay later when you need to know exactly what is cached, and the answer is spread across four layers of documentation.
A router that changed under your feet
The Pages Router was small and legible: a file is a route, getServerSideProps runs on the server, props arrive in your component. You could hold it in your head.
The App Router is more capable and much bigger: layouts, nested streaming, server components, client boundaries, parallel routes, intercepting routes. Some of that power is real. But it did not arrive as an addition — it arrived as the future, with the old router in maintenance mode and every new feature landing on one side of the line. Migration was not a codemod; it was a rewrite of your data flow, your error handling and your mental model, undertaken not because your product needed it but because your framework moved.
There is a special tiredness in teams that have done this twice. It shows up as a question in planning meetings: "is this a Next thing or an us thing?" When that question comes up often, the framework has stopped being infrastructure and started being weather.
"use client" is a confession
The directive itself is fine. What it confesses is not: the split between server components and client components pushed a distributed-systems question — where does this code run? — into every file of an application, to be answered by every developer, forever, one directive at a time.
Get it wrong in one direction and you ship a server secret to the browser. Get it wrong in the other and your bundle quietly grows, because one "use client" at the top of a tree drags everything under it across the line. The framework cannot fully check either mistake, which is why every team I know has a folklore document about it — and folklore is what you write when the type system can't help you.
The best version lives on one host
Run Next on its home platform and everything works: image optimisation, ISR, the edge pieces, the caches. Run it on your own boxes — which is where plenty of European clients contractually need it — and you meet the asterisks. Self-hosted ISR wants shared storage you now operate. Image optimisation wants a service you now provision. Some behaviours differ just enough that your staging environment tells you less than it should.
None of this is sinister; a company funds the framework and the framework fits the company. But it means the honest comparison is never "Next versus something else". It is "Next on their infrastructure, or a different set of trade-offs on yours" — and that second option deserves to be evaluated on purpose, not discovered during a procurement review.
What I actually want
Fewer layers I have to trust and more I can read. Data fetching that behaves like the platform primitive it wraps. A router that fits in working memory. Costs that appear where I can see them — in my code — rather than where I cannot: in a cache with four names, on a host I did not choose.
Easy setup is worth one afternoon. Legibility is worth every afternoon after that. Magic optimises the first day of a project; the bill is presented across the following thousand. Whatever you build on, check who holds the loan.