ik several reasons why app router is hated because it breaks every mental shortcut engineers expect from a router. People see a folder /app/dashboard and think, “okay, a page, easy.” No. That file is a server component segment. That layout is a suspense boundary. Every fetch you place inside it is an async landmine. One tiny fetch in a child layout can suspend the parent, trigger Flight payload waterfalls, and your client components hydrate in the wrong order. Debugging this is brutal. Flight payloads are opaque JSON structures representing React elements. If something fails, you get undefined or cannot read property X of null. Good luck tracing it.
Nested layouts? Forget it. Passing useParams, usePathname, or context through multiple segments can fail silently. The parent segment may not have resolved, leaving child segments with undefined. Streaming SSR tries to help. It flushes partial HTML while children suspend. But this introduces fallback flickers. The UI shows a placeholder, then swaps content, then sometimes remounts unexpectedly. That’s what people see and hate.
Caching is another nightmare. Each segment has its own cache. Fetches inside server components are cached per segment. Revalidation only affects the segment, but nested async children may cascade refetches, streaming new Flight payloads, breaking client hydration. Engineers hate this because it’s non-intuitive, inconsistent, and easy to break without realizing why.
Suspense boundaries, streaming, and client/server separation compound the pain. Client components must be explicitly marked. Server components cannot access client-only hooks or state. Cross-boundary communication is fragile. One wrong assumption, one misaligned fetch, and hydration breaks silently.
In short, people hate the App Router because it forces engineers to think differently. You can’t reason about pages anymore; you must reason about trees of async segments, Flight payload serialization, per-segment caches, streaming SSR, suspense boundaries, and incremental hydration. It is fast, future-proof, and minimal on client JS, but it is opaque, brittle, and extremely difficult to debug.
That’s the reality. No hype. No fluff. It works beautifully when you understand it, and it breaks horribly when you don’t. That’s why engineers rage, and that’s why they swear by the old Page Router when the app is small.