An Honest Take on the Next.js App Router After a Year
I've shipped real apps on the App Router since the beta. Server Components changed how I think about data fetching, the caching defaults bit me more than once, and 'use client' is both the best and most misunderstood part. Here's the unvarnished version.
The short answer: After a year of shipping real production apps on the Next.js App Router, I'd use it again — the Server Components model is a genuine step forward for data fetching, but the caching defaults were the biggest source of surprise bugs and you have to learn them deliberately before you trust them.
I've been running production apps on the Next.js App Router since it was still flagged as beta, through all the churn, the breaking changes, and the documentation that occasionally lagged reality. Enough time has passed that I can give an honest verdict instead of a hot take. The short version: it got the big architectural idea right, and it got the ergonomics of its most powerful feature — caching — badly wrong at launch. Both things are true at once.
Server Components are the real win
The headline feature deserves the headline. React Server Components let me fetch data where it's used, on the server, with no client-side loading waterfall and no useEffect dance. The data layer collapses into the component that needs it.
The mental model takes a minute: by default, everything is a Server Component, and you opt into the client with "use client" only when you need interactivity, state, or browser APIs. Once that clicks, you stop shipping JavaScript you don't need. My bundles got meaningfully smaller because the data-fetching and templating code simply never crosses the wire. That's not a micro-optimization — it's a different shape of application.
The caching defaults were the real gotcha
Here's where I lost days. The App Router shipped with aggressive, implicit caching on fetch and route segments, and the defaults were silent. A fetch would get cached indefinitely, a dynamic page would get statically frozen, and nothing in the code told you it was happening. I had a dashboard showing yesterday's numbers because a fetch had been quietly memoized across requests, and no amount of staring at the component revealed why.
The feature is powerful — the layered cache is genuinely clever. But a cache you can't see is a cache that will bite you, and making it the invisible default meant every team learned about it through a production incident instead of the docs. "Correct but surprising" is a bad place for a default to live.
"use client" is misunderstood, not evil
The most common complaint I hear is that "use client" is a step backwards, forcing you to carve your app into server and client halves. In practice the problem is almost always that people put the directive too high in the tree. Mark a top-level layout as a client component and you've just dragged everything beneath it onto the client. The fix is to push the boundary down — keep pages and layouts on the server, and isolate "use client" to the small interactive leaves: the button, the dropdown, the form. Server Components can render client ones; the boundary just has to be drawn at the right depth.
The verdict after a year
Would I start a new project on it today? Yes — but with eyes open. The architecture is the future of React and I'm not fighting it. Server Components genuinely improved my apps. But I now treat caching as something I configure explicitly on day one rather than something I discover in staging, and I draw my client boundaries deliberately. The App Router didn't make my job simpler; it made it different, and traded a familiar set of problems for a more powerful set of tools with sharper edges. A year in, I'll take that trade — I just wish the sharpest edge hadn't been the invisible default.